异步I/O
异步I/O是POSIX定义的一组标准接口,Linux支持。异步I/O在提交完I/O操作请求后就立即返回,程序不需要等到I/O操作完成后在去做别的事情,具有非阻塞的特性。当底层把I/O操作完成后,可以给提交者发送信号,或调用注册的回调函数,告知请求提交者I/O操作完成。在信号处理函数或回调函数中,可以使用异步I/O接口来获得I/O的完成情况(获取读写操作返回的字节数或错误码、读取的数据等)。
一个简单的针对虚拟串口的异步I/O应用程序部分代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <linux/input.h>
#include <aio.h>#include "vser.h"void aiow_completion_handler(sigval_t sigval)
{int ret;struct aiocb *req;req = (struct aiocb *)sigval.sival_ptr;if (aio_error(req) == 0) {ret = aio_return(req);printf("aio write %d bytes\n", ret);}return;
}void aior_completion_handler(sigval_t sigval)
{int ret;struct aiocb *req;req = (struct aiocb *)sigval.sival_ptr;if (aio_error(req) == 0) {ret = aio_return(req);if (ret)printf("aio read: %s\n", (char *)req->aio_buf);}return;
}int main(int argc, char *argv[])
{int ret;int fd;struct aiocb aiow, aior;fd = open("/dev/vser0", O_RDWR);if (fd == -1) goto fail;memset(&aiow, 0, sizeof(aiow));memset(&aior, 0, sizeof(aior));aiow.aio_fildes = fd;aiow.aio_buf = malloc(32);strcpy((char *)aiow.aio_buf, "aio test");aiow.aio_nbytes = strlen((char *)aiow.aio_buf) + 1;aiow.aio_offset = 0;aiow.aio_sigevent.sigev_notify = SIGEV_THREAD;aiow.aio_sigevent.sigev_notify_function = aiow_completion_handler;aiow.aio_sigevent.sigev_notify_attributes = NULL;aiow.aio_sigevent.sigev_value.sival_ptr = &aiow;aior.aio_fildes = fd;aior.aio_buf = malloc(32);aior.aio_nbytes = 32;aior.aio_offset = 0;aior.aio_sigevent.sigev_notify = SIGEV_THREAD;aior.aio_sigevent.sigev_notify_function = aior_completion_handler;aior.aio_sigevent.sigev_notify_attributes = NULL;aior.aio_sigevent.sigev_value.sival_ptr = &aior;while (1) {if (aio_write(&aiow) == -1)goto fail;if (aio_read(&aior) == -1)goto fail;sleep(1);}fail:perror("aio test");exit(EXIT_FAILURE);
}
定义了两个分别用于读和写的异步I/O控制块,初始化这两个控制块,主要是文件描述符,用于读写的缓冲区,读写的字节数和异步I/O完成后的回调函数。
发起一个异步写操作,该函数会立即返回, 具体的写操作在底层的驱动完成。写完成后,注册的aiow_competion_handler写完成函数将被自动自动调用,该函数通过aio_error及aio_return获取了I/O操作的错误码及实际的写操作的返回值。给sigval.sival_ptr赋值,指向了I/O控制块aiow。
同样,读完成后,注册的aior_completion_handler读完成函数将会被自动调用。
除了像写完成操作中可以获取完成状态,还可以从aio_buf中获取读取的数据。
sleep()是为了模拟其他操作所消耗的时间。再一次异步操作中,可以将多个i/o请求合并,从而完成一系列的读写操作,其对应的接口函数是lio_listio。
异步I/O相关代码:
static ssize_t vser_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{size_t read = 0;unsigned long i;ssize_t ret;for (i = 0; i < nr_segs; i++) {ret = vser_read(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);if (ret < 0)break;read += ret;}return read ? read : -EFAULT;
}static ssize_t vser_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{size_t written = 0;unsigned long i;ssize_t ret;for (i = 0; i < nr_segs; i++) {ret = vser_write(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);if (ret < 0)break;written += ret;}return written ? written : -EFAULT;
}static struct file_operations vser_ops = {....aio_read = vser_aio_read,.aio_write = vser_aio_write,
};
在vser_aio_read函数中,最关键的是调用了之前实现的vser_read函数 ,vser_read函数被调用了nr_segs次,这和分散/聚焦操作是类似的,即一次读操作实际上是分多次进行的,没读取一定的字节数(iov[i].iov_len),然后分别将读到的数据放入分散的内存区域中(iov[i].iov_base)。
异步I/O可以在驱动中阻塞,但上层的操作是非阻塞的。
I/O模型总结
| 阻塞 | 非阻塞 | |
| 同步 | 阻塞I/O | 非阻塞I/O |
| 异步 | I/O多路复用 | 异步I/O |
阻塞I/O:在资源不可用时,进程阻塞,阻塞发生在驱动中,资源可用后进程被唤醒,在阻塞期间不占用CPU(常见)
非阻塞I/O:调用立即返回,即使在资源不可用时,通过返回值来确定I/O操作是否成功,如果不成功,之后继续尝试。对大多数时间内资源都不可用的设备(鼠标键盘),这种尝试将会白白消耗CPU大量的时间,如果将尝试的间隔时间增加,有可能会产生不能及时处理设备的数据。
I/O多路复用:可以同时监听多个设备的状态。如果被监听的所有设备都没有关心的事件产生,那么系统调用被阻塞。当被监听的任意一个设备有对应关心的事件发生,将唤醒系统调用,系统调用将再次遍历所有的被监听的设备,获取其事件信息,然后系统调用返回。之后可以对设备发起非阻塞的读或写操作。
异步I/O:调用这只是发起I/O操作的请求,然后立即返回,程序可以去做别的事情。具体的I/O操作在驱动中完成,驱动中可能被阻塞,也可能不会被阻塞。当驱动的I/O操作完成后,调用者将会得到通知,通常是内核向调用者发送信号,或自动调用调用者注册的回调函数,通知操作是由内核完成的,不是驱动完成的。
异步通知
异步通知类似于异步I/O,只是当设备资源可用时,他是向应用层发送信号,而不能直接调用应用层注册的回调函数,并且发信号的操作也是驱动程序自身来完成的。
前面的I/O模型中,应用程序都是主动获取设备可用资源,即使是异步I/O也要先发起一个I/O操作请求。而异步通知则是当设备资源可获得时,由驱动主动通知应用程序,再由应用程序发起访问。
异步通知的应用程序实现步骤:
- 注册信号处理函数(注册中断处理函数)
- 打开设备文件,设置文件属主(目的是是驱动根据打开文件的file结构,找到相应的进程,从而向该进程发送信号)
- 设置设备资源可用可用时驱动向进程发送的信号(非必须,但要用sigaction的高级特性需要)
- 设置文件的FASYNC标志,使能异步通知机制(相当于中断使能位)
经典的应用程序代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>#include "vser.h"int fd;void sigio_handler(int signum, siginfo_t *siginfo, void *act)
{int ret;char buf[32];if (signum == SIGIO) {if (siginfo->si_band & POLLIN) {printf("FIFO is not empty\n");if ((ret = read(fd, buf, sizeof(buf))) != -1) {buf[ret] = '\0';puts(buf);}}if (siginfo->si_band & POLLOUT)printf("FIFO is not full\n");}
}int main(int argc, char *argv[])
{int ret;int flag;struct sigaction act, oldact;//注册信号处理函数//阻塞了SIGIO,防止信号处理函数的嵌套调用sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, SIGIO);act.sa_flags = SA_SIGINFO;act.sa_sigaction = sigio_handler;if (sigaction(SIGIO, &act, &oldact) == -1)goto fail;//设置文件属主fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);if (fd == -1) goto fail;if (fcntl(fd, F_SETOWN, getpid()) == -1)goto fail;//设置当前资源可用时,向进程发送SIGIO信号if (fcntl(fd, F_SETSIG, SIGIO) == -1)goto fail;//打开异步通知机制if ((flag = fcntl(fd, F_GETFL)) == -1)goto fail;if (fcntl(fd, F_SETFL, flag | FASYNC) == -1)goto fail;while (1)sleep(1);fail:perror("fasync test");exit(EXIT_FAILURE);
}
主函数初始化后一直休眠,等待驱动发来的信号。当进程收到驱动发来的信号后,注册的信号处理函数sigio_handler自动被调用,函数的第一个参数是信号值,第二个参数是信号的附带信息(ID号、发送时间等)。si_band成员将记录资源是可读还是可写,从而进行操作。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>#include <linux/ioctl.h>
#include <linux/uaccess.h>#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/aio.h>#include "vser.h"#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"struct vser_dev {unsigned int baud;struct option opt;struct cdev cdev;wait_queue_head_t rwqh;wait_queue_head_t wwqh;struct fasync_struct *fapp;
};DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;static int vser_fasync(int fd, struct file *filp, int on);static int vser_open(struct inode *inode, struct file *filp)
{return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{vser_fasync(-1, filp, 0);return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_empty(&vsfifo)) {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;if (wait_event_interruptible_exclusive(vsdev.rwqh, !kfifo_is_empty(&vsfifo)))return -ERESTARTSYS;}ret = kfifo_to_user(&vsfifo, buf, count, &copied);if (!kfifo_is_full(&vsfifo)) {wake_up_interruptible(&vsdev.wwqh);kill_fasync(&vsdev.fapp, SIGIO, POLL_OUT);}return ret == 0 ? copied : ret;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{int ret;unsigned int copied = 0;if (kfifo_is_full(&vsfifo)) {if (filp->f_flags & O_NONBLOCK)return -EAGAIN;if (wait_event_interruptible_exclusive(vsdev.wwqh, !kfifo_is_full(&vsfifo)))return -ERESTARTSYS;}ret = kfifo_from_user(&vsfifo, buf, count, &copied);if (!kfifo_is_empty(&vsfifo)) {wake_up_interruptible(&vsdev.rwqh);kill_fasync(&vsdev.fapp, SIGIO, POLL_IN);}return ret == 0 ? copied : ret;
}static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{if (_IOC_TYPE(cmd) != VS_MAGIC)return -ENOTTY;switch (cmd) {case VS_SET_BAUD:vsdev.baud = arg;break;case VS_GET_BAUD:arg = vsdev.baud;break;case VS_SET_FFMT:if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))return -EFAULT;break;case VS_GET_FFMT:if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))return -EFAULT;break;default:return -ENOTTY;}return 0;
}static unsigned int vser_poll(struct file *filp, struct poll_table_struct *p)
{int mask = 0;poll_wait(filp, &vsdev.rwqh, p);poll_wait(filp, &vsdev.wwqh, p);if (!kfifo_is_empty(&vsfifo))mask |= POLLIN | POLLRDNORM;if (!kfifo_is_full(&vsfifo))mask |= POLLOUT | POLLWRNORM;return mask;
}static ssize_t vser_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{size_t read = 0;unsigned long i;ssize_t ret;for (i = 0; i < nr_segs; i++) {ret = vser_read(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);if (ret < 0)break;read += ret;}return read ? read : -EFAULT;
}static ssize_t vser_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)
{size_t written = 0;unsigned long i;ssize_t ret;for (i = 0; i < nr_segs; i++) {ret = vser_write(iocb->ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);if (ret < 0)break;written += ret;}return written ? written : -EFAULT;
}static int vser_fasync(int fd, struct file *filp, int on)
{return fasync_helper(fd, filp, on, &vsdev.fapp);
}static struct file_operations vser_ops = {.owner = THIS_MODULE,.open = vser_open,.release = vser_release,.read = vser_read,.write = vser_write,.unlocked_ioctl = vser_ioctl,.poll = vser_poll,.aio_read = vser_aio_read,.aio_write = vser_aio_write,.fasync = vser_fasync,
};static int __init vser_init(void)
{int ret;dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if (ret)goto reg_err;cdev_init(&vsdev.cdev, &vser_ops);vsdev.cdev.owner = THIS_MODULE;vsdev.baud = 115200;vsdev.opt.datab = 8;vsdev.opt.parity = 0;vsdev.opt.stopb = 1;ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);if (ret)goto add_err;init_waitqueue_head(&vsdev.rwqh);init_waitqueue_head(&vsdev.wwqh);return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);
reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev = MKDEV(VSER_MAJOR, VSER_MINOR);cdev_del(&vsdev.cdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE("GPL");