linux设备驱动的异步通知和异步I/O

  1. 信号的接收
  2. 信号的释放
  3. 异步I/O

异步通知是设备一旦就绪,则主动通知应用程序,而不需要应用程序一直查询设备状态,这类似于硬件上“中断”的概念。在linux中提供了阻塞与非阻塞和轮询的设备访问机制,但如果加上异步通知机制,则设备访问的机制将变得更加完善。

异步通知机制不同于轮询机制,它是建立在信号的基础上的。在linux中定义了许多具有特定意义的信号(参考),因此异步通知机制可以说就是linux的信号机制,接下来将针对信号机制进行讨论。

信号的接收

对于用户程序来讲,为了捕捉信号,可以使用signal()函数来设置对应信号的处理函数。

1
2
3
void (*signal(int sig, void (*func)(int)))(int);

int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);

这两个接口都是在应用程序中接受并处理信号所依赖的函数接口。

信号的释放

但对于信号机制来说,只有接受显然是不够的,而信号的释放则是有设备的驱动程序来完成的。在设备驱动程序中增加信号释放的相关代码,使设备具有异步通知机制涉及到3项工作。

  1. 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID
  2. 支持F_SETFL命令的处理,每当FASYNC标志改变时驱动程序的fasync()函数将得以执行。
  3. 在设备资源可用时调用kill_fasync()函数激发相应信号。

因此要完成一个支持异步通知机制的设备驱动,应该实现如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct xxx_dev {
struct cdev cdev;
...
struct fasync_struct *async_queue;
};

static int xxx_fasync(int fd, struct file *filp, int mode)
{
struct xxx_dev *dev = filp->private_data;
return fasync_helper(fd, filp, mode, &dev->async_queue);
}

static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{
struct xxx_dev *dev = filp->private_data;
...
/* 产生异步读信号 */
if (dev->async_queue)
kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}

static int xxx_release(struct inode *inode, struct file *filp)
{
/* 将文件从异步通知列表删除 */
xxx_fasync(-1, filp, 0);
...
return 0;
}

异步I/O

linux中常用的I/O模型是同步I/O,但为了提高CPU和I/O的吞吐率,异步I/O模型逐渐产生。

在linux中,AIO有多种实现,其中一种实现时在用户空间中的glibc库中实现的。它本质上借用了多线程模型,用开启新线程以同步的方法来做I/O,新的AIO辅助线程与发起AIO的线程以条件变量的方式来实现线程间同步。

glibc的aio实现主要提供了如下接口。

1
2
3
4
5
6
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);
int aio_error(struct aiocb *aiocbp);
ssize_t aio_return(struct aiocb *aiocbp);
int aio_suspend(const struct aiocb *const cblist[], int n, const struct timespec *timeout);
int aio_cancel(int fd, struct aiocb *aiocbp);

在用户空间实现AIO的缺点时增加了线程的负载和上下文切换的开销。因此linux 2.6版本以后,AIO已经成为了内核的一个标准特性。

对于内核AIO的调用,在用户空间通常使用libaio来做的。

而对于驱动程序来说,AIO的实现则要实现相应的file_operations中的相关接口。

1
2
3
4
5
6
7
8
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);

旧接口:
ssize_t (*aio_read) (struct kiocb *, struct iov_iter *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, struct iov_iter *, unsigned long, loff_t);
int (*aio_fsync)(struct kiocb *kiocb, int datasync);

AIO一般由内核的通用代码处理,对于块设备和网络设备而言,一般在linux的核心层的代码已经解决。字符设备驱动一般不需要实现AIO支持。linux内核中对字符设备驱动实现AIO的特例包括drivers/char/mem.c里实现的null和zero等,因此对于AIO支持关于驱动的实现可参考之。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:linux设备驱动的异步通知和异步I/O

本文作者:红尘追风

发布时间:2016-07-03, 18:32:53

原始链接:http://www.micernel.com/2016/07/03/linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E7%9A%84%E5%BC%82%E6%AD%A5%E9%80%9A%E7%9F%A5%E5%92%8C%E5%BC%82%E6%AD%A5I_O/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录