Linux异步通知之fasync简析

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

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 的内核对象 */
	do_fcntl(fd, cmd, arg, f.file/* @fd 的内核 struct 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_dirver_fasync() 分析

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

static const struct file_operations xxx_dirver_fops = {
	...
	.fasync = xxx_dirver_fasync, 
	...
};

xxx_dirver_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); /* 下一个接收目标 */
				}
	...
			
}
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux