異步通知

要弄明白這個問題,我們得從最基本的原理開始。我們知道,驅動程序運行在內核空間中,應用程序運行 在用戶空間中,兩者是不能直接通信的。但在實際應用中,在設備已經準備好的時候,我們希望通知用戶 程序設備已經ok,用戶程序可以讀取了,這樣應用程序就不需要一直查詢該設備的狀態,從而節約了資源 ,這就是異步通知。 好,那下一個問題就來了,這個過程如何實現呢?簡單,兩方面的工作。 一 驅動方面: 1. 在設備抽象的數據結構中增加一個struct fasync_struct的指針 2. 實現設備操作中的fasync函數,這個函數很簡單,其主體就是調用內核的fasync_helper函數。 3. 在需要向用戶空間通知的地方(例如中斷中)調用內核的kill_fasync函數。 4. 在驅動的release方法中調用前面定義的fasync函數 呵呵,簡單吧,就三點。其中fasync_helper和kill_fasync都是內核函數,我們只需要調用就可以了。在 1中定義的指針是一個重要參數,fasync_helper和kill_fasync會使用這個參數。 二 應用層方面 1. 利用signal或者sigaction設置SIGIO信號的處理函數 2. fcntl的F_SETOWN指令設置當前進程爲設備文件owner 3. fcntl的F_SETFL指令設置FASYNC標誌 完成了以上的工作的話,當內核執行到kill_fasync函數,用戶空間SIGIO函數的處理函數就會被調用了。 呵呵,看起來不是很複雜把,讓我們結合具體代碼看看就更明白了。 先從應用層代碼開始吧: #include #include #include #include #include #include #define MAX_LEN 100 void 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); } main() {  int oflags;  //啓動信號驅動機制  signal(SIGIO, input_handler); /* 將SIGIO信號同input_handler函數關聯起來, 一旦產生SIGIO信號,就會執行input_handler, 有點軟中斷的意思吧 */  fcntl(STDIN_FILENO, F_SETOWN, getpid()); /* STDIN_FILENO是打開的設備文件描述符, F_SETOWN用來決定操作是幹什麼的, getpid()是個系統調用,功能是找到一個進程號pid分配給當前進程 整個函數的功能是STDIN_FILENO設置這個設備文件的主人爲當前進程。 */  oflags = fcntl(STDIN_FILENO, F_GETFL); /*得到打開文件描述符的狀態*/  fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); /* 設置文件描述符的狀態爲oflags | FASYNC屬性, 一旦文件描述符被設置成具有FASYNC屬性的狀態, 也就是將設備文件切換到異步操作模式。 這時系統就會自動調用驅動程序的fasync方法。 */  //最後進入一個死循環,程序什麼都不幹了,只有信號能激發input_handler的運行  //如果程序中沒有這個死循環,會立即執行完畢  while (1); } 再看驅動層代碼,驅動層其他部分代碼不變,就是增加了一個fasync方法的實現以及一些改動 static struct fasync_struct *fasync_queue; /*首先是定義一個結構體,其實這個結構體存放的是一個列表,這個列表保存的是 一系列設備文件,SIGIO信號就發送到這些設備上*/ static int my_fasync(int fd, struct file * filp, int on) /*fasync方法的實現*/ { int retval; retval=fasync_helper(fd,filp,on,&fasync_queue); /*將該設備登記到fasync_queue隊列中去*/ if(retval<0) return retval; return 0; } 在驅動的release方法中我們再調用my_fasync方法 int my_release(struct inode *inode, struct file *filp) { /*..processing..*/ drm_fasync(-1, filp, 0); /*..processing..*/ } 這樣後我們在需要的地方(比如中斷)調用下面的代碼,就會向fasync_queue隊列裏的設備發送SIGIO信號 ,應用程序收到信號,執行處理程序 if (fasync_queue) kill_fasync(&fasync_queue, SIGIO, POLL_IN); 好了,這下大家知道該怎麼用異步通知機制了吧? 以下是幾點說明[1]: 1 兩個函數的原型 int fasync_helper(struct inode *inode, struct file *filp, int mode, struct fasync_struct **fa); 一個"幫忙者", 來實現 fasync 設備方法. mode 參數是傳遞給方法的相同的值, 而 fa 指針指向一個設 備特定的 fasync_struct * void kill_fasync(struct fasync_struct *fa, int sig, int band); 如果這個驅動支持異步通知, 這個函數可用來發送一個信號到登記在 fa 中的進程. 2. fasync_helper 被調用來從相關的進程列表中添加或去除入口項, 當 FASYNC 標誌因一個打開文件而改變 . 它的所有參數除了最後一個, 都被提供給 fasync 方法並且被直接傳遞. 當數據到達時 kill_fasync 被用來通知相關的進程. 它的參數是被傳遞的信號(常常是 SIGIO)和 band, 這幾乎都是 POLL_IN[25](但 是這可用來發送"緊急"或者帶外數據, 在網絡代碼裏). 這是 scullpipe 如何實現 fasync 方法的: static int scull_p_fasync(int fd, struct file *filp, int mode) { struct scull_pipe *dev = filp->private_data; return fasync_helper(fd, filp, mode, &dev->async_queue); } 顯然所有的工作都由 fasync_helper 進行. 但是, 不可能實現這個功能在沒有一個方法在驅動裏的情況 下, 因爲這個幫忙函數需要存取正確的指向 struct fasync_struct (這裏是 與dev->async_queue)的指針, 並且只有驅動可提供這個信息. 當數據到達, 下面的語句必須被執行來通知異步讀者. 因爲對 sucllpipe 讀者的新數據通過一個發出 write 的進程被產生, 這個語句出現在 scullpipe 的 write 方法中. if (dev->async_queue) kill_fasync(&dev->async_queue, SIGIO, POLL_IN); 注意, 一些設備還實現異步通知來指示當設備可被寫入時; 在這個情況, 當然, kill_fasnyc 必須被使用 一個 POLL_OUT 模式來調用. [1]參考自LLD3
發佈了11 篇原創文章 · 獲贊 22 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章