Linux驅動開發--異步通知與異步I/O

3、異步通知與異步I/O

3.1 Linux信號

阻塞與非阻塞訪問、poll()函數提供了較好的解決設備訪問的機制,但是如果有了異步通知,整套機制則更加完整了。
異步通知的意思是:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態,這一點非常類似于硬件上“中斷”的概念,比較準確的稱謂是“信號驅動的異步I/O”。信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候到達。
阻塞I/O意味著一直等待設備可訪問后再訪問,非阻塞I/O中使用poll()意味著查詢設備是否可訪問,而異步通知則意味著設備通知用戶自身可訪問,之后用戶再進行I/O處理。由此可見,這幾種I/O方式可以相互補充。

圖9.1呈現了阻塞I/O、結合輪詢的非阻塞I/O及基于SIGIO的異步通知在時間先后順序上的不同。

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

這里要強調的是:阻塞、非阻塞I/O、異步通知本身沒有優劣,應該根據不同的應用場景合理選擇。

異步通知的核心就是信號,在 arch/xtensa/include/uapi/asm/signal.h 文件中定義了 Linux 所支持的所有信號,這些信號如下所示:

#define SIGHUP 		1 /* 終端掛起或控制進程終止 */
#define SIGINT 		2 /* 終端中斷(Ctrl+C 組合鍵) */
#define SIGQUIT 	3 /* 終端退出(Ctrl+\組合鍵) */
#define SIGILL 		4 /* 非法指令 */
#define SIGTRAP 	5 /* debug 使用,有斷點指令產生 */
#define SIGABRT 	6 /* 由 abort(3)發出的退出指令 */
#define SIGIOT 		6 /* IOT 指令 */
#define SIGBUS 		7 /* 總線錯誤 */
#define SIGFPE 		8 /* 浮點運算錯誤 */
#define SIGKILL		9 /* 殺死、終止進程  					----不可忽略 */
#define SIGUSR1	 	10 /* 用戶自定義信號 1 */
#define SIGSEGV 	11 /* 段違例(無效的內存段) */
#define SIGUSR2 	12 /* 用戶自定義信號 2 */
#define SIGPIPE 	13 /* 向非讀管道寫入數據 */
#define SIGALRM 	14 /* 鬧鐘 */
#define SIGTERM 	15 /* 軟件終止 */
#define SIGSTKFLT 	16 /* 棧異常 */
#define SIGCHLD 	17 /* 子進程結束 */
#define SIGCONT 	18 /* 進程繼續 */
#define SIGSTOP 	19 /* 停止進程的執行,只是暫停 			----不可忽略*/#define SIGTSTP 	20 /* 停止進程的運行(Ctrl+Z 組合鍵) */
#define SIGTTIN 	21 /* 后臺進程需要從終端讀取數據 */
#define SIGTTOU 	22 /* 后臺進程需要向終端寫數據 */
#define SIGURG 		23 /* 有"緊急"數據 */
#define SIGXCPU 	24 /* 超過 CPU 資源限制 */
#define SIGXFSZ 	25 /* 文件大小超額 */
#define SIGVTALRM 	26 /* 虛擬時鐘信號 */
#define SIGPROF 	27 /* 時鐘信號描述 */
#define SIGWINCH 	28 /* 窗口大小改變 */
#define SIGIO 		29 /* 可以進行輸入/輸出操作 */
#define SIGPOLL 	SIGIO
/* #define SIGLOS 29 */
#define SIGPWR 		30 /* 斷點重啟 */
#define SIGSYS 		31 /* 非法的系統調用 */
#define SIGUNUSED 	31 /* 未使用信號 *//* These should not be considered constants from userland.  */
#define SIGRTMIN	32
#define SIGRTMAX	(_NSIG-1)

除了 SIGKILL(9)和 SIGSTOP(19)這兩個信號不能被忽略外,進程能夠忽略或捕獲其他的全部信號。一個信號被捕獲的意思是當一個信號到達時有相應的代碼處理它。如果一個信號沒有被這個進程所捕獲,內核將采用默認行為處理。

3.2 信號的接收–應用端

我們使用中斷的時候需要設置中斷處理函數,同樣的,如果要在應用程序中使用信號,那么就必須設置信號所使用的信號處理函數,在應用程序中使用 signal 函數來設置指定信號的處理函數, signal 函數原型如下所示:

sighandler_t signal(int signum, sighandler_t handler)-signum:要設置處理函數的信號。
-handler: 信號的處理函數。若為SIGIGN,表示忽略該信號;若為SIGDFL,表示采用系統默認方式處理信號;若為用戶自定義的函數,則信號被捕獲到后,該函數將被執行。
-返回值: 設置成功的話返回信號的前一個處理函數handler,設置失敗的話返回 SIG_ERR。

信號處理函數原型如下所示:

typedef void (*sighandler_t)(int)

先來看一個使用信號實現異步通知的例子,它通過signal(SIGIO,input_handler)對標準輸入文件描述符STDIN_FILENO啟動信號機制。用戶輸人后,應用程序將接收到SIGIO信號其處理函數input_handler()將被調用,如代碼清單 9.2所示。

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#define MAX_LEN 100void input_handler(int num)
{char data[MAX_LEN];int len;/* 讀取并輸出 STDIN_FILENO 上的輸入 */len = read(STDIN_FILENO, &data, MAX_LEN);data[len] = 0;printf("input available:%s\n", data);
}int main(void)
{int oflags;/* 啟動信號驅動機制 */signal(SIGIO, input_handler);fcntl(STDIN_FILENO, F_SETOWN, getpid());oflags = fcntl(STDIN_FILENO, F_GETFL);fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);/* 最后進入一個死循環,僅為保持進程不終止。如果程序中沒有這個死循環會立即執行完畢 */while (1);
}

上述代碼 24 行為 SIGIO 信號安裝 input_handler() 作為處理函數,第 25 行設置本進程為 STDIN_FILENO 文件的擁有者,沒有這一步,內核不會知道應該將信號發給哪個進程。而為了啟用異步通知機制,還需對設備設置 FASYNC 標志,第 26 行、27 行代碼可實現此目的。
整個程序的執行效果如下:

[root@localhost driver_study1# ./signal_test
I am Chinese.
input available: I am Chinese.
-> signal_test 程序打印I love Linux driver.
input available: I love Linux driver.
-> signal_test 程序打印

從中可以看出,當用戶輸入一串字符串后,標準輸入設備釋放 SIGIO 信號,這個信號“中斷”與驅使對應的應用程序中的 input_handler() 得以執行,并將用戶輸入顯示出來。
**由此可見,為了能在用戶空間中處理一個設備釋放的信號,它必須完成 3 項工作。**應用程序對異步通知的處理包括以下三步:
1、注冊信號處理函數
應用程序根據驅動程序所使用的信號來設置信號的處理函數,應用程序使用 signal 函數來設置信號的處理函數。

2、將本應用程序的進程號告訴給內核
使用 fcntl(fd, F_SETOWN, getpid())將本應用程序的進程號告訴給內核。

3、開啟異步通知
使用如下兩行程序開啟異步通知:

flags = fcntl(fd, F_GETFL);  		/* 獲取當前的進程狀態 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啟當前進程異步通知功能 */

重點就是通過 fcntl 函數設置進程狀態為 FASYNC,經過這一步,驅動程序中的 fasync 函數就會執行。

3.3 信號的釋放–設備驅動端

在設備驅動和應用程序的異步通知交互中,僅僅在應用程序端捕獲信號是不夠的,因為信號的源頭在設備驅動端。因此,應該在合適的時機讓設備驅動釋放信號,在設備驅動程序中增加信號釋放的相關代碼。

為了使設備支持異步通知機制,驅動程序中涉及3項工作。

1)支持 F_SETOWN 命令:

  • 能在這個控制命令處理中設置 filp->f_owner 為對應進程 ID。不過此項工作已由內核完成,設備驅動無須處理。

2)支持 F_SETFL 命令的處理:

  • 每當 FASYNC 標志改變時,驅動程序中的 fasync() 函數將得以執行。因此,驅動中應該實現 fasync() 函數。

3)在設備資源可獲得時,調用 kill_fasync() 函數激發相應的信號。

驅動中的上述3項工作和應用程序中的3項工作是一一對應的:

在這里插入圖片描述

設備驅動中異步通知編程比較簡單,主要用到一項數據結構和兩個函數。

數據結構是 fasync_struct 結構體:

struct fasync_struct {spinlock_t fa_lock;       		// 保護結構體的自旋鎖int magic;               		// 用于驗證結構體的魔術數字int fa_fd;              	 	// 文件描述符struct fasync_struct *fa_next; 	// 指向下一個結構體的指針,形成單向鏈表struct file *fa_file;     		// 指向文件結構體的指針struct rcu_head fa_rcu;   		// RCU機制使用的頭結構
};

和其他的設備驅動一樣,fasync_struct 結構體指針放在設備結構體中仍然是最佳選擇,支持異步通知的設備結構體模板,如下所示:

struct xxx_dev {struct cdev cdev; /* cdev 結構體 */...struct fasync_struct *async_queue; /* 異步結構體指針 */
};
  • 當一個進程對某個文件調用 fcntl() 系統調用并設置 FASYNC 標志時,內核會創建一個 fasync_struct 實例,并將其加入到文件的異步通知隊列中。
  • 當文件狀態發生變化時(如數據可讀或可寫),內核會遍歷這個隊列----上圖中的fasync_struct列表,找到相關的 fasync_struct,并通過文件描述符通知對應的進程。

引出了**“異步通知隊列”**:接下來再進一步分析

在 Linux 內核中,異步通知隊列(由 fasync_struct 結構體組成的鏈表)是通過設備驅動程序的私有數據結構來維護的(也就是前面的struct fasync_struct *async_queue; /* 異步結構體指針 */

如何維護異步通知隊列

  1. 私有數據結構:設備驅動程序通常會在其私有數據結構中維護一個指向 fasync_struct 的指針。這個私有數據結構可能是 struct inode 的一部分,或者是設備驅動程序定義的其他結構體。
  2. fasync_helper 函數:當需要處理異步通知時,內核會調用 fasync_helper 函數。這個函數會檢查設備驅動程序的私有數據結構中是否有指向 fasync_struct 的指針,并據此決定是否需要創建或修改異步通知隊列。
  3. kill_fasync 函數:當設備驅動程序需要通知應用程序文件狀態發生變化時(如數據可讀或可寫),它會調用 kill_fasync 函數。這個函數會遍歷由 fasync_struct 結構體組成的鏈表,并向每個注冊的進程發送信號。

兩個函數分別是:

1)處理 FASYNC 標志變更的函數。

int (*fasync) (int fd, struct file *filp, int on);
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);

2)釋放信號用的函數。

void kill_fasync(struct fasync_struct **fa, int sig, int band);

如果要使用異步通知,需要在設備驅動中實現 file_operations 操作集中的 fasync 函數,fasync 函數里面一般通過調用 fasync_helper 函數來初始化前面定義的 fasync_struct 結構體指針

當應用程序通過“fcntl(fd, F_SETFL, flags | FASYNC)”改變fasync 標記的時候,驅動程序 file_operations 操作集中的 fasync 函數就會執行。 在設備驅動的 fasync() 函數中,只需要簡單地將該函數的3個參數以及 fasync_struct 結構體指針的指針作為第4個參數傳入 fasync_helper() 函數即可。下面給出了支持異步通知的設備驅動程序 fasync() 函數的模板。

struct xxx_dev {......struct fasync_struct *async_queue; /* 異步相關結構體 */
};static int xxx_fasync(int fd, struct file *filp, int on)
{struct xxx_dev *dev = (xxx_dev *)filp->private_data;if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)return -EIO;return 0;
}static struct file_operations xxx_ops = {.......fasync = xxx_fasync,......
};

在設備資源可以獲得時,應該調用 kill_fasync() 釋放 SIGIO 信號。在可讀時,第3個參數設置為 POLL_IN,在可寫時,第3個參數設置為 POLL_OUT。下面給出了釋放信號的范例。

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{struct xxx_dev *dev = filp->private_data;.../* 產生異步讀信號 */if (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);...
}

最后,在文件關閉時,即在設備驅動的 release() 函數中,應調用設備驅動的 fasync() 函數將文件從異步通知的列表中刪除。給出了支持異步通知的設備驅動 release() 函數的模板。

static int xxx_release(struct inode *inode, struct file *filp)
{/* 將文件從異步通知列表中刪除 */xxx_fasync(-1, filp, 0);...return 0;
}

調用前面代碼中的 xxx_fasync 函數來完成 fasync_struct 的釋放工作,但是,其最終還是通過 fasync_helper 函數完成釋放工作。

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77694.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77694.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77694.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大語言模型推理能力的強化學習現狀理解GRPO與近期推理模型研究的新見解

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎&#xff1f;訂閱我們的簡報&#xff0c;深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同&#xff0c;從行業內部的深度分析和實用指南中受益。不要錯過這個機會&#xff0c;成為AI領…

【Linux系統】Linux基礎指令(詳解Linux命令行常用指令,每一個指令都有示例演示)

文章目錄 一、與文件路徑相關的指令0.補充知識&#xff1a;路徑的認識1.pwd 指令2.cd 指令&#xff08;含家目錄的介紹&#xff09; 二、創建和刪除文件的指令0.補充知識&#xff1a;普通文件和目錄文件1.touch 指令&#xff08;可以修改文件的時間戳&#xff09;2.mkdir 指令3…

LangChain 單智能體模式示例【純代碼】

# LangChain 單智能體模式示例import os from typing import Anyfrom langchain.agents import AgentType, initialize_agent, Tool from langchain_openai import ChatOpenAI from langchain.tools import BaseTool from langchain_experimental.tools.python.tool import Pyt…

解決:VSCode C++ conan 安裝第三方庫后 頭文件報錯

文章目錄 1 頭文件include路徑查找報錯參考 1 頭文件include路徑查找報錯 找到conan_toolchain.cmake中 INCLUDE_PATH list(PREPEND CMAKE_INCLUDE_PATH "/Users/hanliqiang/.conan2/p/b/fmte8c4f7a755477/p/include")生成C編譯配置 CtrlShiftP 中選擇C Edit Confi…

松靈Cobot Magic雙臂具身遙操機器人(基于ROS的定位建圖與協同導航技術)

摘要 本文以CobotMagic可移動協作機器人為研究對象&#xff0c;從硬件架構設計、軟件系統架構、多傳感器融合定位建圖系統、智能導航系統協同機制四個維度&#xff0c;深入解析機器人系統工作原理。重點研究多傳感器融合定位建圖系統實現原理&#xff0c;結合實測數據驗證系統…

回歸,git 分支開發操作命令

核心分支說明 主分支&#xff08;master/production&#xff09;存放隨時可部署到生產環境的穩定代碼&#xff0c;僅接受通過測試的合并請求。 開發分支&#xff08;develop&#xff09;集成所有功能開發的穩定版本&#xff0c;日常開發的基礎分支&#xff0c;從該分支創建特性…

ASP.NET Core 最小 API:極簡開發,高效構建(下)

在上篇文章 ASP.NET Core 最小 API&#xff1a;極簡開發&#xff0c;高效構建&#xff08;上&#xff09; 中我們添加了 API 代碼并且測試&#xff0c;本篇繼續補充相關內容。 一、使用 MapGroup API 示例應用代碼每次設置終結點時都會重復 todoitems URL 前綴。 API 通常具有…

Spring之我見 - Spring Boot Starter 自動裝配原理

歡迎光臨小站&#xff1a;致橡樹 Spring Boot Starter 的核心設計理念是 約定優于配置&#xff0c;其核心實現基于 自動配置&#xff08;Auto-Configuration&#xff09; 和 條件化注冊&#xff08;Conditional Registration&#xff09;。以下是其生效原理&#xff1a; 約定…

精益數據分析(7/126):打破創業幻想,擁抱數據驅動

精益數據分析&#xff08;7/126&#xff09;&#xff1a;打破創業幻想&#xff0c;擁抱數據驅動 在創業的道路上&#xff0c;我們都懷揣著夢想&#xff0c;但往往容易陷入自我編織的幻想中。我希望通過和大家一起學習《精益數據分析》&#xff0c;能幫助我們更清醒地認識創業過…

牛客java練習題

[toc] 1.依賴注入 依賴注入是一種設計模式和編程思想,不依賴 具體的框架實現,可以通過多種方式和框架來實現可以通過Spring , Google Guice , PicoContainer 等都可以實現依賴注入,也可以通過手動編寫實現目的: 為了解耦合,將對象之間的依賴關系從代碼中解耦出來, 使系統更加…

大模型應用開發自學筆記

理論學習地址&#xff1a; https://zh.d2l.ai/chapter_linear-networks/index.html autodl學術加速&#xff1a; source /etc/network_turboconda常見操作: 刪除&#xff1a; conda remove --name myenv --all -y導出&#xff1a; conda env export > environment.yml…

鴻蒙ArkUI實戰之TextArea組件、RichEditor組件、RichText組件、Search組件的使用

本文接上篇繼續更新ArkUI中組件的使用&#xff0c;本文介紹的組件有TextArea組件、RichEditor組件、RichText組件、Search組件&#xff0c;這幾個組件的使用對應特定場景&#xff0c;使用時更加需要注意根據需求去使用 TextArea組件 官方文檔&#xff1a; TextArea-文本與輸…

除了`String`、`StringBuffer` 和 `StringBuilder`之外,還有什么處理字符串的方法?

一、標準庫中的字符串處理類 1. StringJoiner&#xff08;Java 8&#xff09; 用途&#xff1a;用于在拼接字符串時自動添加分隔符、前綴和后綴。示例&#xff1a;StringJoiner sj new StringJoiner(", ", "[", "]"); sj.add("A").…

Qt中讀寫結構體字節數據

在Qt中讀寫結構體字節數據通常涉及將結構體轉換為字節數組(QByteArray)或直接從內存中讀寫。以下是幾種常見方法&#xff1a; 方法1&#xff1a;使用QDataStream讀寫結構體 cpp #include <QFile> #include <QDataStream>// 定義結構體 #pragma pack(push, 1) //…

Windows 10 上安裝 Spring Boot CLI詳細步驟

在 Windows 10 上安裝 Spring Boot CLI 可以通過以下幾種方式完成。以下是詳細的步驟說明&#xff1a; 1. 手動安裝&#xff08;推薦&#xff09; 步驟 1&#xff1a;下載 Spring Boot CLI 訪問 Spring Boot CLI 官方發布頁面。下載最新版本的 .zip 文件&#xff08;例如 sp…

Unity3D仿星露谷物語開發37之澆水動畫

1、目標 當點擊水壺時&#xff0c;實現澆水的動畫。同時有一個水從水壺中流出來的特效。 假如某個grid被澆過了&#xff0c;則不能再澆水了。。 如果某個grid沒有被dug過&#xff0c;也不能被澆水。 2、優化Settings.cs腳本 增加如下內容&#xff1a; public static float…

【2】Kubernetes 架構總覽

Kubernetes 架構總覽 主節點與工作節點 主節點 Kubernetes 的主節點&#xff08;Master&#xff09;是組成集群控制平面的關鍵部分&#xff0c;負責整個集群的調度、狀態管理和決策。控制平面由多個核心組件構成&#xff0c;包括&#xff1a; kube-apiserver&#xff1a;集…

如何對docker鏡像存在的gosu安全漏洞進行修復——筑夢之路

這里以mysql的官方鏡像為例進行說明&#xff0c;主要流程為&#xff1a; 1. 分析鏡像存在的安全漏洞具體是什么 2. 根據分析結果有針對性地進行修復處理 3. 基于當前鏡像進行修復安全漏洞并復核驗證 # 鏡像地址mysql:8.0.42 安全漏洞現狀分析 dockerhub網站上獲取該鏡像的…

【Tauri2】026——Tauri+Webassembly

前言 不多廢話 直言的說&#xff0c;筆者看到這篇文章大佬的文章 【04】Tauri 入門篇 - 集成 WebAssembly - 知乎https://zhuanlan.zhihu.com/p/533025312嘗試集成一下WebAssembly&#xff0c;直接開始 正文 準備工作 新建一個項目 安裝 vite的rsw插件和rsw pnpm instal…

OpenHarmony Camera開發指導(五):相機預覽功能(ArkTS)

預覽是在相機啟動后實時顯示場景畫面&#xff0c;通常在拍照和錄像前執行。 開發步驟 創建預覽Surface 如果想在屏幕上顯示預覽畫面&#xff0c;一般由XComponent組件為預覽流提供Surface&#xff08;通過XComponent的getXcomponentSurfaceId方法獲取surfaceid&#xff09;&…