码迷,mamicode.com
首页 > 系统相关 > 详细

Linux之异步通知机制分析

时间:2018-06-24 15:06:53      阅读:222      评论:0      收藏:0      [点我收藏+]

标签:void   file   kill   剖析   mod   unlock   问题   分配   机制   

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-2.6.22.6

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

Linux之异步通知机制分析

标签:void   file   kill   剖析   mod   unlock   问题   分配   机制   

原文地址:https://www.cnblogs.com/hwli/p/9220432.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!