linux 异步机制

学习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(&current->blocked, regs, syscall);
}

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值