Linux 設備驅動學習之 異步通知

From:http://blog.csdn.net/ypoflyer/article/details/6131334

 

異步通知:

 

使用poll輪詢方式的時候,相當於應用程序在需要的時候詢問設備“準備好了嗎?”,如果有這樣一種情況,一個進程在低優先級正在執行長的循環計算,但又需要“儘可能快”的處理輸入數據,如果採用poll的方式,那麼需要這個應用程序週期性的調用poll來檢測數據,也就是週期性的詢問設備“準備好了嗎?”,顯然這種情況下poll並不是最佳的方法。更好的方法應該是一旦設備準備好了就發出一個“我準備好了”的信號給應用程序,然後應用程序再去處理。這樣顯然更高效。這種方法就是:異步通知。

 


通過上面的描述,顯然如果想要異步通知,首先設備就要有發信號的功能,這就要啓動文件的異步通知機制。爲了啓動這個機制,用戶程序需要進行兩個步驟:
1.指定一個進程作爲文件的owner(“所有者”或者“屬主”),用來接收“我準備好了”這個信號。該進程的ID好被保存到filp->f_owner中。通過調用fcntl執行F_SETOWN來完成這一步。
2.在設備中設置FASYNC標誌來真正啓動異步通知機制。通過調用fcntl執行F_SETFL命令完成這一步。

 


執行完這兩個步驟以後,當有新數據到達時設備文件會發送一個SIGIO信號(“我準備好了”),該信號被髮送到存放在file->f_owner中的進程。
注:顯然進程如果只接受到“我準備好了”這個信號,它就會問“那個‘我’是誰阿”,所以如果有多於一個文件可以異步通知輸入的進程,那麼當應用程序接收到信號時還是要藉助poll輪詢一次,以便真正確定輸入的來源。


下面分別從驅動程序角度和應用程序角度分別總結一下我們應該進行哪些工作。
一、 驅動方面:
1. 在設備抽象的數據結構中增加一個struct fasync_struct的指針
2. 實現設備操作中的fasync函數,其主體就是調用內核的fasync_helper函數。
3. 在需要向用戶空間通知的地方(例如scullpipe的write中)調用內核的kill_fasync函數。
4. 在驅動的release方法中調用前面定義的fasync函數, 例如scull_p_fasync(-1, filp, 0);


看一下驅動方面的源碼:


    //對應上邊的第1步
    struct scull_pipe   

  1.     {  
  2.         ......  
  3.         struct fasync_struct *async_queue;   
  4.         ......  
  5.     };  

 

    //對應上面的第2步
static int scull_p_fasync(int fd, struct file *filp, int mode)   

  1.     {  
  2.         struct scull_pipe *dev = filp->private_data;  
  3.   
  4.         return fasync_helper(fd, filp, mode, &dev->async_queue);  
  5.     }  
    
    //對應上面的第3步,由於新數據是由於進程調用了write而產生的,所以在這裏發送SIGIO信號
static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)  
  1.     {  
  2.         ......  
  3.         ......  
  4.         if (dev->async_queue)   
  5.             kill_fasync(&dev->async_queue, SIGIO, POLL_IN);  
  6.         ......  
  7.         ......  
  8.   
  9.     }  
    
    //對應上面第4步
  1. static int scull_p_release(struct inode *inode, struct file *filp)  
  2.     {  
  3.         ......  
  4.         scull_p_fasync(-1, filp, 0);  
  5.         ......  
  6.     }  

 

 


二、 應用層方面
1. 利用signal或者sigaction設置SIGIO信號的處理函數,關於signal的使用參照http://blog.csdn.net/ypoflyer/article/details/6131334

2. fcntl的F_SETOWN指令設置當前進程爲設備文件owner
3. fcntl的F_SETFL指令設置FASYNC標誌


看一下應用層方面的源代碼,來自asynctest.c文件
   

#include <stdio.h>  
  1. #include <stdlib.h>  
  2. #include <string.h>  
  3. #include <unistd.h>  
  4. #include <signal.h>  
  5. #include <fcntl.h>  
  6.   
  7. int gotdata=0;  
  8. void sighandler(int signo) //這個就是收到SIGIO信號對應的處理函數  
  9. {  
  10.     if (signo==SIGIO)  
  11.         gotdata++;  
  12.     return;  
  13. }  
  14.   
  15. char buffer[4096];  
  16.   
  17. int main(int argc, char **argv)  
  18. {  
  19.     int count;  
  20.     struct sigaction action; //sigaction結構變量action  
  21.   
  22.     memset(&action, 0, sizeof(action)); //清空該變量  
  23.     action.sa_handler = sighandler;  
  24.     action.sa_flags = 0;  
  25.   
  26.     //對應上面的第1步,設置SIGIO的處理函數爲sighandle  
  27.     sigaction(SIGIO, &action, NULL); r  
  28.   
  29.     //對應上面的第2步,設置當前進程爲設備文件owner。使用getpid獲得當前進程的ID  
  30.     fcntl(STDIN_FILENO, F_SETOWN, getpid());  
  31.   
  32.     //對應上面的第3步,設置FASYNC標誌,使用的命令是F_SETFL  
  33.     //一旦文件描述符被設置成具有FASYNC屬性的狀態,  
  34.     //也就是將設備文件切換到異步操作模式。  
  35.     //這時系統就會自動調用驅動程序的fasync方法  
  36.     fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | FASYNC);  
  37.     //到此異步通知機制就已經設置好了  
  38.       
  39.     while(1) {  
  40.         /* this only returns if a signal arrives */  
  41.         sleep(86400); //此處線程開始休眠,該程序一直處於休眠狀態,當有標準輸入時候,linux操作系統發送SIGIO消息給進程,並將其喚醒  
  42.         if (!gotdata) //喚醒之後還要再檢查以下gotdata的狀態,如果不是SIGIO喚醒的,則表明沒有標準輸入,則繼續進行while循環  
  43.             continue;  
  44.         count=read(0, buffer, 4096);  
  45.         /* buggy: if avail data is more than 4kbytes... */  
  46.         write(1,buffer,count);  
  47.         gotdata=0;  
  48.     }  
  49. }  

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章