fasync和kill_fasync机制分析
【
相关源码版本:
LINUX内核源码版本:linux-3.0.86
UBOOT版本:uboot-2010.12.
Android系统源码版本:Android-5.0.2】
讨论完了信号的大致原理,下面分析信号在驱动中的一个特定应用场景:
fasync和kill_fasync是内核信号机制在驱动中的一个应用,低层原理是信号收发。让一个驱动一个进程绑定。由于驱动并不知道应用程序的ID,或者说应用程序的ID也并不是固定的。因此为了每次都正确的发送给一个固定应用及进程。则要对信号进行特殊应用。因为信号必须要知道进程ID才能正确的收发。LINUX系统对于每个应用进程号都是随机的每次开机或启动。这样就必须让一个特定驱动代码与特定进程通过每个变量来建立确定的关系。这就是fasync文件异步机制的真实背景。由于这个机制是对信号的应用。因此我们下面还是根据应用层到内核层的调节关系来分析问题。一个最基本的FASYNC框架应该如下:
A.注册fasync
B.发送信号kill_fasync
A.注册fasync
它的作用就是让驱动与一个特定的进程绑定,这样KILL_FASYNC才能知道发给谁。
下面举一个从应用层到驱动的最简单例子:
sigaction(sigio.&sigaction_struct,null) //为当前进程注册一个接收sigio信号的支持回调函数sigaction_struct(实现参见信号框架梗概那节,把回调函数注册到一个当前进程所在的TODO链表中。)
Fcntl(fd,F_SETOWN,getpid())// getpid()=current 让fd与CURRENT关联起来
进入内核的系统调用do_fcntl------f_setown-----__f_setown----
f_modown(filp, pid, type, force)-----------filp->f_owner.pid = get_pid(pid);//这一步是关键这儿的pid和上面注册回调函数得到的结构体是一样的
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
{
filp->f_owner.pid = get_pid(pid);//为应用层利用FD来传给内核驱动时绑定进程号
}
C.
Flags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,flags|FASYNC)进入内核setfl------
filp->f_op->fasync(fd, filp, (arg & FASYNC)【这个函数是驱动指定的】--------
.fasync = int (*fsync) (struct file *, int datasync);此函数中会固定调用
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
这个函数。其中fapp是在驱动中定义的全景变量。它的作用就是用来记录当前进程来操作这个驱动时FD里面指定的进程号current.当条件满足时来给应用层发送信号,调用回调函数。
struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
Fapp>fa_fd = fd;//这条说明我们在驱动中定义的全景变量fasync_struct **fapp中的fa_fd指向应层打开此驱动时得到的FD.这也就是后面发送信号时能找到程号的原因。
经过上面三步完成了信号回调函数的注册和fasync_struct **fapp与进程的绑定。
D:利用全局变量fapp中的进程号来发信号给应用层。
void kill_fasync(struct fasync_struct **fp, int sig, int band)//这儿的FP就是我前面定义 的全局变量fapp。这个是驱动与特定驱动一一关联的纽带。
{
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
}
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
send_sigio(fown, fa->fa_fd, band);//给fa->fd发送信号。
}
void send_sigio(struct fown_struct *fown, int fd, int band)
{
send_sigio_to_task(p, fown, fd, band, group);//唤醒fd中记录的进程。并且执行我们注册的回调函数。
}
do_send_sig_info-》send_signal-》complete_signal-》signal_wake_up(t, sig == SIGKILL);-》kick_process(如果进程休眠唤醒它,会去执行回调函数)/set_tsk_thread_flag(如果进程没有休眠则去设置线程任务标记,可以认为像中断一样设置中断标记,告诉程序可以进入中断程序了。设置好这个标记,系统应该会去主动调用我们的回调函数根据进程current,一一对应的调用。)