異步通知fasync應用于系統調用signal和sigaction函數,簡單的說,signal函數就是讓一個信號與與一個函數對應,每當接收到這個信號就會調用相應的函數。[1]
那么什么是異步通知?異步通知類似于中斷的機制,當設備可寫時,設備驅動函數發送一個信號給內核,告知內核有數據可讀,在條件不滿足之前,并不會造成阻塞。而不像之前學的阻塞型IO和poll,它們是調用函數進去檢查,條件不滿足時還會造成阻塞。
其實在應用層啟用異步通知只三個步驟:
1)signal(SIGIO, sig_handler);
調用signal函數,讓指定的信號SIGIO與處理函數sig_handler對應。
2)fcntl(fd, F_SET_OWNER, getpid());
指定一個進程作為文件的“屬主(filp->owner)”,這樣內核才知道信號要發給哪個進程。
3)f_flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC);
在設備文件中添加FASYNC標志,驅動中就會調用將要實現的test_fasync函數。
三個步驟執行后,一旦有信號產生,相應的進程就會收到。
3.1 異步通知內核實現
實現異步通知,內核需要知道幾個東西:哪個文件(filp),什么信號(SIGIIO),發給哪個進程(pid),收到信號后做什么(sig_handler)。這些都由上述前兩個步驟完成了,而這前兩個步驟內核幫忙實現了,所以,我們只需要實現第三個步驟的一個簡單的傳參。
3.2 fasync_struct結構體
要實現傳參,我們需要把一個結構體struct fasync_struct添加到內核的異步隊列中,這個結構體用來存放對應設備文件的信息(如fd, filp)并交給內核來管理。一但收到信號,內核就會在這個所謂的異步隊列頭找到相應的文件(fd),并在filp->owner中找到對應的進程PID,并且調用對應的sig_handler了。
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
3.3 內核中我們的工作
上面說了前兩個步驟會由內核完成,所以我們只要做兩件事情:
1)定義結構體fasync_struct。
struct fasync_struct *async_queue;
2)實現test_fasync,把函數fasync_helper將fd,filp和定義的結構體傳給內核。
int test_fasync (int fd, struct file *filp, int mode)
{
struct _test_t *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}
函數fasync_helper的定義為:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
前面的三個參數其實就是teat_fasync的三個參數,所以只要我們定義好的fasync_struct結構體也傳進去就可以了。
3.4 其余的工作
另外還有兩件事:
3)當設備可寫時,調用函數kill_fasync發送信號SIGIO給內核。
if (dev->async_queue){
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}
講解一下這個函數:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
sig就是我們要發送的信號。
band(帶寬),一般都是使用POLL_IN,表示設備可讀,如果設備可寫,使用POLL_OUT
4)當設備關閉時,需要將fasync_struct從異步隊列中刪除:
test_fasync(-1, filp, 0);
刪除也是調用test_fasync,不過改了一下參數而已。