Linux之異步通知機制分析


1.概念:

異步通知機制:一旦設備就緒,則主動通知應用程序,這樣應用程序根本就不需要查詢設備狀態,是一種“信號驅動的異步I/O”。信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一個中斷請求可以說是一樣的。信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什么時候會到達。

2.我們試圖通過兩個方面來分析異步通知機制:

從用戶程序的角度考慮:為了啟動文件的異步通知機制,用戶程序必須執行兩個步驟。首先,他們指定一個進程作為文件的“屬主(owner)”。當進程使用fcntl系統調用執行

F_SETOWN命令時,屬主進程的進程ID號就被保存在filp->f_owner中。這一步是必需的,目的是為了讓內核知道應該通知哪個進程。 然后為了真正啟動異步通知機制,用戶程序還必須

在設備中設置FASYNC標志,這通過fcntl的F_SETFL命令完成的。 執行完這兩個步驟之后,輸入文件就可以在新數據到達時請求發送一個SIGIO信號。該信號被發送到存放在filp-

>f_owner中的進程(如果是負值就是進程組)。

在用戶程序中,為了捕獲信號,可以使用signal()函數來設置對應信號的處理函數:

1 void (*signal(int signum, void (*handler))(int)))(int);

該函數原型較難理解, 它可以分解為:

1 typedef void (*sighandler_t)(int);                        //消息處理函數
2 sighandler_t signal(int signum, sighandler_t handler));   //連接信號與消息處理函數

第一個參數指定信號的值,第二個參數指定針對前面信號值的處理函數,若為SIG_IGN,表示忽略該信號;若為SIG_DFL,表示采用系統默認方式處理信號;若為用戶自定義的函

數,則信號被捕獲到后,該函數將被執行。如果signal()調用成功,它返回最后一次為信號signum綁定的處理函數的handler值,失敗則返回SIG_ERR。

1 fcntl(STDIN_FILENO, F_SETOWN, getpid()); //設置本進程為STDIN_FILENO文件的擁有者,沒有這一步,內核不會知道應該將信號發給哪個進程
2 oflags = fcntl(STDIN_FILENO, F_GETFL);   //獲取設備文件的f_flags
3 fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC); //為了啟用異步通知機制,還需對設備設置FASYNC標志

我們先通過內核源碼,剖析上面的實現原理。

 1 app:fcntl()
 2 kernel:sys_fcntl()
 3             do_fcntl()
 4                  switch (cmd) {
 5                  ……
 6                  case F_GETFL:
 7                      err = filp->f_flags;      //返回文件標志
 8                      break;  
 9                  case F_SETFL:
10                      err = setfl(fd, filp, arg);   //轉調用setfl函數
11                      break;
12                  ……
13                  case F_SETOWN:
14                      err = f_setown(filp, arg, 1);  //轉調用f_setown函數
15                      break;
16                  ……
17                  default:
18                      break;
19                  }
20                  return err;

//來看看f_setown函數的內部實現:設置文件的屬主進程

 1 int f_setown(struct file *filp, unsigned long arg, int force)
 2 {
 3 ...
 4     pid = find_pid(who);                           //獲取當前進程的pid
 5     result = __f_setown(filp, pid, type, force);   //內部主要調用f_modown函數
 6 ...
 7 }
 8 static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,uid_t uid, uid_t euid, int force)
 9 {
10 ...
11     if (force || !filp->f_owner.pid) { //設置對應的pid,uid,euid
12         put_pid(filp->f_owner.pid);
13         filp->f_owner.pid = get_pid(pid);
14         filp->f_owner.pid_type = type;
15         filp->f_owner.uid = uid;
16         filp->f_owner.euid = euid;
17     }
18 ...
19 }

//再來看看setfl函數的內部實現:

 1 static int setfl(int fd, struct file * filp, unsigned long arg)
 2 {
 3 ...
 4     if ((arg ^ filp->f_flags) & FASYNC) {   //也就是說FASYNC標志從0變為1的時候,才為真。
 5         if (filp->f_op && filp->f_op->fasync) {
 6             error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); //調用的就是驅動程序的fasync()函數
 7             if (error < 0)
 8                 goto out;
 9         }
10     }
11 ...
12 }

從驅動程序角度考慮:

應用程序在執行F_SETFL啟用FASYNC時,調用驅動程序的fasync方法。只要filp->f_flags中的FASYNC標識發生了變化,就會調用該方法,以便把這個變化通知驅動程序,使其能

正確響應。文件打開時,FASYNC標志被默認為是清除的。當數據到達時,所有注冊為異步通知的進程都會被發送一個SIGIO信號。

Linux的這種通用方法基於一個數據結構和兩個函數:

1 extern int fasync_helper(int, struct file *, int, struct fasync_struct **);
2 //當一個打開的文件的FASYNC標志被修改時,調用驅動程序的fasync方法間接調用fasync_helper函數以便將當前進程加入到驅動程序的異步通知等待隊列中。
3 extern void kill_fasync(struct fasync_struct **, int, int);
4 //當設備可訪問時,可使用kill_fasync函數發信號所有的相關進程。進程進而調用綁定的消息處理函數。

//分析fasync_helper的內部實現

 1 int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
 2 {
 3     struct fasync_struct *fa, **fp;
 4     struct fasync_struct *new = NULL;
 5     int result = 0;
 6     if (on) {
 7         new = kmem_cache_alloc(fasync_cache, SLAB_KERNEL);//創建對象,slab分配器
 8         if (!new)
 9             return -ENOMEM;
10     }
11     write_lock_irq(&fasync_lock);
12     //遍歷整個異步通知隊列,看是否存在對應的文件指針
13     for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
14         if (fa->fa_file == filp) {//已存在
15             if(on) {
16                 fa->fa_fd = fd;//文件描述符賦值  //注:不明白為什么這里只需要更新文件描述符,而不需要更新文件指針
17                 kmem_cache_free(fasync_cache, new);//銷毀剛創建的對象
18             } else {
19                 *fp = fa->fa_next;//繼續遍歷
20                 kmem_cache_free(fasync_cache, fa);//刪除非目標對象 此用於應用程序屏蔽異步通知.
21                 result = 1;
22             }
23             goto out;//找到了
24         }
25     }
26 //看到下面可以得知,所謂的把進程添加到異步通知隊列中
27 //實則是將文件指針關聯到異步結構體對象,然后將該對象掛載在異步通知隊列中(等待隊列也是這個原理)
28 //那么最后發送信號又是怎么知道是哪個進程的呢?我們看后面的kill_fasync函數。
29     if (on) {//不存在
30         new->magic = FASYNC_MAGIC;
31         new->fa_file = filp;//指定文件指針
32         new->fa_fd = fd;//指定文件描述符
33         new->fa_next = *fapp;//掛載在異步通知隊列中
34         *fapp = new;//掛載
35         result = 1;
36     }
37 out:
38     write_unlock_irq(&fasync_lock);
39     return result;
40 }

//看看kill_fasync函數是怎么將信號通知指定進程的:

 1 void __kill_fasync(struct fasync_struct *fa, int sig, int band)
 2 {
 3     while (fa) {
 4         ...
 5         fown = &fa->fa_file->f_owner;//這里便是回答上面的問題,如果知道是哪個進程的,通過異步對象的文件指針知道其屬主進程
 6         /* Don't send SIGURG to processes which have not set a queued signum: SIGURG has its own default signallingmechanism. */
 7         if (!(sig == SIGURG && fown->signum == 0))
 8             send_sigio(fown, fa->fa_fd, band);//發送信號
 9         fa = fa->fa_next;
10         ...
11     }
12 }

總結:應用程序使用fcntl()設置當前進程的pid和FASYNC標志。進而調用驅動程序的fasync(),即fasync_helper()。然后申請和設置fasync_struct結構,將此結構掛載到驅動程序

的fasync_struct結構鏈表中。當設備可用時,驅動程序會使用kill_fasync(),從fasync_struct鏈表中,查找所有的等待進程,然后調用send_sigio發送相應的消息給進程。進程接收到

消息,就會跳轉到與消息綁定的消息處理函數中。

 

實例:基於<<Linux設備驅動開發詳解:基於最新的Linux4.0內核.pdf>>第9.3章節

驅動程序源碼.

  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/uaccess.h>
  7 #include <linux/poll.h>
  8 
  9 #define GLOBALMEM_SIZE 0x1000
 10 //#define GLOBALMEM_SIZE 0x10
 11 #define GLOBALMEM_MAJOR 230
 12 #define GLOBALMEM_MAGIC 'g'
 13 //#define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0)
 14 #define MEM_CLEAR (0x01)
 15 static int globalfifo_major = GLOBALMEM_MAJOR;
 16 module_param(globalfifo_major, int, S_IRUGO);
 17 
 18 struct globalfifo_dev {
 19  struct cdev cdev;
 20  unsigned int current_len;
 21  unsigned char mem[GLOBALMEM_SIZE];
 22  struct mutex mutex;
 23  wait_queue_head_t r_wait;
 24  wait_queue_head_t w_wait;
 25  struct fasync_struct *queue;
 26 };
 27 
 28 struct globalfifo_dev *globalfifo_devp;
 29 
 30 static int globalfifo_open(struct inode *inode, struct file *filp)
 31 {
 32     filp->private_data = globalfifo_devp;
 33     return 0;
 34 }
 35 
 36 static ssize_t globalfifo_read(struct file *filp, char __user * buf, size_t size,
 37  loff_t * ppos)
 38 {
 39     unsigned int count = size;
 40     int ret = 0;
 41     struct globalfifo_dev *dev = filp->private_data;
 42     DECLARE_WAITQUEUE(wait, current);
 43 
 44     mutex_lock(&dev->mutex);
 45     add_wait_queue(&dev->r_wait, &wait);
 46 
 47     while(dev->current_len ==0){
 48         if(filp->f_flags & O_NONBLOCK){
 49             ret = -EAGAIN;
 50             goto out;
 51         }
 52 
 53         set_current_state(TASK_INTERRUPTIBLE);
 54         mutex_unlock(&dev->mutex);
 55 
 56         schedule();
 57         if(signal_pending(current)){
 58             ret = -ERESTARTSYS;
 59             goto out2;
 60         }
 61         mutex_lock(&dev->mutex);
 62 
 63     }
 64 
 65     if (count > dev->current_len)
 66         count = dev->current_len;
 67 
 68     if (copy_to_user(buf, dev->mem, count)) {
 69         ret = -EFAULT;
 70         goto out;
 71     } else {
 72         memcpy(dev->mem, dev->mem+count, dev->current_len - count);
 73         dev->current_len -=count;
 74         printk(KERN_INFO "read %d bytes(s) current_len %d\n", count, dev->current_len);
 75         wake_up_interruptible(&dev->w_wait);
 76 
 77         if(dev->queue){
 78             kill_fasync(&dev->queue, SIGIO, POLL_OUT);
 79             printk(KERN_DEBUG "%s kill SIGIO\n", __func__);
 80         }
 81         ret = count;
 82     }
 83 out:
 84     mutex_unlock(&dev->mutex);
 85 out2:
 86     remove_wait_queue(&dev->r_wait, &wait);
 87     set_current_state(TASK_RUNNING);
 88 
 89  return ret;
 90 }
 91 
 92 static ssize_t globalfifo_write(struct file *filp, const char __user * buf,
 93  size_t size, loff_t * ppos)
 94 {
 95     unsigned int count = size;
 96     int ret = 0;
 97     struct globalfifo_dev *dev = filp->private_data;
 98     DECLARE_WAITQUEUE(wait, current);
 99 
100     mutex_lock(&dev->mutex);
101     add_wait_queue(&dev->w_wait, &wait);
102 
103     while(dev->current_len == GLOBALMEM_SIZE){
104         if(filp->f_flags & O_NONBLOCK){
105             ret = -EAGAIN;
106             goto out;
107         }
108 
109         set_current_state(TASK_INTERRUPTIBLE);
110         mutex_unlock(&dev->mutex);
111         schedule();
112         if(signal_pending(current)){
113             ret = -ERESTARTSYS;
114             goto out2;
115         }
116         mutex_lock(&dev->mutex);
117 
118     }
119 
120     if (count > (GLOBALMEM_SIZE - dev->current_len))
121         count = (GLOBALMEM_SIZE - dev->current_len);
122 
123     if (copy_from_user(dev->mem + dev->current_len, buf, count)){
124         ret = -EFAULT;
125         goto out;
126     }
127     else {
128         dev->current_len += count;
129         wake_up_interruptible(&dev->r_wait);
130         ret = count;
131         printk(KERN_INFO "written %d bytes(s) current_len %d\n", count, dev->current_len);
132 
133         if(dev->queue){
134             kill_fasync(&dev->queue, SIGIO, POLL_IN);
135             printk(KERN_DEBUG "%s kill SIGIO\n", __func__);
136         }
137     }
138 out:
139     mutex_unlock(&dev->mutex);
140 out2:
141     remove_wait_queue(&dev->w_wait, &wait);
142     set_current_state(TASK_RUNNING);
143  return ret;
144 }
145 static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
146 {
147     loff_t ret = 0;
148     switch (orig) {
149     case 0: /* ´ÓÎļþ¿ªÍ·Î»ÖÃseek */
150     if (offset< 0) {
151         ret = -EINVAL;
152         break;
153     }
154     if ((unsigned int)offset > GLOBALMEM_SIZE) {
155         ret = -EINVAL;
156         break;
157     }
158     filp->f_pos = (unsigned int)offset;
159     ret = filp->f_pos;
160     break;
161     case 1: /* ´ÓÎļþµ±Ç°Î»ÖÿªÊ¼seek */
162     if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
163         ret = -EINVAL;
164         break;
165     }
166     if ((filp->f_pos + offset) < 0) {
167         ret = -EINVAL;
168         break;
169     }
170     filp->f_pos += offset;
171     ret = filp->f_pos;
172     break;
173     default:
174     ret = -EINVAL;
175     break;
176     }
177     return ret;
178 }
179 static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
180  unsigned long arg)
181 {
182     struct globalfifo_dev *dev = filp->private_data;
183     switch (cmd) {
184     case MEM_CLEAR:
185         mutex_lock(&dev->mutex);
186         memset(dev->mem, 0, GLOBALMEM_SIZE);
187     dev->current_len =0;
188         printk(KERN_INFO "globalfifo is set to zero\n");
189         mutex_unlock(&dev->mutex);
190         break;
191     default:
192     return -EINVAL;
193  }
194 
195  return 0;
196 }
197 static unsigned int globalfifo_poll(struct file * filp, poll_table * wait)
198 {
199     unsigned int mask =0;
200     struct globalfifo_dev *dev = filp->private_data;
201 
202     mutex_lock(&dev->mutex);
203 
204     poll_wait(filp, &dev->w_wait, wait);
205     poll_wait(filp, &dev->r_wait, wait);
206 
207     if(dev->current_len != 0)
208         mask |=POLLIN | POLLRDNORM;
209     if(dev->current_len != GLOBALMEM_SIZE)
210         mask |=POLLOUT | POLLWRNORM;
211 
212     mutex_unlock(&dev->mutex);
213     return mask;
214 }
215 
216 static int globalfifo_fasync(int fd, struct file *filp, int on)
217 {
218     struct globalfifo_dev *dev = filp->private_data;
219 
220     return fasync_helper(fd, filp, on, &dev->queue);
221 }
222 
223 static int globalfifo_release(struct inode *inode, struct file *filp)
224 {
225     globalfifo_fasync(-1, filp, 0);
226     return 0;
227 }
228 static const struct file_operations globalfifo_fops = {
229     .owner          = THIS_MODULE,
230     .llseek         = globalfifo_llseek,
231     .read           = globalfifo_read,
232     .write          = globalfifo_write,
233     .unlocked_ioctl = globalfifo_ioctl,
234     .open           = globalfifo_open,
235     .poll           = globalfifo_poll,
236     .release        = globalfifo_release,
237     .fasync         = globalfifo_fasync,
238 };
239 static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
240 {
241     int err, devno = MKDEV(globalfifo_major, index);
242     cdev_init(&dev->cdev, &globalfifo_fops);
243     dev->cdev.owner = THIS_MODULE;
244     err = cdev_add(&dev->cdev, devno, 1);
245     if (err)
246     printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index);
247 }
248 static int __init globalfifo_init(void)
249 {
250     int ret;
251     dev_t devno = MKDEV(globalfifo_major, 0);
252 
253     if (globalfifo_major)
254     ret = register_chrdev_region(devno, 1, "globalfifo");
255     else {
256     ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
257     globalfifo_major = MAJOR(devno);
258     }
259     if (ret < 0)
260     return ret;
261 
262     globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
263     if (!globalfifo_devp) {
264     ret = -ENOMEM;
265     goto fail_malloc;
266     }
267     globalfifo_setup_cdev(globalfifo_devp, 0);
268 
269     mutex_init(&globalfifo_devp->mutex);
270     init_waitqueue_head(&globalfifo_devp->r_wait);
271     init_waitqueue_head(&globalfifo_devp->w_wait);
272     return 0;
273 
274 fail_malloc:
275     unregister_chrdev_region(devno, 1);
276     return ret;
277 }
278 
279 static void __exit globalfifo_exit(void)
280 {
281     cdev_del(&globalfifo_devp->cdev);
282     kfree(globalfifo_devp);
283     unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
284 }
285 module_init(globalfifo_init);
286 module_exit(globalfifo_exit);
287 
288 MODULE_LICENSE("GPL v2");
View Code

應用程序源碼.

 1 #include <stdio.h>
 2 #include <fcntl.h>
 3 #include <errno.h>
 4 #include <signal.h>
 5 #include <sys/types.h>
 6 #include <unistd.h>
 7 
 8 
 9 void signal_handler(int signalNum)
10 {
11     printf("signalNum:0x%x\n",signalNum);
12 }
13 
14 int main(int argc, char **argv)
15 {
16     int fd;
17     int err;
18 
19     fd = open("/dev/globalfifo", O_RDONLY);
20     if(fd == -1)
21         printf("open fail\n");
22     else{
23         signal(SIGIO, signal_handler);
24         fcntl(fd, F_SETOWN, getpid());
25         int oflags = fcntl(fd, F_GETFL);
26         fcntl(fd, F_SETFL, oflags | FASYNC);
27         while(1)
28             sleep(100);
29     }
30     return 0;
31 }
View Code

結果:

當echo 1 >/dev/globalfifo時

 

此文源碼基於內核源碼版本為linux-2.6.22.6

參考:https://www.cnblogs.com/tureno/articles/6059711.html


免責聲明!

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



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