Linux異步通知fasync


本文轉載於CSDN博客:轉載請注明原文地址http://blog.csdn.net/billowszpt/article/details/7184302

linux設備驅動歸納總結(三):7.異步通知fasync

 

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

異步通知fasync是應用於系統調用signalsigaction函數,下面我會使用signal函數。簡單的說,signal函數就是讓一個信號與與一個函數對應,沒當接收到這個信號就會調用相應的函數。

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

一、什么是異步通知

 

個人認為,異步通知類似於中斷的機制,如下面的將要舉例的程序,當設備可寫時,設備驅動函數發送一個信號給內核,告知內核有數據可讀,在條件不滿足之前,並不會造成阻塞。而不像之前學的阻塞型IOpoll它們是調用函數進去檢查,條件不滿足時還會造成阻塞

 

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

二、應用層中啟用異步通知機制

 

其實就三個步驟:

1signal(SIGIO, sig_handler);

調用signal函數,讓指定的信號SIGIO與處理函數sig_handler對應。

2fcntl(fd, F_SET_OWNER, getpid());

指定一個進程作為文件的“屬主(filp->owner)”,這樣內核才知道信號要發給哪個進程。

3f_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_helperfd,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

 

四、阻塞型IOpoll和異步通知的區別:

 

宋寶華書上的圖,描述的挺好的:圖片不態清晰,將就一下。

一個最重要的區別:

1)異步通知是不會造成阻塞的。

2)調用阻塞IO時如果條件不滿足,會在驅動函數中的test_readtest_write中阻塞。

3)如果條件不滿足,selcet會在系統調用中阻塞。

 

所謂的異步,就是進程可以在信號沒到前干別的事情,等到信號到來了,進程就會被內核通知去做相應的信號操作。進程是不知道信號什么時候來的。

 

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

五,總結

 

今天只是講了異步通知在內核中的實現,並且對應的應用函數和驅動函數需要做什么事情。最后總結了一下阻塞IOpoll和異步通知的區別。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM