Linux异步IO之fasync简析

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); /* 下一个接收目标 */
				}
	...
			
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,让我来给你讲解一下。fasync是一个用于实现异步通知的系统调用,当进程注册fasync后,当设备接收到一个信号时,内核会向该进程发出SIGIO信号,通知该进程可以进行读写操作了。下面是一个简单的fasync demo: ```c #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/sched.h> #define DEV_NAME "fasync_test" static dev_t devnum; static struct cdev *cdev; static struct file_operations fops; // 实现fasync函数 int my_fasync(int fd, struct file *filp, int mode) { return fasync_helper(fd, filp, mode, &filp->f_async); } // 实现设备的read和write操作 ssize_t fasync_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { pr_info("read called.\n"); return 0; } ssize_t fasync_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { pr_info("write called.\n"); return 0; } // 初始化函数 static int __init fasync_init(void) { // 为设备分配设备号 alloc_chrdev_region(&devnum, 0, 1, DEV_NAME); // 创建一个字符设备 cdev = cdev_alloc(); cdev->ops = &fops; cdev_add(cdev, devnum, 1); // 初始化fops fops.owner = THIS_MODULE; fops.read = fasync_read; fops.write = fasync_write; fops.fasync = my_fasync; return 0; } // 卸载函数 static void __exit fasync_exit(void) { // 删除字符设备 cdev_del(cdev); // 释放设备号 unregister_chrdev_region(devnum, 1); } module_init(fasync_init); module_exit(fasync_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("author"); MODULE_DESCRIPTION("fasync test"); ``` 在该demo中,我们定义了my_fasync函数,用于注册fasync,然后实现了一个简单的设备read和write函数,用于接收用户空间的读写。在初始化函数中,我们使用alloc_chrdev_region函数为设备分配设备号,然后使用cdev_alloc函数创建了一个字符设备,并将fops赋给cdev的ops成员,最后使用cdev_add函数将设备添加到内核中。在fops中,我们使用了我们自己实现的fasync_read和fasync_write函数,并将my_fasync函数赋给了fops的fasync成员。在卸载函数中,我们使用cdev_del函数删除字符设备,并使用unregister_chrdev_region函数释放设备号。 这个demo只是一个简单的演示,但是它可以帮助我们更好地理解fasync的使用方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值