讓驅(qū)動主動“通知”應(yīng)用程序的核心機制,擺脫傳統(tǒng)阻塞或輪詢的低效模式,本質(zhì)是用軟件信號模擬硬件中斷,實現(xiàn)驅(qū)動與應(yīng)用的高效協(xié)同。以下從原理到實操,梳理核心邏輯。
一、異步通知:為什么需要它?
前文學(xué)習(xí)的阻塞IO和非阻塞IO(配合poll輪詢),核心都是應(yīng)用程序主動查詢驅(qū)動狀態(tài)——要么等待休眠,要么不斷輪詢,CPU資源被大量占用。而異步通知機制實現(xiàn)了徹底反轉(zhuǎn):
當驅(qū)動準備好數(shù)據(jù)時,主動向應(yīng)用程序發(fā)送“信號”,應(yīng)用程序接收到信號后再執(zhí)行讀寫操作,無需提前查詢,既節(jié)省CPU,又提升了響應(yīng)效率。這種機制就像硬件中斷,只不過發(fā)生在軟件層面,信號就是中斷的“軟件模擬版本”。
阻塞、非阻塞、異步通知沒有優(yōu)劣之分,僅適用于不同場景,需結(jié)合實際需求選擇,而異步通知是事件驅(qū)動型場景的最優(yōu)解。
二、信號:異步通知的核心載體
- 信號的本質(zhì)
Linux用信號作為異步通知的媒介,不同信號對應(yīng)不同事件,可理解為“軟件中斷號”。所有信號在內(nèi)核頭文件中預(yù)定義,核心要點是:
- 除SIGKILL(9號,強制終止)和SIGSTOP(19號,暫停進程)外,其余信號都可被忽略或捕獲;
- 驅(qū)動程序主要通過發(fā)送SIGIO信號,告知應(yīng)用程序設(shè)備可訪問。
- 應(yīng)用程序的信號處理
應(yīng)用程序需先注冊信號處理函數(shù),才能響應(yīng)驅(qū)動發(fā)來的信號,核心工具是signal()函數(shù):
sighandler_t signal(int signum, sighandler_t handler);
- 參數(shù):
signum指定目標信號,handler是信號觸發(fā)時執(zhí)行的處理函數(shù);
- 返回值:成功返回前一次處理函數(shù),失敗返回
SIG_ERR。
信號處理函數(shù)的原型是void (*sighandler_t)(int),函數(shù)內(nèi)可執(zhí)行數(shù)據(jù)讀取、狀態(tài)更新等操作。例如修改SIGINT的默認處理:當按下Ctrl+C時,先打印提示再退出,具體操作是新建測試程序,通過signal(SIGINT, 自定義函數(shù))注冊處理邏輯,編譯運行后,按下Ctrl+C即可觸發(fā)自定義邏輯,驗證信號捕獲的有效性。
三、驅(qū)動端:異步通知的實現(xiàn)步驟
驅(qū)動要實現(xiàn)主動發(fā)信號的能力,需完成三件事:定義結(jié)構(gòu)體、實現(xiàn)核心函數(shù)、在關(guān)鍵事件中觸發(fā)信號。
- 定義異步通知結(jié)構(gòu)體
在驅(qū)動的設(shè)備結(jié)構(gòu)體中,必須加入fasync_struct類型的指針變量,它是驅(qū)動與應(yīng)用建立異步連接的核心載體,以第13章的按鍵驅(qū)動結(jié)構(gòu)體為例,直接添加該指針成員,為后續(xù)管理異步隊列打下基礎(chǔ)。
- 實現(xiàn)file_operations的核心函數(shù)
驅(qū)動需實現(xiàn)兩個關(guān)鍵操作:
-
fasync()函數(shù):當應(yīng)用程序通過fcntl(fd, F_SETFL, flags | FASYNC)開啟異步通知時,該函數(shù)被自動調(diào)用,內(nèi)部核心是通過fasync_helper()初始化異步隊列,建立驅(qū)動與應(yīng)用的關(guān)聯(lián),最終返回操作結(jié)果。
-
release()函數(shù):當應(yīng)用程序關(guān)閉設(shè)備文件時,該函數(shù)自動執(zhí)行,調(diào)用fasync()函數(shù)并傳入終止參數(shù),清理異步隊列資源,避免內(nèi)存泄漏。
同時,要在file_operations操作集中綁定這兩個函數(shù),確保驅(qū)動能響應(yīng)應(yīng)用程序的異步開啟和關(guān)閉請求。
- 關(guān)鍵事件觸發(fā)信號
驅(qū)動在核心事件觸發(fā)時發(fā)送信號,以按鍵驅(qū)動為例,當定時器消抖確認有效按鍵后,需先判斷異步隊列是否存在,若存在則調(diào)用kill_fasync(),向應(yīng)用發(fā)送SIGIO信號,告知設(shè)備可讀。還需注意,實現(xiàn)異步通知后,原有的輪詢喚醒邏輯可屏蔽,因為信號已承擔(dān)通知職責(zé)。
四、應(yīng)用端:捕獲信號的實現(xiàn)步驟
應(yīng)用要接收驅(qū)動的信號,需精準完成三步配置,缺一不可:
- 注冊信號處理函數(shù):用
signal(SIGIO, 自定義函數(shù))捕獲SIGIO信號,自定義函數(shù)中通過read()讀取驅(qū)動數(shù)據(jù),完成后續(xù)處理。
- 設(shè)置進程歸屬:用
fcntl(fd, F_SETOWN, getpid())告訴內(nèi)核,當前進程是接收SIGIO信號的歸屬進程,確保信號精準投遞。
- 開啟異步通知標志:先通過
fcntl(fd, F_GETFL)獲取當前文件狀態(tài)標志,再通過fcntl(fd, F_SETFL, flags | FASYNC)添加FASYNC標志,正式開啟異步通知模式。
完成配置后,主循環(huán)無需輪詢,直接休眠等待信號,信號觸發(fā)時自動執(zhí)行處理函數(shù),讀取按鍵值并打印,極大降低CPU占用。
五、完整測試流程
- 編譯環(huán)節(jié):
- 驅(qū)動編譯:編寫Makefile,將目標文件指定為驅(qū)動源文件編譯結(jié)果,執(zhí)行編譯指令生成
.ko模塊文件;
- 應(yīng)用編譯:用交叉編譯器編譯應(yīng)用程序,生成適配開發(fā)板的可執(zhí)行文件。
- 運行環(huán)節(jié):
- 將驅(qū)動模塊和應(yīng)用文件復(fù)制到開發(fā)板指定目錄,先執(zhí)行
depmod初始化模塊依賴,再用modprobe加載驅(qū)動;
- 運行應(yīng)用程序,傳入設(shè)備路徑,按下開發(fā)板按鍵,終端會輸出按鍵值,驗證異步通知功能正常;
- 卸載驅(qū)動時,執(zhí)行對應(yīng)卸載指令,清理模塊資源。
六、核心總結(jié)
異步通知的本質(zhì)是用信號模擬中斷,驅(qū)動和應(yīng)用的職責(zé)完全反轉(zhuǎn):驅(qū)動主動觸發(fā),應(yīng)用被動響應(yīng),徹底擺脫輪詢的低效,實現(xiàn)低CPU占用、高實時響應(yīng)的通信模式。
核心邏輯可概括為:驅(qū)動定義異步結(jié)構(gòu)、實現(xiàn)fasync和release函數(shù)、按鍵事件發(fā)信號;應(yīng)用注冊信號處理、設(shè)置歸屬、開啟FASYNC;二者通過SIGIO信號聯(lián)動,高效完成數(shù)據(jù)傳輸。
該機制是Linux驅(qū)動中事件驅(qū)動場景的基礎(chǔ),與阻塞、非阻塞形成互補,為復(fù)雜驅(qū)動場景提供高效的解決方案。