Linux异步IO之fasync简析

fasync是Linux4.14内核提供的异步IO机制,允许用户应用程序通过注册SIGIO信号处理函数,当设备驱动通过kill_fasync()在事件发生时发送SIGIO信号,通知进程。这一机制使得进程无需阻塞等待,提高了效率。文章通过代码示例和内核源码分析,详细阐述了fasync的使用和实现过程,包括预订设备事件信号、将进程添加到等待队列以及驱动发送设备事件信号的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. 分析背景

本文基于 Linux 4.14 内核源码进行分析。

3. 什么是 fasync ?

fasync 是 Linux 提供的一种异步IO机制。用户侧的应用程序通过信号 SIGIO,预订设备驱动的异步IO事件:当设备的特定事件来临,设备驱动通过 kill_fasync() 将该事件来临的信号,发送给预订了 SIGIO 信号的进程。这样的实现,进程可以不必一直阻塞等待某个事件的来临,内核驱动会在事件来临时,以信号方式这种异步方式,通知进程。

4. fasync 的典型用法

我们用下面的代码片段来演示用户侧应用 fasync 的典型用法:

/* (1) 设定 SIGIO 信号处理函数 */
signal(SIGIO, signal_io_handler);

/* (2) 打开驱动设备文件 */
int fd = open("/dev/xxx-device", ...);

/* (3) 将进程设定为设备事件接收进程: 
 * 因为 fasync 是通过信号处理,而信号的发送是要有接收目标进程
 * 的。
 * 这里的目的是将当前进程,设定为 fd 指向设备的事件的接收目标:
 * 当设备驱动通过 kill_fasync() 发送设备事件信号时,当前进程才
 * 能接收到信号,步骤 (1) 里面注册的信号处理函数才会被触发。
 */
fcntl(fd, F_SETOWN, getpid());

/* (4) 将进程添加到设备驱动 fasync 事件等待队列 */
oflags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(fd,  F_SETFL, oflags | FASYNC);		

5. fasync 的实现分析

5.1 进程预订设备驱动事件信号

/* 设定设备 SIGIO 信号处理函数 */
signal(SIGIO, signal_io_handler);

/* (2) 打开驱动设备文件 */
int fd = open("/dev/xxx-device", ...);

/* (3) 将进程设定为设备事件接收进程 */
sys_fcntl(fd, F_SETOWN, getpid())
	struct fd f = fdget_raw(fd); /* 获取 @fd 的内核 struct file 对象 */
	do_fcntl(fd, cmd, arg, f.file)
		switch (cmd) {
		...
		case F_SETOWN:
			err = f_setown(filp, arg, 1)
				int who = arg;
				pid = find_vpid(who);
				__f_setown(filp, pid, type, force)
					f_modown(filp, pid, type, force)
						/* 将进程设定为 @filp 指向的设备对象事件的接收目标 */
						filp->f_owner.pid = get_pid(pid);
						filp->f_owner.pid_type = type;
						if (pid) {
							const struct cred *cred = current_cred();
							filp->f_owner.uid = cred->uid;
							filp->f_owner.euid = cred->euid;
						}
			break;
		...
		}

/* (4) 将进程添加到设备驱动 fasync 事件等待队列 */
oflags = fcntl(STDIN_FILENO, F_GETFL);
sys_fcntl(fd,  F_SETFL, oflags | FASYNC)
	do_fcntl(fd, cmd, arg, f.file)
		switch (cmd) {
		...
		case F_SETFL:
			err = setfl(fd, filp, arg)
				if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
					/* 调动设备驱动的 file.f_op.fasync 接口 xxx_dirver_fasync() */
					filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0)
						xxx_dirver_fasync(fd, filp, (arg & FASYNC) != 0)
							/* 
							 * 将进程添加到驱动设备事件信号的接收队列。
							 * 后续细节参考后面的分析。
							 */
				}
			break;
		...
		}

上接 xxx_driver_fasync() 分析:

/*
 * 某设备驱动代码片段。
 */
struct xxx_device_data {
	...
	struct fasync_struct *async_queue; /* 预订设备事件信号的进程列表 */
	...
} xxx_device;

static const struct file_operations xxx_driver_fops = {
	...
	.fasync = xxx_driver_fasync, 
	...
};

xxx_driver_fasync(fd, filp, (arg & FASYNC) != 0)
	/*
	 * fasync_helper() 建立 fasync 队列:
	 *
	 *   struct fasync_struct        struct fasync_struct
	 *  ---------------------       ---------------------
	 * | ...                 |     | ...                 |
	 * |---------------------|     |---------------------|
	 * | fa_fd               |     | fa_fd               |
	 * |---------------------|     |---------------------|
	 * | fa_next             | --> | fa_next             | --> ...
	 * |---------------------|     |---------------------|
	 * | fa_file             |     | fa_file             |
	 * |---------------------|     |---------------------|
	 * | ...                 |     | ...                 |
	 *  ---------------------       ---------------------
	 *
	 * 其中:
	 * . fa_fd 是打开的设备文件描述符;
	 * . fa_file 是打开设备的文件结构,同时用于关联一个信号接收目标进程:
	 *   通过 fcntl(fd, F_SETOWN, getpid()) 设定。
	 */
	fasync_helper(fd, filp, on, &xxx_device.async_queue)
		fasync_add_entry(fd, filp, fapp)
			/* 分配1个队列节点 */
			struct fasync_struct *new = fasync_alloc();
			/* 将进程添加到信号接收队列 */
			fasync_insert_entry(fd, filp, fapp, new)
				...
				new->magic = FASYNC_MAGIC;
				new->fa_file = filp; /* 设备文件内核对象 */
				new->fa_fd = fd; /* 设备文件对象句柄 */
				new->fa_next = *fapp;
				rcu_assign_pointer(*fapp, new); /* 新节点放入队首 */
				filp->f_flags |= FASYNC;

5.2 驱动发送设备事件信号

当设备的事件到达时,向章节 5.1 进程预订设备驱动事件信号 中,预订设备事件信号的进程发送信号。事件通常从设备驱动的中断中采集:

/*
 * 某设备驱动代码片段。
 */
 
static irqreturn_t xxx_driver_irq(int irq, void *dev_id)
{
	...
	/* 数据准备好了 或 事件来临了,发信号 SIGIO 通知接收进程 */
	kill_fasync(&sonypi_device.fifo_async, SIGIO, POLL_IN)
			kill_fasync_rcu(rcu_dereference(*fp), sig, band)
				while (fa) {
					struct fown_struct *fown;
					if (fa->fa_file) {
						fown = &fa->fa_file->f_owner;
						if (!(sig == SIGURG && fown->signum == 0))
							/* 发送 SIGIO 信号给进程 */
							send_sigio(fown, fa->fa_fd, band);
					}
					fa = rcu_dereference(fa->fa_next); /* 下一个接收目标 */
				}
	...
			
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值