核與IPC機(jī)制詳解:從實(shí)時(shí)系統(tǒng)原理到嵌入式開發(fā)實(shí)踐)
簡(jiǎn)介面向嵌入式實(shí)時(shí)系統(tǒng)開發(fā)者這套QNX開發(fā)手冊(cè)以中文詳細(xì)講解操作系統(tǒng)的核心機(jī)制與編程方法。內(nèi)容從QNX概述、系統(tǒng)結(jié)構(gòu)入手覆蓋多任務(wù)調(diào)度、優(yōu)先級(jí)搶占、進(jìn)程間消息通信、文件系統(tǒng)操作命令及C/C應(yīng)用程序開發(fā)流程并結(jié)合實(shí)例演示從環(huán)境配置到代碼實(shí)現(xiàn)的全過(guò)程適合剛接觸QNX或需要快速查閱開發(fā)要點(diǎn)的工程師。壓縮包為單個(gè)doc文檔體積2.2MB便于閱讀和檢索。目前已有3072人學(xué)習(xí)下載是入門QNX開發(fā)的高頻參考資料。手冊(cè)特別強(qiáng)調(diào)QNX微內(nèi)核、分布式網(wǎng)絡(luò)及可裁剪配置等特色對(duì)編寫實(shí)時(shí)控制、通信或數(shù)據(jù)采集程序具有直接借鑒價(jià)值。1. 實(shí)時(shí)系統(tǒng)不是跑得快這本 QNX 開發(fā)手冊(cè)的中文價(jià)值在哪前段時(shí)間幫一個(gè)遙控遙測(cè)主站項(xiàng)目做排查現(xiàn)象很典型CPU 占用率不到三成但一個(gè) 500ms 的周期任務(wù)就是不穩(wěn)定偶爾一次調(diào)度能拖到一秒出頭。把核心服務(wù)遷到 QNX 實(shí)時(shí)操作系統(tǒng)之后抖動(dòng)直接消失。QNX 不是冷門玩具它用微內(nèi)核加消息機(jī)制做分布式實(shí)時(shí)系統(tǒng)集成網(wǎng)絡(luò)后整個(gè)內(nèi)核不過(guò) 148KB到今天還大量跑在工業(yè)控制、通信設(shè)備和車載系統(tǒng)里。這份中文開發(fā)手冊(cè)覆蓋了系統(tǒng)結(jié)構(gòu)、進(jìn)程間通信、常用命令和應(yīng)用程序?qū)嵗龔牧慊A(chǔ)到能動(dòng)手改工程的完整路徑都有適合剛從通用操作系統(tǒng)轉(zhuǎn)過(guò)來(lái)的嵌入式開發(fā)也適合想系統(tǒng)補(bǔ)一遍 QNX 概念的老手。它不是源碼包是一份可以直接對(duì)照學(xué)習(xí)的中文底稿。2. 系統(tǒng)結(jié)構(gòu)拆解微內(nèi)核、管理器與五種進(jìn)程間通信手冊(cè)開篇反復(fù)強(qiáng)調(diào)一個(gè)反直覺的點(diǎn)QNX 是“小組型結(jié)構(gòu)”而不是“層次型結(jié)構(gòu)”。我一開始沒當(dāng)回事直到追一個(gè)驅(qū)動(dòng)問(wèn)題才發(fā)現(xiàn)理解這個(gè)結(jié)構(gòu)比背命令重要得多——很多運(yùn)行期詭異現(xiàn)象根源都在于你把它當(dāng)成單體內(nèi)核來(lái)推斷行為。2.1 先看骨架微內(nèi)核加管理器不是單體內(nèi)核QNX 由一個(gè)很小的內(nèi)核和若干負(fù)責(zé)系統(tǒng)管理的共操作進(jìn)程組成。內(nèi)核本身不實(shí)現(xiàn)文件系統(tǒng)也不直接管設(shè)備它只做兩件事任務(wù)調(diào)度和消息路由。文件由 Fsys 文件系統(tǒng)管理器負(fù)責(zé)進(jìn)程創(chuàng)建、信號(hào)、內(nèi)存由 Proc32 進(jìn)程管理器負(fù)責(zé)終端、打印機(jī)這類外圍設(shè)備由 Dev 設(shè)備管理器負(fù)責(zé)。這些管理器是獨(dú)立進(jìn)程它們之間通過(guò)內(nèi)核轉(zhuǎn)發(fā)的消息協(xié)作所以整個(gè)系統(tǒng)看起來(lái)像一群對(duì)等的小組而不是一個(gè)垂直的層級(jí)樹。這個(gè)設(shè)計(jì)直接帶來(lái)兩個(gè)實(shí)時(shí)系統(tǒng)最看重的特性。第一是故障隔離某個(gè)管理器崩了內(nèi)核和其他管理器還能繼續(xù)跑不至于整機(jī)掛掉第二是可裁剪最小配置只要幾十 KB 內(nèi)存手冊(cè)里寫的“集成網(wǎng)絡(luò)后只有 148KB”指的就是這種緊湊狀態(tài)。對(duì)嵌入式場(chǎng)景來(lái)說(shuō)省下來(lái)的內(nèi)存和確定性的響應(yīng)路徑比多幾個(gè)花哨功能實(shí)際得多。2.2 任務(wù)與調(diào)度優(yōu)先級(jí)搶占從來(lái)不是玄學(xué)手冊(cè)把任務(wù)定義為一個(gè)可調(diào)度的執(zhí)行單元由執(zhí)行代碼和該任務(wù)私有的數(shù)據(jù)組成。調(diào)度方式是你可控制的優(yōu)先級(jí)驅(qū)動(dòng)的急者優(yōu)先搶占。同優(yōu)先級(jí)的任務(wù)按時(shí)間片輪巡低優(yōu)先級(jí)任務(wù)必須等高優(yōu)先級(jí)任務(wù)進(jìn)入 I/O 等待或主動(dòng)掛起之后才有機(jī)會(huì)跑最高優(yōu)先級(jí)任務(wù)只要就緒就立刻運(yùn)行。這帶來(lái)一個(gè)常見誤解新手以為“實(shí)時(shí)”就是“快”其實(shí) QNX 的實(shí)時(shí)性來(lái)自確定性而不是絕對(duì)速度。手冊(cè)里那句“如果高優(yōu)先級(jí)任務(wù)一直在運(yùn)行低優(yōu)先級(jí)任務(wù)就無(wú)法運(yùn)行”就是典型的坑后面避坑章節(jié)我專門展開。查看當(dāng)前進(jìn)程優(yōu)先級(jí)用 sin 或 ps 命令輸出里的 PRI 列就是優(yōu)先級(jí)數(shù)字越大優(yōu)先級(jí)越高這點(diǎn)和 Linux 的 nice 值方向相反別記反了。2.3 進(jìn)程間通信消息、代理、信號(hào)、共享內(nèi)存、消息隊(duì)列QNX 的 IPC 是它的立身之本手冊(cè)花了一整節(jié)講五種基本方式每一種適用的場(chǎng)景都不一樣。消息是阻塞式同步通信發(fā)送方把數(shù)據(jù)復(fù)制到接收方地址空間在對(duì)方應(yīng)答之前自己被掛起代理是非阻塞事件通知發(fā)出去就不用等回應(yīng)信號(hào)是 POSIX 兼容的傳統(tǒng)異步手段共享內(nèi)存吞吐最大但必須自己加同步消息隊(duì)列則是緩沖解耦的中間人。實(shí)際開發(fā)里用得最多的是消息。下面這個(gè)例子我用現(xiàn)代的 QNX Neutrino API 演示服務(wù)端接收和應(yīng)答手冊(cè)里老版本的概念完全一致只是函數(shù)名不同/* server.c - 消息接收端 */ #include sys/neutrino.h #include stdio.h #include string.h #define CHID 1 int main(void) { int chid, rcvid; char msg[64]; char reply[16] ack; /* 創(chuàng)建消息通道其他進(jìn)程通過(guò)這個(gè)通道號(hào)連接 */ chid ChannelCreate(0); if (chid -1) { perror(ChannelCreate); return 1; } for (;;) { /* 阻塞等待消息rcvid 是發(fā)送方的連接標(biāo)識(shí) */ rcvid MsgReceive(chid, msg, sizeof(msg), NULL); if (rcvid -1) { perror(MsgReceive); break; } printf(server received: %s\n, msg); /* 必須應(yīng)答否則發(fā)送端會(huì)一直掛在等待狀態(tài) */ MsgReply(rcvid, 0, reply, strlen(reply) 1); } return 0; }ChannelCreate 負(fù)責(zé)創(chuàng)建并返回一個(gè)通道號(hào)這個(gè) chid 相當(dāng)于服務(wù)進(jìn)程的“門牌號(hào)”。MsgReceive 是阻塞調(diào)用沒消息時(shí)進(jìn)程進(jìn)入 RECV 狀態(tài)不耗 CPU。參數(shù)里 rcvid 是核心它標(biāo)識(shí)了這一次消息來(lái)自誰(shuí)后續(xù) MsgReply 必須原樣用它答錯(cuò)了客戶端就永遠(yuǎn)等不到回應(yīng)。這一收一答的同步模型就是 QNX 幾乎所有系統(tǒng)服務(wù)的工作基礎(chǔ)。2.4 文件和網(wǎng)絡(luò)一套透明的資源命名方式QNX 把節(jié)點(diǎn)當(dāng)作獨(dú)立工作站節(jié)點(diǎn)間通過(guò)局域網(wǎng)連接但對(duì)程序員來(lái)說(shuō)整個(gè)網(wǎng)絡(luò)像一臺(tái)虛擬機(jī)所有任務(wù)跑在上面所有文件都可存取。要訪問(wèn)別的節(jié)點(diǎn)的資源在資源名前加節(jié)點(diǎn)號(hào)就行例如 /dev/con1 是本地控制臺(tái)//2/dev/con1 就是 2 號(hào)節(jié)點(diǎn)的控制臺(tái)。手冊(cè)提到 2.15 版用令牌環(huán)協(xié)議支持 255 個(gè)節(jié)點(diǎn)4.x 版改成 IEEE802.3 以太網(wǎng)協(xié)議節(jié)點(diǎn)更多性能也更好。文件系統(tǒng)由 Fsys 統(tǒng)一管理至少六種文件類型除了常規(guī)文件和目錄還有管道、FIFO、符號(hào)鏈、塊特殊文件、字符特殊文件。設(shè)備統(tǒng)一掛在 dev 目錄下靠 I/O 名字空間來(lái)定位這樣應(yīng)用程序可以不用關(guān)心硬件細(xì)節(jié)像讀寫普通文件一樣操作設(shè)備。這個(gè)設(shè)計(jì)到今天依然是 QNX 資源管理框架的雛形。3. 常用命令實(shí)戰(zhàn)從 pwd 到 chmod 把 QNX 命令行串起來(lái)QNX 的命令很多長(zhǎng)得像 UNIX但細(xì)節(jié)不通用。比如 who am i、sin、use 這幾個(gè)在 Linux 里就沒有對(duì)應(yīng)物。這一章按實(shí)際工作流來(lái)組織先定位自己在哪、系統(tǒng)里有什么再找文件、處理文本、改權(quán)限照著走一遍就能在目標(biāo)機(jī)上干活了。3.1 系統(tǒng)狀態(tài)與用戶信息pwd、who、finger、date# 顯示當(dāng)前工作目錄 pwd # 查看當(dāng)前登錄用戶 who am i # 查看所有登錄用戶及詳細(xì)信息 finger # 定制顯示日期時(shí)間 date DATE: %m/%d/%y%nTIME: %H:%M:%Spwd 是 print working directory 的縮寫輸出以 / 開頭的絕對(duì)路徑告訴你當(dāng)前在文件系統(tǒng)的哪個(gè)位置。who am i 這個(gè)寫法比較老派輸出包含登錄名、終端線路和登錄時(shí)間注意終端如果是 //1/dev/ttyp0 這種帶節(jié)點(diǎn)號(hào)的說(shuō)明你是遠(yuǎn)程進(jìn)來(lái)的。finger 后面跟用戶名可以查某個(gè)用戶的詳細(xì)信息不管他在不在線。date 加單引號(hào)格式化用在腳本里非常方便%m 是月、%d 是日、%y 是兩位年份%n 是換行系統(tǒng)管理員也能用它直接設(shè)置系統(tǒng)時(shí)間。3.2 文件定位與查看ls、find、more、cat# 區(qū)分目錄、文件、可執(zhí)行程序 ls -F # 遞歸列出所有子目錄的文件 ls -R # 在指定目錄下按名字找文件 find /rtu -name qrtu.c # 分屏瀏覽文本 more /rtu/config/comn1.sys # 顯示文件內(nèi)容也可以重定向生成新文件 cat /rtu/config/comn1.sys cat qcdt.sys channel1.sysls -F 的輸出里名字后面帶 / 的是目錄帶 * 的是可執(zhí)行文件沒后綴的是普通文件這個(gè)一眼定位在排查目錄結(jié)構(gòu)時(shí)很省事。find 的格式是“find 路徑列表 表達(dá)式”-name 后面跟目標(biāo)文件名手冊(cè)里的實(shí)例是 find /rtu -name qrtu.c一下子把整個(gè)工程的源文件位置撈出來(lái)。more 每顯示一屏就暫??崭穹乱豁?yè)/ 可以在文件里搜字符串。cat 除了看文件還能直接做文件復(fù)制cat qcdt.sys channel1.sys 等價(jià)于 cp但順便能把多個(gè)文件拼接成一個(gè)。3.3 文本處理的四件套diff、grep、sort、wc# 對(duì)比兩個(gè)配置文件的差異 diff comn1.sys comn2.sys # 忽略大小寫搜索字符串 grep -i protocol channel1.sys channel2.sys # 按規(guī)則排序文件行 sort config.list # 統(tǒng)計(jì)行數(shù)、詞數(shù)、字符數(shù) wc -h dbms.cfg ser.cfgdiff 的輸出格式值得讀一下6,7c6,7 表示 comn1.sys 的第 6、7 行要改成 comn2.sys 的第 6、7 行然后下面用 和 分別標(biāo)出兩個(gè)文件的內(nèi)容。grep 支持正則表達(dá)式-i 忽略大小寫多文件搜索時(shí)輸出會(huì)帶文件名前綴比如 channel1.sys: Protocol: U4F。sort 的排序規(guī)則有點(diǎn)講究數(shù)字開頭的行排在前面大寫字母開頭的行排在小寫前面符號(hào)按 ASCII 碼排。wc 的 -h 參數(shù)會(huì)打印表頭多文件時(shí)最后一行 Total 給出總計(jì)檢查配置文件有沒有漏行時(shí)這個(gè)命令很實(shí)用。3.4 文件與目錄管理cp、mv、rm、cd、mkdir、rmdir# 拷貝文件 cp qcdt.sys /rtu/config/channel1.sys # 移動(dòng)并改名 mv /rtu/config/qcdt.sys /rtu1/config/channel1.sys # 遞歸刪除目錄不可恢復(fù) rm -r /rtu/old_bin # 遞歸建立目錄 mkdir -p /rtu/config # 刪除空目錄 rmdir /rtu/empty_dircp 只作用于文件不能復(fù)制目錄要復(fù)制目錄得配合 -r 選項(xiàng)。mv 既能改名也能搬目錄手冊(cè)里特意演示了“移動(dòng)并改名”一步到位的寫法。rm 這條要格外小心QNX 不保留被刪文件的副本rm 之后沒有后悔藥符號(hào)鏈接被刪時(shí)只刪鏈接本身指向的文件還在。rmdir 只能刪空目錄目錄里有內(nèi)容會(huì)報(bào)“Directory not empty”必須先清空內(nèi)部。mkdir -p 是腳本里最常用的參數(shù)父目錄不存在時(shí)自動(dòng)逐級(jí)創(chuàng)建寫部署腳本時(shí)能省掉一堆判斷。3.5 權(quán)限管理chmod 的符號(hào)法和八進(jìn)制法# 符號(hào)模式屬主加執(zhí)行權(quán)限 chmod ux mkrtu # 八進(jìn)制模式所有用戶讀/寫/執(zhí)行 chmod 777 mkrtuchmod 的符號(hào)模式里u 是屬主、g 是同組、o 是其他用戶、a 是全部 表示授予- 表示拒絕。注意一個(gè)坑who 和 之間不能有空格手冊(cè)原話是“不要在 who 和 、- 和 permission 之間輸入空格”寫成 chmod u x 會(huì)被當(dāng)成兩個(gè)參數(shù)解析命令根本執(zhí)行不了。八進(jìn)制模式更直接三個(gè)數(shù)字分別對(duì)應(yīng)屬主、同組、其他用戶的權(quán)限數(shù)字是 rwx 三種權(quán)限的累加值數(shù)字權(quán)限含義4r--只讀5r-x讀和執(zhí)行6rw-讀和寫7rwx讀、寫、執(zhí)行排查權(quán)限問(wèn)題先 ls -l 看當(dāng)前狀態(tài)九個(gè)字符按三三一組第一組屬主、第二組同組、第三組其他人r、w、x 各自對(duì)位用 - 表示拒絕。我一般習(xí)慣用八進(jìn)制模式因?yàn)樗粫?huì)出現(xiàn)空格解析問(wèn)題也更容易寫進(jìn)部署腳本。4. 應(yīng)用程序開發(fā)從環(huán)境搭建到一個(gè)完整消息通信實(shí)例前面把系統(tǒng)和命令都過(guò)了一遍這一章終于落到代碼上。手冊(cè)第三章講開發(fā)過(guò)程和實(shí)例我這里把它擴(kuò)展成一個(gè)可以直接上手跑的最小工程先搭環(huán)境再寫一個(gè)父子進(jìn)程例子最后把上一章的 server/client 消息通信完整跑通。4.1 開發(fā)環(huán)境目標(biāo)機(jī)與交叉編譯QNX 應(yīng)用程序開發(fā)的基本套路是在一臺(tái)開發(fā)機(jī)上寫代碼和交叉編譯生成的可執(zhí)行文件傳到目標(biāo)機(jī)運(yùn)行。開發(fā)環(huán)境由編輯器、C/C 編譯器、鏈接器、make 和頭文件庫(kù)組成。老版本 QNX 常見的是 Watcom C 編譯器新版本基本都切到 GCC用 C 還是 C 取決于業(yè)務(wù)實(shí)時(shí)控制這部分用純 C 更直接因?yàn)槔瞎こ汤锎罅?POSIX 風(fēng)格的接口用起來(lái)沒有 C 的額外開銷。部署空間這塊要心里有數(shù)手冊(cè)強(qiáng)調(diào)“最小配置只占用幾十 KB 內(nèi)存”這種緊湊性是靠裁剪管理器實(shí)現(xiàn)的。你在開發(fā)機(jī)上可能跑著幾十個(gè)進(jìn)程到目標(biāo)板可能只放內(nèi)核、Proc32、Fsys、Dev 和你自己寫的兩三個(gè)應(yīng)用進(jìn)程。寫代碼時(shí)不要依賴那些開發(fā)機(jī)上的便利工具目標(biāo)板上的運(yùn)行環(huán)境可能連 shell 腳本解釋器都沒有。4.2 進(jìn)程創(chuàng)建實(shí)例父進(jìn)程 spawn 出子進(jìn)程QNX 支持的任務(wù)創(chuàng)建方式是 spawn 系列調(diào)用比傳統(tǒng)的先 fork 再 exec 更輕量一步就能指定新程序路徑、參數(shù)和環(huán)境/* spawn_demo.c - 先創(chuàng)建子進(jìn)程再等它退出 */ #include stdio.h #include process.h #include sys/wait.h int main(void) { int pid; /* 參數(shù)依次是程序路徑、文件描述符繼承、環(huán)境變量、命令行名、參數(shù)表 */ pid spawn(child_demo, NULL, NULL, child_demo, NULL); if (pid -1) { perror(spawn); return 1; } printf(parent: child pid%d, waiting...\n, pid); waitpid(pid, NULL, 0); printf(parent: child done.\n); return 0; }/* child_demo.c */ #include stdio.h #include process.h int main(void) { printf(child: my pid is %d\n, getpid()); return 0; }spawn 的第一個(gè)參數(shù)是要執(zhí)行的程序路徑第二和第三個(gè)參數(shù)控制文件描述符繼承和環(huán)境傳遞這里傳 NULL 表示用默認(rèn)值第四個(gè)參數(shù)是命令行里的程序名最后一個(gè)參數(shù)是參數(shù)數(shù)組。編譯時(shí)兩個(gè)文件分別編spawn 的執(zhí)行路徑要能找到 child_demo 這個(gè)可執(zhí)行文件如果不在同一目錄就寫全路徑。waitpid 讓父進(jìn)程阻塞直到子進(jìn)程退出這是父子同步最基本的做法。cc -o child_demo child_demo.c cc -o spawn_demo spawn_demo.c ./spawn_demo4.3 消息通信完整實(shí)例Client/Server 跑通 IPC消息通信是 QNX 應(yīng)用的核心骨架我直接復(fù)用 2.3 的 server.c再補(bǔ)一個(gè) client.c/* client.c - 消息發(fā)送端 */ #include sys/neutrino.h #include stdio.h #include string.h int main(int argc, char *argv[]) { int coid, chid 1; /* 通道號(hào)與服務(wù)端約定為 1 */ int server_pid; char reply[16]; if (argc 2) { fprintf(stderr, usage: client server_pid\n); return 1; } server_pid atoi(argv[1]); /* 通過(guò) 節(jié)點(diǎn)號(hào)進(jìn)程PID通道號(hào) 定位服務(wù)端 */ coid ConnectAttach(0, server_pid, chid, 0, 0); if (coid -1) { perror(ConnectAttach); return 1; } /* 同步發(fā)送發(fā)送后阻塞等待服務(wù)端應(yīng)答 */ if (MsgSend(coid, hello, 6, reply, sizeof(reply)) -1) { perror(MsgSend); return 1; } printf(server reply: %s\n, reply); return 0; }運(yùn)行順序有講究先啟服務(wù)端再啟客戶端# 終端 1啟動(dòng)服務(wù)端后臺(tái)運(yùn)行 ./server # 終端 2用 sin 查到 server 的 PID sin # 終端 2把 PID 傳給客戶端 ./client 11234ConnectAttach 的四個(gè)參數(shù)分別表示節(jié)點(diǎn)描述符、目標(biāo)進(jìn)程 PID、目標(biāo)進(jìn)程的通道號(hào)、標(biāo)志位。這里第一個(gè) 0 指本地節(jié)點(diǎn)跨節(jié)點(diǎn)通信時(shí)改成對(duì)應(yīng)節(jié)點(diǎn)號(hào)就行。MsgSend 把 6 個(gè)字節(jié)的 “hello” 發(fā)給服務(wù)端然后阻塞等待應(yīng)答服務(wù)端 MsgReply 之后才繼續(xù)往下走。整個(gè)調(diào)用鏈體現(xiàn)的就是手冊(cè)里那句話消息從第一個(gè)任務(wù)的地址空間復(fù)制到第二個(gè)任務(wù)地址空間時(shí)發(fā)送方被掛起直到收到應(yīng)答。這個(gè)同步模型保證了消息不會(huì)丟代價(jià)是收發(fā)雙方必須都在線進(jìn)程退出或通道關(guān)閉都會(huì)導(dǎo)致錯(cuò)誤返回。4.4 調(diào)試與狀態(tài)檢查進(jìn)程卡死的時(shí)候最先要看的是 sin 或 ps 輸出的 STATE 列。常見狀態(tài)有 READY就緒在跑、WAIT等待某種事件、RECV阻塞等消息、REPLY發(fā)送方在等服務(wù)端應(yīng)答。如果客戶端一直處于 REPLY、服務(wù)端一直處于 RECV那多半是消息路徑上有人沒應(yīng)答或者雙方在互相等待。手冊(cè)里的 use 命令可以查看某個(gè)命令的用法use 本身也是 QNX 一個(gè)特色所有命令的文檔都能通過(guò) use 命令在 shell 里直接調(diào)出來(lái)不用翻紙質(zhì)手冊(cè)。5. 避坑實(shí)錄QNX 開發(fā)中五個(gè)高頻故障的排查路徑QNX 的坑主要不在語(yǔ)法而在系統(tǒng)的底層假設(shè)。下面五個(gè)場(chǎng)景都是手冊(cè)里埋了伏筆、我在實(shí)際調(diào)試中真踩過(guò)的每一條按“現(xiàn)象、原因、解決”拆開講。5.1 rm 之后沒有后悔藥現(xiàn)象誤刪了一個(gè)配置文件或二進(jìn)制翻遍系統(tǒng)也找不回來(lái)所有備份手段都沒有啟用。原因QNX 不像桌面系統(tǒng)有回收站rm 直接刪目錄項(xiàng)文件占用的數(shù)據(jù)塊被標(biāo)記為釋放命令執(zhí)行完就回天乏術(shù)。解決刪文件之前先 ls 確認(rèn)路徑關(guān)鍵目錄定期用 tar 打包存到另一節(jié)點(diǎn)腳本里刪除操作統(tǒng)一加 -i 交互確認(rèn)。從那以后我凡是批量刪除的場(chǎng)合第一反應(yīng)都是先把目錄 tar 一份再動(dòng)手血淚經(jīng)驗(yàn)。5.2 chmod 符號(hào)模式中間打了空格現(xiàn)象執(zhí)行 chmod 沒報(bào)錯(cuò)但權(quán)限根本沒變或者命令被拆成兩個(gè)參數(shù)解析。原因手冊(cè)明確寫了 who 和 、- 和 permission 之間不能有空格寫成 chmod u x file 時(shí) shell 會(huì)把 u、、x 當(dāng)成獨(dú)立參數(shù)。解決符號(hào)模式寫成 chmod ux file 這樣緊湊格式批量處理或者寫腳本時(shí)直接用八進(jìn)制模式 chmod 755 file少一個(gè)坑。5.3 共享內(nèi)存不同步丟數(shù)據(jù)現(xiàn)象兩個(gè)任務(wù)同時(shí)修改一份共享記錄結(jié)果其中一次修改沒了數(shù)據(jù)對(duì)不上。原因共享內(nèi)存是五種 IPC 里吞吐最大的但本質(zhì)是裸內(nèi)存訪問(wèn)手冊(cè)原話是“若不采取同步措施往往也容易出錯(cuò)”兩個(gè)任務(wù)并發(fā)寫同一個(gè)位置必然互相覆蓋。解決寫入前用信號(hào)量加鎖多讀單寫場(chǎng)景配合原子操作或者干脆改用消息通信讓數(shù)據(jù)的歸屬權(quán)清晰。這里不是共享內(nèi)存不行是不加保護(hù)就裸奔不行。5.4 高優(yōu)先級(jí)進(jìn)程忙等低優(yōu)先級(jí)餓死現(xiàn)象系統(tǒng)沒死機(jī)但某個(gè)低優(yōu)先級(jí)任務(wù)永遠(yuǎn)得不到執(zhí)行日志里一直沒它的輸出。原因最高優(yōu)先級(jí)任務(wù)只要就緒就一直占據(jù) CPU低優(yōu)先級(jí)任務(wù)根本沒有調(diào)度機(jī)會(huì)這是優(yōu)先級(jí)搶占的必然結(jié)果。解決高優(yōu)先級(jí)任務(wù)里不要用 while(1) 自旋忙等該阻塞的調(diào)用比如 MsgReceive就用阻塞調(diào)用把 CPU 讓出去任務(wù)優(yōu)先級(jí)不要全部拉滿按周期和重要程度分級(jí)。5.5 消息通信兩端互相等成死鎖現(xiàn)象客戶端卡在 REPLY 狀態(tài)服務(wù)端卡在 RECV 狀態(tài)兩邊都不動(dòng)請(qǐng)求超時(shí)。原因服務(wù)端在處理完前一條消息之前又阻塞接收新消息而客戶端等的是舊請(qǐng)求的應(yīng)答形成循環(huán)等待。解決用 sin 查 STATEREPLY 說(shuō)明在等應(yīng)答RECV 說(shuō)明在等新消息一眼定位是誰(shuí)沒有履行應(yīng)答義務(wù)協(xié)議上約定“收到必須答”處理不過(guò)來(lái)的場(chǎng)景用代理或者異步消息而不是讓服務(wù)端無(wú)限阻塞。6. 二次開發(fā)思路把手冊(cè)案例映射成自己的板級(jí)工程真正要在這個(gè)手冊(cè)基礎(chǔ)上做二次開發(fā)第一步不是抄代碼而是把老 API 映射到現(xiàn)代 QNX Neutrino 或者你目標(biāo)板上實(shí)際支持的接口。我習(xí)慣先建一張映射表把手冊(cè)里的概念轉(zhuǎn)成當(dāng)前工程能直接用的調(diào)用手冊(cè)里的概念 / API現(xiàn)代 QNX Neutrino 對(duì)應(yīng)備注內(nèi)部任務(wù)消息MsgSend / MsgReceive / MsgReply同步阻塞核心 IPC代理MsgSendPulse非阻塞事件通知共享內(nèi)存shm_open mmap 信號(hào)量必須自己加鎖消息隊(duì)列POSIX mq_open / mq_send緩沖解耦Fsys / Dev 管理器資源管理器框架resmgr 實(shí)現(xiàn)設(shè)備服務(wù)映射做完之后改造路徑我一般分三步走。第一步在 QEMU 或虛擬機(jī)上把手冊(cè)里的命令全部過(guò)一遍pwd、find、chmod、sin 這些操作形成肌肉記憶第二步把手冊(cè)里的 news 實(shí)例代碼按映射表重寫成目標(biāo)板 API 版本能編譯能跑通第三步才是接手業(yè)務(wù)代碼把里面的 IPC 調(diào)用點(diǎn)逐個(gè)替換成你工程里的通信層。替換時(shí)優(yōu)先處理消息收發(fā)和進(jìn)程創(chuàng)建這兩個(gè)高頻點(diǎn)其他模塊可以后置。這里給一個(gè)實(shí)用的驗(yàn)證方法改造完一個(gè)模塊不要直接全量替換先讓新舊兩個(gè)進(jìn)程同時(shí)跑用 sin 對(duì)比兩者的 STATE 變化。如果新進(jìn)程的 RECV/REPLY 狀態(tài)切換和舊進(jìn)程節(jié)奏一致再切流量過(guò)去。我以前接手一個(gè)老項(xiàng)目時(shí)就是用這個(gè)辦法在不動(dòng)整體架構(gòu)的前提下把消息通信層一點(diǎn)一點(diǎn)換掉的。從那以后我每次接觸新板子都強(qiáng)制先把 IPC 的狀態(tài)機(jī)走一遍再碰業(yè)務(wù)代碼。QNX 的實(shí)時(shí)性不神秘但它的消息模型值得認(rèn)真對(duì)待希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取