核實(shí)時(shí)系統(tǒng):Linux 4.9環(huán)境搭建與延遲排查實(shí)戰(zhàn))
1. 先說結(jié)論這套雙內(nèi)核組合解決的是最壞情況延遲剛接手產(chǎn)線項(xiàng)目那會(huì)兒看到 xenomai 3.0.5/3.0.9 linux 4.9.38/4.9.90 這套組合第一反應(yīng)也是都什么年代了還在用 4.9 內(nèi)核等我把 EtherCAT 主站跑起來、把伺服周期從 1ms 壓到 500us才明白這套老組合能一直在工控設(shè)備上存活是有理由的。這篇文章就是把環(huán)境搭建到穩(wěn)定運(yùn)行的配置細(xì)節(jié)以及過程中踩過的一堆 BUG 原原本本記下來給后面接手類似項(xiàng)目的人省點(diǎn)時(shí)間。Xenomai 的 Cobalt 模式本質(zhì)是在 Linux 內(nèi)核里插入一條 IRQ pipeline中斷流水線讓實(shí)時(shí)任務(wù)跑在一個(gè)更霸道的執(zhí)行域里。普通 Linux 任務(wù)照常使用各種生態(tài)實(shí)時(shí)任務(wù)走專用通道兩者共享 CPU 但優(yōu)先級(jí)完全不對(duì)等。它真正的價(jià)值不是平均延遲而是最壞情況延遲。PREEMPT_RT 方案能把平均延遲做到幾十微秒但 worst case 偶爾給你來一下幾百微秒運(yùn)動(dòng)控制里這一下可能就是一次急停報(bào)警。Xenomai 要做的就是把 max 值釘死在個(gè)位數(shù)的微秒量級(jí)這是雙內(nèi)核方案到今天仍被工控行業(yè)繼續(xù)采用的核心原因。這篇文章適合兩類人一類是在 x86_64 工控機(jī)或 ARM 板子上做運(yùn)動(dòng)控制、數(shù)據(jù)采集需要硬實(shí)時(shí)但還沒碰過 Xenomai 的開發(fā)者另一類是已經(jīng)能編譯內(nèi)核、但被各種延遲毛刺折騰得睡不著覺的老手。我會(huì)把版本搭配、打補(bǔ)丁、內(nèi)核配置、用戶態(tài)編譯運(yùn)行、以及故障排查的思路全部攤開講每個(gè) BUG 都會(huì)說明現(xiàn)象、根因和最終的處理手段盡量讓你能照著抄作業(yè)。1.1 Cobalt 雙內(nèi)核到底怎么個(gè)工作法理解 Xenomai 不用想得太玄。你可以把 Linux 內(nèi)核想象成一棟寫字樓普通任務(wù)就是樓里的普通上班族按電梯排隊(duì)上樓。Xenomai 做的是在這棟樓里加了一條 VIP 專用電梯通道所有外部中斷先經(jīng)過這條通道的保安亭實(shí)時(shí)任務(wù)要處理的中斷直接走 VIP 電梯剩下 Linux 不關(guān)心的中斷才放行給普通電梯。這個(gè)保安亭就是 I-pipe/IRQ pipeline實(shí)時(shí)任務(wù)的運(yùn)行環(huán)境就是 Cobalt 核。所以 Xenomai 的實(shí)時(shí)任務(wù)和 Linux 任務(wù)并不是兩個(gè)操作系統(tǒng)各跑各的而是共享同一個(gè) CPU 資源池、由中斷優(yōu)先級(jí)強(qiáng)行分出了先后。Cobalt 域的實(shí)時(shí)線程永遠(yuǎn)先于 Linux 域的進(jìn)程調(diào)度Linux 域密集計(jì)算再厲害也干擾不到實(shí)時(shí)任務(wù)的關(guān)鍵路徑。這個(gè)設(shè)計(jì)帶來的代價(jià)是要打內(nèi)核補(bǔ)丁而且補(bǔ)丁版本必須和內(nèi)核版本嚴(yán)格對(duì)齊這正是本文后半部分大量 BUG 的源頭。1.2 適合誰(shuí)、不適合誰(shuí)這套方案適合交互頻繁、對(duì)確定性要求苛刻的場(chǎng)景EtherCAT 主站、CNC 插補(bǔ)、多軸協(xié)調(diào)運(yùn)動(dòng)控制、高速數(shù)據(jù)采集、以及需要和機(jī)器視覺聯(lián)動(dòng)的硬實(shí)時(shí)環(huán)節(jié)。這些場(chǎng)景的共同特點(diǎn)是遲到一個(gè)周期就出事而不是平均速度夠快就行。不適合的場(chǎng)景也明確純算力密集任務(wù)、圖形界面為主的產(chǎn)品、或者團(tuán)隊(duì)里沒人愿意維護(hù)一個(gè)打了私有補(bǔ)丁的內(nèi)核。Xenomai 這套東西一旦跑起來你就得一直維護(hù)這個(gè)打了 ipipe 補(bǔ)丁的內(nèi)核升級(jí)內(nèi)核等于重新做一遍全套測(cè)試這個(gè)維護(hù)成本在立項(xiàng)前就必須想清楚。2. 版本矩陣3.0.5/3.0.9 與 4.9.38/4.9.90 怎么對(duì)上號(hào)2.1 版本搭配總表先說這次涉及的組合實(shí)際用過兩套配置一套是量產(chǎn)設(shè)備的基礎(chǔ)版本一套是后來升級(jí)修訂的版本。Xenomai 版本Linux 內(nèi)核ipipe 補(bǔ)丁示例說明3.0.54.9.38ipipe-core-4.9.38-x86-3.patch早期量產(chǎn)組合穩(wěn)定但有一些 SMP 下的延遲抖動(dòng)3.0.94.9.90ipipe-core-4.9.90-x86-2.patch升級(jí)后的組合高負(fù)載下調(diào)度更穩(wěn)3.0.94.9.90ipipe-core-4.9.90-arm-*.patchARM 平臺(tái)對(duì)應(yīng)版本注意架構(gòu)不能混用上面補(bǔ)丁文件名末尾的修訂號(hào)以官網(wǎng)實(shí)際發(fā)布為準(zhǔn)不同時(shí)期會(huì)有 -2、-3、-4 這類后綴差別但命名規(guī)則就是這樣ipipe-core-內(nèi)核版本-架構(gòu)-修訂號(hào)。這里最要命的一條規(guī)則是ipipe 補(bǔ)丁必須精確匹配內(nèi)核的小版本號(hào)。4.9.38 的補(bǔ)丁打不到 4.9.90 上4.9.90 的補(bǔ)丁也打不到 4.9.38 上一個(gè)點(diǎn)版本不對(duì)就是一片 hunk failed。因?yàn)?ipipe 補(bǔ)丁會(huì)改動(dòng)內(nèi)核的kernel/irq/、arch/x86/kernel/這類核心目錄上下文對(duì)不上直接拒絕。2.2 3.0.5 到 3.0.9升級(jí)到底賺了什么兩個(gè) Xenomai 版本的用戶態(tài) API 基本兼容所以從 3.0.5 升到 3.0.9 的成本不高但收益是實(shí)打?qū)嵉摹?.0.9 修了一批 Cobalt 核在 SMP 環(huán)境下的中斷親和性問題在我這邊的現(xiàn)象是原來 3.0.5 在高負(fù)載下偶爾冒出來的 50us 左右的抖動(dòng)升級(jí)之后基本消失。另外 3.0.9 對(duì) 4.9 系列較新的點(diǎn)版本支持更完整所以如果內(nèi)核要升到 4.9.90Xenomai 這邊也建議同步升到 3.0.9。內(nèi)核為什么選 4.9 LTS 系列也很簡(jiǎn)單4.9 是長(zhǎng)期維護(hù)版本ipipe 補(bǔ)丁覆蓋全工控行業(yè)當(dāng)時(shí)的主流物料就是它在支撐。4.9.38 和 4.9.90 都屬于這個(gè)分支的不同維護(hù)點(diǎn)后者修掉了很多前者時(shí)期的驅(qū)動(dòng)和調(diào)度問題。如果項(xiàng)目從零開始直接上 3.0.9 4.9.90 就好別再用 3.0.5 折騰自己。3. 關(guān)鍵配置實(shí)錄打上 ipipe 補(bǔ)丁本身就有一堆坑3.1 補(bǔ)丁獲取與打補(bǔ)丁ipipe 補(bǔ)丁可以從 Xenomai 官網(wǎng)的 ipipe 下載目錄拿也可以用git拉取對(duì)應(yīng)倉(cāng)庫(kù)。下載時(shí)先確認(rèn)兩件事架構(gòu)對(duì)不對(duì)、內(nèi)核點(diǎn)版本對(duì)不對(duì)。x86_64 和 ARM 的補(bǔ)丁文件不是同一個(gè)在 x86 機(jī)器上下載 ARM 補(bǔ)丁打上去大概率直接報(bào)錯(cuò)。拿到補(bǔ)丁后的標(biāo)準(zhǔn)操作如下# 假設(shè)已經(jīng)下載好 linux-4.9.90.tar.xz 和 ipipe-core-4.9.90-x86-2.patch tar xf linux-4.9.90.tar.xz cd linux-4.9.90 patch -p1 ../ipipe-core-4.9.90-x86-2.patch打完補(bǔ)丁馬上檢查有沒有.rej文件殘留有就說明部分補(bǔ)丁沒打進(jìn)去find . -name *.rej -o -name *.orig如果這條命令有輸出千萬(wàn)不要繼續(xù)往下編譯先把補(bǔ)丁問題解決再說。常見情況是下載到了網(wǎng)頁(yè) HTML 而不是原始文件file命令看一眼就能識(shí)別。3.2 內(nèi)核配置的關(guān)鍵項(xiàng)打完補(bǔ)丁之后內(nèi)核配置菜單里會(huì)多出一個(gè) IRQ pipeline 相關(guān)菜單。用make menuconfig打開搜索IPIPE能看到對(duì)應(yīng)選項(xiàng)。我這邊實(shí)際用下來的配置項(xiàng)推薦如下配置項(xiàng)推薦值原因CONFIG_IPIPEy打開中斷流水線這是 Xenomai 運(yùn)行的前提CONFIG_HZ_1000y時(shí)鐘頻率提到 1000HzLinux 域響應(yīng)更及時(shí)CONFIG_PREEMPTyLinux 域任務(wù)搶占粒度更細(xì)減少對(duì)實(shí)時(shí)的干擾CONFIG_CPU_FREQn關(guān)閉 CPU 調(diào)頻避免頻率切換造成延遲抖動(dòng)CONFIG_CPU_IDLEn關(guān)閉 CPU 深度睡眠省得喚醒時(shí)卡頓CONFIG_SUSPEND / HIBERNATIONn關(guān)閉睡眠喚醒路徑CONFIG_IPIPE_DEBUGn調(diào)試版會(huì)拖慢實(shí)時(shí)路徑量產(chǎn)必須關(guān)CONFIG_LOCKDEPn鎖檢測(cè)開銷極大會(huì)讓延遲數(shù)據(jù)完全失真配置的時(shí)候建議從發(fā)行版自帶的 config 出發(fā)而不是make defconfig從頭開始cp /boot/config-$(uname -r) .config make olddefconfig make menuconfig這樣能少踩很多驅(qū)動(dòng)缺失的坑。改完配置后編譯make -j$(nproc) bzImage modules sudo make modules_install sudo cp arch/x86/boot/bzImage /boot/vmlinuz-4.9.90-xenomai裝完內(nèi)核記得更新引導(dǎo)配置然后重啟確認(rèn)內(nèi)核版本里帶-ipipe后綴。如果重啟后uname -r還是普通版本號(hào)先查引導(dǎo)配置有沒有生效。3.3 工具鏈提醒4.9 內(nèi)核配太新的 GCC 容易編譯報(bào)錯(cuò)比如 GCC 11 之后常見各種隱含聲明、頭文件不兼容問題。這不是 Xenomai 的問題是老內(nèi)核和新編譯器的兼容性問題。我這邊用的是 GCC 7/8 附近的工具鏈一次通過。如果你只有新 GCC可以先試試在舊內(nèi)核源碼里補(bǔ)一些兼容性頭文件但最省事的方案還是換工具鏈。4. 從編譯到跑通 latency用戶態(tài)安裝實(shí)操4.1 Xenomai 用戶態(tài)編譯內(nèi)核搞定后接著編譯 Xenomai 用戶態(tài)。下載 xenomai-3.0.9 源碼包后tar xf xenomai-3.0.9.tar.bz2 cd xenomai-3.0.9 ./scripts/bootstrap ./configure --with-corecobalt --enable-smp --prefix/usr/xenomai make -j$(nproc) sudo make installbootstrap這步很多人會(huì)漏掉。直接從官網(wǎng)下載的 release 包一般帶 configure 腳本但如果是從 git 倉(cāng)庫(kù)拉的最新代碼必須先跑 bootstrap 生成 configure。--with-corecobalt是選擇雙內(nèi)核模式這也是 Xenomai 實(shí)時(shí)性最強(qiáng)的工作方式。編譯期間一個(gè)常見問題是缺 autoconf、automake、libtool 這類工具發(fā)行版里裝一下即可。別小看這一步bootstrap 階段報(bào)錯(cuò)基本都是這些基礎(chǔ)工具版本太老或者沒裝全。4.2 環(huán)境變量與模塊加載安裝完成后把環(huán)境變量寫進(jìn)當(dāng)前會(huì)話或用戶的腳本里export XENO_BASE/usr/xenomai export PATH$XENO_BASE/bin:$PATH export XENO_CONFIG$XENO_BASE/bin/xeno-config加載內(nèi)核模塊和啟動(dòng) Xenomai 服務(wù)sudo $XENO_BASE/sbin/start.sh dmesg | grep -i xenomai正常情況下 dmesg 里能看到 Xenomai/Cobalt 相關(guān)初始化信息。如果 start.sh 報(bào)模塊加載失敗直接看下一章的 BUG 記錄這恰恰是最容易卡住的環(huán)節(jié)。4.3 第一次跑 latency用戶態(tài)跑通后第一個(gè)要測(cè)的就是自帶 latency 示例這是判斷環(huán)境是否干凈的黃金標(biāo)準(zhǔn)sudo /usr/xenomai/demos/latency -T 60 -p 1000 -s -i-p 1000表示測(cè)試周期 1000us-T 60表示跑 60 秒-s是同步模式更接近真實(shí)控制循環(huán)的觸發(fā)方式-i會(huì)在測(cè)試結(jié)束時(shí)輸出更多統(tǒng)計(jì)信息。運(yùn)行后屏幕會(huì)滾動(dòng)類似這樣的數(shù)據(jù)RTT| 00:00:10 (periodic, 1000 us, 10000 samples, avg 2 us, max 9 us)判斷標(biāo)準(zhǔn)很簡(jiǎn)單x86_64 平臺(tái)上 max 值應(yīng)該在 10us 以內(nèi)大部分時(shí)候是個(gè)位數(shù)ARM 平臺(tái)看主頻和中斷控制器質(zhì)量但 max 原則上也不該超過 50us。如果你的 max 數(shù)值在這個(gè)量級(jí)以上直接跳到后面的排查章節(jié)你的環(huán)境肯定有地方?jīng)]配置干凈。5. BUG 記實(shí)上補(bǔ)丁打不上、模塊加載失敗、啟動(dòng)即卡死5.1 現(xiàn)象patch 報(bào) hunk FAILED這是新手必踩的第一個(gè)坑我也沒躲過去?,F(xiàn)象是執(zhí)行patch -p1后屏幕一堆Hunk #1 FAILED at 1042這類輸出或者干脆一路 reject。排查鏈如下第一步file查看補(bǔ)丁文件類型如果是 HTML document 說明下載到了網(wǎng)頁(yè)而不是原始文件重新下載 raw 版本。第二步核對(duì)架構(gòu)。x86 的補(bǔ)丁里會(huì)看到 arch/x86 的改動(dòng)ARM 補(bǔ)丁則涉及 arch/arm看 20 行就能區(qū)分。第三步核對(duì)內(nèi)核點(diǎn)版本。tar xf出來的目錄名必須和補(bǔ)丁文件名里的版本完全一致比如 linux-4.9.90 的源碼樹就只能打 ipipe-core-4.9.90 的補(bǔ)丁。這個(gè)問題的根因就一個(gè)版本或來源不對(duì)。補(bǔ)丁不是通用的它是針對(duì)特定內(nèi)核版本生成的上下文差異。所以別去第三方博客下載補(bǔ)丁一定要從官方渠道拿并且和內(nèi)核版本逐字符核對(duì)。5.2 現(xiàn)象模塊 version magic 對(duì)不上內(nèi)核和用戶態(tài)都編譯完了跑 start.sh 加載模塊時(shí)報(bào)錯(cuò)大意是內(nèi)核模塊的 version magic 不匹配說當(dāng)前內(nèi)核是4.9.90-ipipe但模塊期望的是4.9.90或者反過來。根因在 ipipe 補(bǔ)丁會(huì)給內(nèi)核版本自動(dòng)追加-ipipe后綴。外部模塊必須用打過補(bǔ)丁的內(nèi)核源碼樹編譯它的 version magic 才會(huì)帶上-ipipe。最常見的錯(cuò)誤是/lib/modules/$(uname -r)/build這個(gè)軟鏈接指向了沒打補(bǔ)丁的干凈內(nèi)核源碼導(dǎo)致外部模塊編譯時(shí)用了錯(cuò)誤的頭文件。處理辦法是重新確認(rèn)軟鏈接指向打過補(bǔ)丁的那棵源碼樹然后重新make modules_install再重新編譯安裝 Xenomai 用戶態(tài)。這一步如果不重做模塊加載的報(bào)錯(cuò)會(huì)反復(fù)出現(xiàn)。5.3 現(xiàn)象啟動(dòng)到 I-pipe 附近卡死另一個(gè)讓人頭皮發(fā)麻的問題是內(nèi)核引導(dǎo)到剛初始化中斷流水線的地方就沒了輸出反復(fù)重啟都一樣。如果手頭是 x86_64 工控機(jī)常見原因是串口 console 驅(qū)動(dòng)與其他驅(qū)動(dòng)在中斷接管上沖突或者配置里開了某些調(diào)試選項(xiàng)導(dǎo)致早期初始化路徑變得極慢。我當(dāng)時(shí)的排查過程是先在引導(dǎo)參數(shù)里加earlyprintk把啟動(dòng)輸出提前打出來確認(rèn)卡死的位置再把可能搶占早期中斷的設(shè)備驅(qū)動(dòng)從內(nèi)核里摘掉比如串口 DMA、某些 GPIO 控制器最終定位到是設(shè)備樹里一個(gè)中斷屬性與 ipipe 補(bǔ)丁的預(yù)期不一致。這個(gè)問題的通用排查路徑是先確認(rèn)卡死函數(shù)再逐個(gè)禁用可疑驅(qū)動(dòng)最后對(duì)比設(shè)備樹中斷配置。ARM 平臺(tái)尤其常見x86 平臺(tái)相對(duì)少但排查思路一樣。6. BUG 記實(shí)下延遲毛刺、外設(shè)中斷和用戶態(tài)段錯(cuò)誤6.1 固定周期毛刺是內(nèi)核在做家務(wù)latency 測(cè)試最大的敵人之一是周期性出現(xiàn)的尖峰?,F(xiàn)象非常典型平時(shí) max 只有 5us但每隔固定時(shí)間就冒出來一個(gè) 100us 左右的尖峰像時(shí)鐘一樣準(zhǔn)。這種固定周期的毛刺優(yōu)先懷疑三個(gè)東西NMI watchdog、RCU 回調(diào)、tick 處理。NMI watchdog 會(huì)定期在每個(gè) CPU 上觸發(fā)不可屏蔽中斷這個(gè)中斷在實(shí)時(shí)域看來就是天外來客RCU 的回調(diào)如果落在實(shí)時(shí)任務(wù)所在的核心上也會(huì)周期性制造延遲。處理手段如下# 關(guān)閉 NMI watchdog sysctl -w kernel.nmi_watchdog0 # 引導(dǎo)參數(shù)里把 CPU1 從調(diào)度和 RCU 家務(wù)里摘出來 # isolcpus1 rcu_nocbs1 # 然后實(shí)時(shí)任務(wù)固定綁在 CPU1 上 taskset -c 1 /usr/xenomai/demos/latency -T 60 -p 1000 -s -i當(dāng)時(shí)我加了這幾個(gè)參數(shù)之后那個(gè)如鐘表般準(zhǔn)時(shí)的尖峰就消失了。這個(gè)問題的本質(zhì)是Linux 域的周期性家務(wù)打擾了 Cobalt 域方向比具體參數(shù)更重要。6.2 外設(shè)一跑實(shí)時(shí)就崩中斷親和性另一種高頻場(chǎng)景安靜環(huán)境下 latency 一切正常一開網(wǎng)卡流量max 值從個(gè)位數(shù)直接跳到幾百微秒甚至毫秒級(jí)。我遇到過一跑 iperf 實(shí)時(shí)就報(bào)警的項(xiàng)目排查到后面發(fā)現(xiàn)是網(wǎng)卡中斷和實(shí)時(shí)任務(wù)擠在了同一個(gè) CPU 核心上。x86 平臺(tái)的irqbalance會(huì)動(dòng)態(tài)輪轉(zhuǎn)中斷到各核心但它的調(diào)度邏輯不會(huì)考慮你的實(shí)時(shí)任務(wù)綁在哪。解決辦法是把實(shí)時(shí)任務(wù)和中斷分別固定在兩個(gè)核上# 實(shí)時(shí)任務(wù)綁 CPU1 taskset -c 1 /usr/xenomai/demos/latency -T 60 -p 1000 -s -i # 把網(wǎng)卡中斷固定到 CPU0 # 先 /proc/interrupts 找到網(wǎng)卡的 IRQ 號(hào)假設(shè)是 16 echo 1 /proc/irq/16/smp_affinityUSB 控制器也有同樣的問題插拔 U 盤、移動(dòng)鼠標(biāo)時(shí)如果延遲出現(xiàn)毛刺把對(duì)應(yīng) IRQ 挪走即可。這個(gè)排查方向?qū)θ魏瓮庠O(shè)一動(dòng)作實(shí)時(shí)就崩的問題都適用先看/proc/interrupts確認(rèn)中斷落在哪個(gè)核上再調(diào)整親和性。6.3 十幾毫秒巨坑查電源管理最陰間的 BUG 往往在電源管理這里。現(xiàn)象是測(cè) 30 秒沒事跑 10 分鐘突然冒出一個(gè) 10ms 級(jí)別的巨坑而且間隔非常不規(guī)則。如果你先入為主地去查中斷大概率一無所獲因?yàn)閱栴}根本不在中斷而在CPU 頻率和 C-state 深度睡眠。x86 平臺(tái)默認(rèn)的 CPU 調(diào)頻策略是 powersave負(fù)載降下來就會(huì)自動(dòng)降頻實(shí)時(shí)任務(wù)被喚醒時(shí) CPU 正處在低頻或者深睡眠狀態(tài)喚醒延遲直接拉滿。處理方式分三層第一層BIOS 里關(guān)閉 C3/C6 等深度休眠狀態(tài)。第二層內(nèi)核配置里關(guān)掉 CONFIG_CPU_FREQ 和 CONFIG_CPU_IDLE。第三層運(yùn)行時(shí)強(qiáng)制 performance governorcpupower frequency-set -g performance我在這上面耗時(shí)最久一度以為是網(wǎng)卡驅(qū)動(dòng)問題。后來把電源管理全部關(guān)掉所有不規(guī)則大坑全部消失。x86 平臺(tái)做實(shí)時(shí)先關(guān)電源管理再談別的這句話我現(xiàn)在逢人就講。6.4 用戶態(tài)一碰實(shí)時(shí) API 就段錯(cuò)誤還有一個(gè)用戶態(tài)的高頻問題用 xeno-config 編譯出來的程序一調(diào)用rt_task_create就段錯(cuò)誤普通 Linux 程序完全正常。這個(gè)現(xiàn)象的根因幾乎都在編譯環(huán)境或權(quán)限。編譯環(huán)境問題xeno-config --cflags給出的編譯參數(shù)必須完整帶進(jìn)你的構(gòu)建過程。很多人只抄了-I/usr/xenomai/include漏了-D_GNU_SOURCE這類宏定義結(jié)果頭文件里的聲明和庫(kù)實(shí)現(xiàn)對(duì)不上運(yùn)行直接崩。正確的做法是用命令替換export XENO_USER_CFLAGS$(/usr/xenomai/bin/xeno-config --cflags) export XENO_USER_LDFLAGS$(/usr/xenomai/bin/xeno-config --ldflags) # Makefile 里通過 $(shell ...) 引入不要手抄權(quán)限問題非 root 用戶跑實(shí)時(shí)程序需要把用戶加入 Xenomai 的允許組否則內(nèi)核會(huì)拒絕你創(chuàng)建實(shí)時(shí)任務(wù)。方法是在引導(dǎo)參數(shù)里加xenomai.allowed_group1000或者運(yùn)行時(shí)把組 ID 寫進(jìn)/proc/xenomai/group。這個(gè)問題別看簡(jiǎn)單現(xiàn)場(chǎng)項(xiàng)目里因?yàn)槁┝诉@一步導(dǎo)致程序段錯(cuò)誤、然后懷疑整了半天內(nèi)核配置的案例非常普遍。7. 延遲亂飛的系統(tǒng)化排查思路7.1 先分域Linux 域問題 vs Cobalt 域問題踩過一圈 BUG 之后我最大的體會(huì)是延遲問題排查前先分清是 Linux 域的問題還是 Cobalt 域的問題。實(shí)時(shí)任務(wù)里如果調(diào)用了printf、open、malloc這類 Linux 系統(tǒng)服務(wù)就會(huì)發(fā)生域切換切換本身就有開銷次數(shù)多了延遲自然難看。這不是內(nèi)核的錯(cuò)是代碼寫得不對(duì)。Cobalt 域里做日志應(yīng)該用rt_printk這類實(shí)時(shí)安全接口數(shù)據(jù)交換用預(yù)分配的內(nèi)存池文件系統(tǒng)操作全部放到普通 Linux 線程里做。很多延遲莫名其妙變高的案例最后定位到就是實(shí)時(shí)任務(wù)里多了一行printf。7.2 癥狀-懷疑對(duì)象對(duì)照表現(xiàn)場(chǎng)排查時(shí)我習(xí)慣拿一張表對(duì)著看效率比瞎猜高得多癥狀優(yōu)先懷疑對(duì)象驗(yàn)證手段固定周期尖峰NMI watchdog、RCU、tick關(guān)閉 watchdogisolcpus/rcu_nocbs 隔離外設(shè)一動(dòng)作就崩中斷親和性/proc/interrupts 查核調(diào) smp_affinity不規(guī)則十幾 ms 大坑電源管理、C-state關(guān) CPUFreq、關(guān) C-stateperformance governor用戶態(tài)段錯(cuò)誤編譯參數(shù)、運(yùn)行權(quán)限檢查 xeno-config cflags/ldflags、allowed_group一跑負(fù)載就抖SMP 調(diào)度、高優(yōu)先級(jí)任務(wù)干擾綁核、調(diào)整任務(wù)優(yōu)先級(jí)、隔離核這張表就是前面所有 BUG 的濃縮版。遇到新問題先對(duì)表對(duì)不上再往下挖。7.3 壓測(cè)環(huán)境干凈化清單做延遲測(cè)試前先把環(huán)境搞干凈否則你測(cè)出來的數(shù)據(jù)全是干擾沒法定位。我自己的固定流程如下SSH 登錄而不是本地圖形界面把桌面、合成器全部停掉。關(guān)閉 NMI watchdogsysctl -w kernel.nmi_watchdog0。關(guān)閉電源管理BIOS 層面和內(nèi)核層面同時(shí)處理。臨時(shí)斷開網(wǎng)卡或把網(wǎng)卡中斷固定到非實(shí)時(shí)核。實(shí)時(shí)任務(wù)用taskset綁核并確認(rèn)沒有其他高優(yōu)先級(jí)進(jìn)程落在同一個(gè)核上。每次測(cè)試記錄dmesg、/proc/interrupts和latency -h直方圖存成帶日期的文件備查。這個(gè)流程跑下來如果還有異常那基本可以確定是硬件或者驅(qū)動(dòng)層面的問題而不是配置層面的問題。8. 一點(diǎn)經(jīng)驗(yàn)小結(jié)后來我把新項(xiàng)目切到了更新的內(nèi)核與 Xenomai 組合但每次新設(shè)備出現(xiàn)莫名的延遲上漲還是會(huì)翻出當(dāng)年這套 3.0.5/3.0.9 4.9.38/4.9.90 的排查筆記思路永遠(yuǎn)是同一個(gè)順序先看電源管理、再看中斷親和、再看 RCU。最后分享一個(gè)讓我少走很多彎路的小習(xí)慣——每套內(nèi)核驗(yàn)證穩(wěn)定后把dmesg、/proc/interrupts、latency直方圖連同內(nèi)核的.config一起打包備份命名里帶上日期和內(nèi)核版本。這套證據(jù)鏈在你三個(gè)月后回來處理現(xiàn)場(chǎng)問題時(shí)比任何排查工具都管用。