異步通知

要弄明白這個問題,我們得從最基本的原理開始。我們知道,驅動程序運行在內核空間中,應用程序運行
在用戶空間中,兩者是不能直接通信的。但在實際應用中,在設備已經準備好的時候,我們希望通知用戶
程序設備已經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 <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#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. 上一節我們已經學習了用poll輪詢數據,來避免不必要的休眠,但是事實上,輪詢的直接負面作用就是效率低下,這樣一節我們學習如何使用異步通知IO來提高效率

 

2. fcntl系統調用

int fcntl(int fd, int cmd, long arg);

fcntl的作用是改變一個已打開文件的屬性,fd是要改變的文件的描述符,cmd是命令羅列如下:

F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_SETLK, F_SETLKW, F_GETLK, F_GETOWN, F_SETOWN

本節只關心F_SETOWN(設置異步IO所有權),F_GETFL(獲取文件flags),F_SETFL(設置文件flags)

arg是要改變的屬性內容

 

3. 用戶進程啓用異步通知機制

首先,設置一個進程作爲一個文件的屬主(owner),這樣內核就知道該把文件的信號發送給哪個進程

fcntl(fd, F_SETOWN, getpid()); // getpid()就是當前進程咯

然後,給文件設置FASYNC標誌,以啓用異步通知機制

fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);

 

4. 缺陷

當有多個文件發送異步通知信號給一個進程時,進程無法知道是哪個文件發送的信號,這時候還是要藉助poll的幫助完成IO

 

5. 從驅動程序的角度考慮

當文件的狀態標誌設置了FASYNC操作時,驅動程序會調用fasync的函數。

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);

}

 

當有新的數據到達時,驅動程序應該發送一個SIGIO給用戶,這個操作用kill_fasync方法完成

if(dev->async_queue)

         kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

 

最後,從異步通知列表中移除註冊進去了的文件指針就直接調用scull_p_fasync(-1, filp, 0);



發佈了43 篇原創文章 · 獲贊 11 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章