本文轉載於CSDN博客:轉載請注明原文地址http://blog.csdn.net/billowszpt/article/details/7184302
linux設備驅動歸納總結(三):7.異步通知fasync
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
異步通知fasync是應用於系統調用signal和sigaction函數,下面我會使用signal函數。簡單的說,signal函數就是讓一個信號與與一個函數對應,沒當接收到這個信號就會調用相應的函數。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、什么是異步通知
個人認為,異步通知類似於中斷的機制,如下面的將要舉例的程序,當設備可寫時,設備驅動函數發送一個信號給內核,告知內核有數據可讀,在條件不滿足之前,並不會造成阻塞。而不像之前學的阻塞型IO和poll,它們是調用函數進去檢查,條件不滿足時還會造成阻塞。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、應用層中啟用異步通知機制
其實就三個步驟:
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函數。
三個步驟執行后,一旦有信號產生,相應的進程就會收到。
來個應用程序:
/*3rd_char_7/1st/app/monitor.c*/
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <sys/stat.h>
4 #include <fcntl.h>
5 #include <sys/select.h>
6 #include <unistd.h>
7 #include <signal.h>
8
9 unsigned int flag;
10
11 void sig_handler(int sig)
12 {
13 printf("<app>%s\n", __FUNCTION__);
14 flag = 1;
15 }
16
17 int main(void)
18 {
19 char buf[20];
20 int fd;
21 int f_flags;
22 flag = 0;
23
24 fd = open("/dev/test", O_RDWR);
25 if(fd < 0)
26 {
27 perror("open");
28 return -1;
29 }
30 /*三個步驟*/
31 signal(SIGIO, sig_handler);
32 fcntl(fd, F_SETOWN, getpid());
33 f_flags = fcntl(fd, F_GETFL);
34 fcntl(fd, F_SETFL, FASYNC | f_flags);
35
36 while(1)
37 {
38 printf("waiting \n"); //在還沒收到信號前,程序還在不停的打印
39 sleep(4);
40 if(flag)
41 break;
42 }
43
44 read(fd, buf, 10);
45 printf("finish: read[%s]\n", buf);
46
47 close(fd);
48 return 0;
49 }
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、驅動中需要實現的異步通知
上面說的三個步驟,內核已經幫忙實現了前兩個步驟,只需要我們稍稍實現第三個步驟的一個簡單的傳參。
實現異步通知,內核需要知道幾個東西:哪個文件(filp),什么信號(SIGIIO),發給哪個進程(pid),收到信號后做什么(sig_handler)。這些都由前兩個步驟完成了。
回想一下,在實現等待隊列中,我們需要將一個等待隊列wait_queue_t添加到指定的等待隊列頭wait_queue_head_t中。
在這里,同樣需要把一個結構體struct fasync_struct添加到內核的異步隊列頭(名字是我自己取的)中。這個結構體用來存放對應設備文件的信息(如fd, filp)並交給內核來管理。一但收到信號,內核就會在這個所謂的異步隊列頭找到相應的文件(fd),並在filp->owner中找到對應的進程PID,並且調用對應的sig_handler了。
看一下fasync_struct
1097 struct fasync_struct {
1098 int magic;
1099 int fa_fd;
1100 struct fasync_struct *fa_next; /* singly linked list */ //一看就覺得他是鏈表
1101 struct file *fa_file;
1102 };
上面紅色標記說所的步驟都是由內核來完成,我們只要做兩件事情:
1)定義結構體fasync_struct。
struct fasync_struct *async_queue;
2)實現test_fasync,把函數fasync_helper將fd,filp和定義的結構體傳給內核。
108 int test_fasync (int fd, struct file *filp, int mode)
109 {
110 struct _test_t *dev = filp->private_data;
111
112 return fasync_helper(fd, filp, mode, &dev->async_queue);
113 }
講一下函數fasync_helper:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
一看就知道,前面的三個參數其實就是teat_fasync的三個參數,只要我們定義號的fasync_struct結構體也傳進去就可以了。內核會完成我上面紅色自己所說的事情。
另外還有兩件事:
3)當設備可寫時,調用函數kill_fasync發送信號SIGIO給內核。
83 if (dev->async_queue){
84 kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
85 }
講解一下這個函數:
void kill_fasync(struct fasync_struct **fp, int sig, int band)
sig就是我們要發送的信號。
band(帶寬),一般都是使用POLL_IN,表示設備可讀,如果設備可寫,使用POLL_OUT
4)當設備關閉時,需要將fasync_struct從異步隊列中刪除:
117 test_fasync(-1, filp, 0);
刪除也是調用test_fasync,不過改了一下參數而已。
既然說完了就上程序:上面的函數需要包含<linux/fs.h>
/*3rd_char_7/1st/test.c*/
23 struct _test_t{
24 char kbuf[DEV_SIZE];
25 unsigned int major;
26 unsigned int minor;
27 unsigned int cur_size;
28 dev_t devno;
29 struct cdev test_cdev;
30 wait_queue_head_t test_queue;
31 wait_queue_head_t read_queue;
32 wait_queue_head_t write_queue;
33 struct fasync_struct *async_queue; //1.定義結構體
34 };
。。。。省略。。。。
68 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
69 {
70 int ret;
71 struct _test_t *dev = filp->private_data;
72
73 if(copy_from_user(dev->kbuf, buf, count)){
74 ret = - EFAULT;
75 }else{
76 ret = count;
77 dev->cur_size += count;
78 P_DEBUG("write %d bytes, cur_size:[%d]\n", count, dev->cur_size);
79 P_DEBUG("kbuf is [%s]\n", dev->kbuf);
80 wake_up_interruptible(&dev->test_queue);
81 wake_up_interruptible(&dev->read_queue);
82
83 if (dev->async_queue){
84 kill_fasync(&dev->async_queue, SIGIO, POLL_IN); //3.可寫時發送信號
85 }
86 }
87
88 return ret; //返回實際寫入的字節數或錯誤號
89 }
。。。。省略。。。。
108 int test_fasync (int fd, struct file *filp, int mode) //2.實現test_fasync
109 {
110 struct _test_t *dev = filp->private_data;
111
112 return fasync_helper(fd, filp, mode, &dev->async_queue);
113 }
114
115 int test_close(struct inode *node, struct file *filp)
116 {
117 test_fasync(-1, filp, 0); //4文件關閉時將結構體從伊部隊列中刪除
118 return 0;
119 }
120
121 struct file_operations test_fops = {
122 .open = test_open,
123 .release = test_close,
124 .write = test_write,
125 .read = test_read,
126 .poll = test_poll,
127 .fasync = test_fasync, //此步驟切記
128 };
.。。。。。。
程序寫完了就得驗證一下:
[root: app]# insmod ../test.ko
major[253] minor[0]
hello kernel
[root: app]# mknod /dev/test c 253 0
[root: app]# ./monitor& //后台運行monitor
waiting
[root: app]# waiting //不停的打印,沒有休眠
waiting
waiting
waiting
waiting
waiting
[root: app]# ./app_write //調用函數寫數據,
<kernel>[test_write]write 10 bytes, cur_size:[10]
<kernel>[test_write]kbuf is [xiao bai]
<app>s<kernel>[test_read]read data..... //寫完后minoter接收到信號,跳出循環讀數據
<kernel>[test_read]read 10 bytes, cur_size:[0]
ig_handler //這是在sig_hanler里面打印的,本應出現在讀函數之前,因為各個函數搶着打印,所以,出現了亂序,不過不影響驗證。
finish: read[xiao bai]
[1] + Done ./monitor
貼張圖總結一下:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、阻塞型IO、poll和異步通知的區別:
宋寶華書上的圖,描述的挺好的:圖片不態清晰,將就一下。
一個最重要的區別:
1)異步通知是不會造成阻塞的。
2)調用阻塞IO時如果條件不滿足,會在驅動函數中的test_read或test_write中阻塞。
3)如果條件不滿足,selcet會在系統調用中阻塞。
所謂的異步,就是進程可以在信號沒到前干別的事情,等到信號到來了,進程就會被內核通知去做相應的信號操作。進程是不知道信號什么時候來的。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五,總結
今天只是講了異步通知在內核中的實現,並且對應的應用函數和驅動函數需要做什么事情。最后總結了一下阻塞IO、poll和異步通知的區別。