好,那下一個問題就來了,這個過程如何實現呢?簡單,兩方面的工作。
一 驅動方面:
1. 在設備抽象的數據結構中增加一個struct fasync_struct的指針
2. 實現設備操作中的fasync函數,這個函數很簡單,其主體就是調用內核的fasync_helper函數。
3. 在需要向用戶空間通知的地方(例如中斷中)調用內核的kill_fasync函數。
4. 在驅動的release方法中調用前面定義的fasync函數
呵呵,簡單吧,就三點。其中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);
}
{
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方法。
*/
//如果程序中沒有這個死循環,會立即執行完畢
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..*/
}
if (fasync_queue)
kill_fasync(&fasync_queue, SIGIO, POLL_IN);
好了,這下大家知道該怎麼用異步通知機制了吧?
1 兩個函數的原型
int fasync_helper(struct inode *inode, struct file *filp, int mode, struct fasync_struct **fa);
一個"幫忙者", 來實現 fasync 設備方法. mode 參數是傳遞給方法的相同的值, 而 fa 指針指向一個設
如果這個驅動支持異步通知, 這個函數可用來發送一個信號到登記在 fa 中的進程.
fasync_helper 被調用來從相關的進程列表中添加或去除入口項, 當 FASYNC 標誌因一個打開文件而改變
這是 scullpipe 如何實現 fasync 方法的:
{
struct scull_pipe *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
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);