学习linux 驱动异步机制 fasync时,看了下内核源码梳理了异步fasync实现原理,梳理了一下和应用层手动发送信号函数kill的联系(之前一直困惑为什么fasync需要和fd配合使用才能实现异步,毕竟只使用函数signal也能异步通知)。
注:内核版本linux-2.6.22
关于驱动里的异步fasync使用步骤如下:
一.注册信号处理函数
二.驱动层支持异步通知
三.设定信号发送的目标进程
四.发送信号给进程(对于驱动是在中断函数进行发送)
一.注册信号处理函数
即应用层编程调用函数signal,注册信号对应的处理函数。
二.驱动层支持异步通知
在应用层调用fcntl(fd, F_SETFL, flag | FASYNC)。内核调用栈如下
sys_fcntl
err = do_fcntl(fd, cmd, arg, filp);
err = setfl(fd, filp, arg);
static int setfl(int fd, struct file * filp, unsigned long arg)
{
...
if ((arg ^ filp->f_flags) & FASYNC) {
if (filp->f_op && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
if (error < 0)
goto out;
}
}
...
}
可以看到最终在函数setfl调用驱动的fasync函数来支持异步通知,这里为什么说“调用驱动函数的fasync函数”就是“支持异步通知”呢?这就要看一下驱动函数的fasync函数是如何实现的了,通常驱动函数如下写:
static int xxx_fasync(int fd, struct file *file, int on)
{
...
return fasync_helper(fd, file, on, &device->fasync);
}
驱动函数的fasync函数调用函数fasync_helper,这个函数会申请一个struct fasync_struct类型的变量,这个变量里面记录了信号要发给哪个进程。实际上这个变量会记录一个文件结构体指针,指向fd所表示的文件结构体,而文件结构体中的f_owner字段才记录了进程的pid。当然,我们需要先调用fcntl(fd, F_SETOWN, getpid()),这个函数会设置f_owner字段。
fs/fcntl.c
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
struct fasync_struct *fa, **fp;
struct fasync_struct *new = NULL;
int result = 0;
if (on) {
new = kmem_cache_alloc(fasync_cache, GFP_KERNEL);
if (!new)
return -ENOMEM;
}
write_lock_irq(&fasync_lock);
for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
if (fa->fa_file == filp) {
if(on) {
fa->fa_fd = fd;
kmem_cache_free(fasync_cache, new);
} else {
*fp = fa->fa_next;
kmem_cache_free(fasync_cache, fa);
result = 1;
}
goto out;
}
}
if (on) {
new->magic = FASYNC_MAGIC;
new->fa_file = filp;
new->fa_fd = fd;
new->fa_next = *fapp;
*fapp = new;
result = 1;
}
out:
write_unlock_irq(&fasync_lock);
return result;
}
include/linux/fs.h
struct fasync_struct {
int magic;
int fa_fd;
struct fasync_struct *fa_next; /* singly linked list */
struct file *fa_file;
};
三.设定信号发送的目标进程
调用函数fcntl指定fd对应的filep->f_owner为信号要发送的目标进程调用过程:
sys_fcntl
err = do_fcntl(fd, cmd, arg, filp);
err = f_setown(filp, arg, 1);
result = __f_setown(filp, pid, type, force);
f_modown(filp, pid, type, current->uid, current->euid, force);
static void f_modown(struct file *filp, struct pid *pid, enum pid_type type,
uid_t uid, uid_t euid, int force)
{
write_lock_irq(&filp->f_owner.lock);
if (force || !filp->f_owner.pid) {
put_pid(filp->f_owner.pid);
filp->f_owner.pid = get_pid(pid);
filp->f_owner.pid_type = type;
filp->f_owner.uid = uid;
filp->f_owner.euid = euid;
}
write_unlock_irq(&filp->f_owner.lock);
}
总结一下:应用层调用fcntl(fd, F_SETFL, flag|faync)时,内核先检查该文件是否有对应的结构体struct fasync_struct,没有的话重新分配一个,放入链表fapp中;接着应用层调用fcntl(fd, F_SETOWN, getpid()) ,内核会在文件对应的结构体struct fasync_struct中记录信号发送的目标进程。
四.发送信号给进程(对于驱动是在中断函数进行发送)
驱动通常是在中断函数中调用函数kill_fasync发送信号,该函数第一个参数就是链表fapp。
static irqreturn_t xxx_irq (int irq, void *dev_id)
{
...
kill_fasync(&device->fasync, SIGIO, POLL_IN);
return IRQ_RETVAL(IRQ_HANDLED);
}
可以看到会遍历链表fapp,获取进程pid,调用函数send_sigio逐一发送信号给进程。
kill_fasync(&dev_async, SIGIO, POLL_IN);
__kill_fasync(*fp, sig, band);
fs/fcntl.c
void __kill_fasync(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct * fown;
if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in "
"fasync_struct!\n");
return;
}
fown = &fa->fa_file->f_owner;
/* Don't send SIGURG to processes which have not set a
queued signum: SIGURG has its own default signalling
mechanism. */
if (!(sig == SIGURG && fown->signum == 0))
send_sigio(fown, fa->fa_fd, band);
fa = fa->fa_next;
}
}
至此驱动fasync机制梳理完毕,那么和单独使用signal函数又有什么不一样呢?类似下面的代码
#include <stdio.h>
#include <signal.h>
void my_signal(int signum)
{
printf("signal trigered\r\n");
}
int main(int argc,char **argv)
{
signal(SIGUSR1,my_signal);
while(true)
{
}
return 0;
}
个人理解fcntl+signal相比单独signal多了一层指定,各自驱动的文件描述符fd指定对应的进程pid,让fd记录pid区别于kill 的手动指定或者默认指定pid。
kill和kill_fasync有什么不一样呢?
sys_kill(int pid, int sig)
return kill_something_info(sig, &info, pid);
ret = kill_pid_info(sig, info, find_pid(pid));
error = group_send_sig_info(sig, info, p);
ret = __group_send_sig_info(sig, info, p);
ret = send_signal(sig, info, p, &p->signal->shared_pending);
__group_complete_signal(sig, p);
signal_wake_up(t, 0);
set_tsk_thread_flag(t, TIF_SIGPENDING);
kill_fasync(struct fasync_struct **fp, int sig, int band)
__kill_fasync(*fp, sig, band);
send_sigio(fown, fa->fa_fd, band);
send_sigio_to_task(p, fown, fd, band);
if (!group_send_sig_info(fown->signum, &si, p))
ret = __group_send_sig_info(sig, info, p);
ret = send_signal(sig, info, p, &p->signal->shared_pending);
__group_complete_signal(sig, p);
signal_wake_up(t, 0);
set_tsk_thread_flag(t, TIF_SIGPENDING);
通过追踪各自在内核的调用栈可以发现出现了相同的调用函数group_send_sig_info(),之后逻辑也就是在signal_wake_up()函数中设置_TIF_SIGPENDING标志,然后系统调用返回时检查进程是否要做信号处理、进程调度等。
代码流程如下:
设置系统调用后的返回地址为ret_fast_syscall;在ret_fast_syscall中检查进程是否需要做额外的事情;假设有信号pending调用do_notify_resume做信号处理执行接受信号对应的函数。
arch/arm/kernel/entry-common.S
ENTRY(vector_swi)
...
adr lr, ret_fast_syscall @ return address
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
...
ret_fast_syscall:
disable_irq @ disable interrupts
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne fast_work_pending
...
fast_work_pending:
...
tst r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING
beq no_work_pending
mov r0, sp @ 'regs'
mov r2, why @ 'syscall'
bl do_notify_resume
...
arch/arm/kernel/signal.c
asmlinkage void
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
if (thread_flags & _TIF_SIGPENDING)
do_signal(¤t->blocked, regs, syscall);
}