点击上方“嵌入式Linux充电站”,选择“置顶/星标公众号”
福利干货,第一时间送达
前言
前几篇介绍了几种IO模型,今天介绍另一种IO模型——异步IO。
相对于前面的几种IO模型,异步IO在提交完IO操作请求后就立即返回,程序不需要等到IO操作完成再去做别的事情,具有非阻塞的特性。
当底层把IO操作完成后,可以给提交者发送信号,或者调用注册的回调函数,告知请求提交者IO操作已完成。
在信号处理函数或者回调函数中,可以使用异步IO接口来获得IO的完成情况,比如获取读写操作返回的字节数或错误码、读取的数据等。
相关接口
struct aiocb
结构体
struct aiocb {
int aio_fildes; /* file descriptor */
off_t aio_offset; /* file offset for I/O */
volatile void *aio_buf; /* buffer for I/O */
size_t aio_nbytes; /* number of bytes tdo transfer */
int aio_reqprio; /* priority */
struct sigevent aio_sigevent; /* signal information */
int aio_lio_opcode; /* operation for list I/O */
};
aio_fildes
:操作的文件描述符
aio_offset
:偏移量,读写操作的起始位置
aio_buf
:读写操作的数据缓冲区
aio_nbytes
:传输数据的字节数
aio_reqprio
:请求权限
aio_lio_opcode
:操作码,表示读还是写,LIO_READ代表读,LIO_WRITE代表写。
struct sigevent
结构体
struct sigevent {
int sigev_notify; /* notify type */
int sigev_signo; /* signal number */
union sigval sigev_value; /* notify argument */
void (*sigev_notify_function)(union sigval); /* notify function */
pthread_attr_t *sigev_notify_attributes; /* notify attrs */
};
sigev_notify
:通知类型。SIGEV_NONE
表示不通知;SIGEV_SIGNAL
表示IO操作完成后,收到sigev_signo
指定的信号;SIGEV_THREAD
表示IO操作完成,内核会创建一个新线程执行一个回调函数,函数由sigev_notify_function
指定
sigev_signo
:指定的信号
sigev_notify_function
:回调函数
sigev_notify_attributes
:使用默认属性,一般设置为NULL
应用层异步IO读写函数:
#include <aio.h>
int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);
//返回值:成功返回0;失败返回-1
获取一个异步读、写或者同步操作的完成状态:
#include <aio.h>
int aio_error(const struct aiocb *aiocb);
调用aio_return
函数,可以用来判断异步IO的执行情况
#include <aio.h>
ssize_t aio_return(const struct aiocb *aiocb);
下面以一个实际例子说明异步IO的用法
应用层
#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>
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.a*io_of f set = 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);
}
1、首先定义两个用于读和写的异步IO控制块struct aiob
2、初始化异步IO控制块,包括文件描述符、用读写的缓冲区、读写的字节数和回调函数
3、发起一个异步操作,调用aio_wirte
或者aio_read
,该函数会立即返回,具体的读写操作会在驱动中完成。
4、读写完成后,对应的回调函数会被自动调用
在写操作回调函数中,通过aio_error
和aio_return
获取了IO操作的错误码及实际的写操作的返回值。
在读操作回调函数中,除了可以获取完成状态,还可以从aio_buf
中获取读取的数据。
注意这里的关键点:1、调用aio_wirte
或者aio_read
,该函数会立即返回,具体的读写操作会在驱动中完成。2、读写完成后,对应的回调函数会被自动调用。
驱动层
驱动中异步IO相关代码如下:
DEFINE_KFIFO(vsfifo, char, 32);
static ssize_t my_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
int ret;
unsigned int copied = 0;
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied : ret;
}
static ssize_t my_write(struct file *flip, const char __user *buf, size_t count, loff_tt *pos)
{
int ret;
unsigned int copied = 0;
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
return ret == 0 ? copied :ret;
}
static ssize_t my_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 reg;
for(i = 0; i < nr_segs; i++) {
ret = my_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 my_aio_write (struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segsf loff_t pos)
{
size_t written = 0;
unsigned longi;
ssize t ret;
for (i = 0;i < nr_segs;i++) {
ret = my_write(iocb->lci_filp, iov [i].iov_base, iov [i].iov_len, &pos);
if(ret < 0)
break;
written += ret;
}
return written ? written : -EFAULT;
}
static struct file_operations my_ops = {
......
.aio_read = my_aio_read,
.aio_write = my_aio_write,
}
从驱动中可以看到,我们分别实现了两种读写函数my_read
、my_write
和my_aio_read
、my_aio_write
。
在一般的读写操作函数实现中,是这样的:
static struct file_operations my_ops = {
......
.read = my_read,
.write = my_write,
}
my_read()
和my_wirte()
函数实际上实现的是struct file_operations
的.read
和.write
接口。
但是在异步IO的实现中,``my_aio_read()和
my_aio_write()分别实现的是
.aio_read和
.aio_write`接口:
static struct file_operations my_ops = {
......
.aio_read = my_aio_read,
.aio_write = my_aio_write,
}
而my_aio_read()
函数又会去调用my_read()
函数,my_aio_write()
函数会去调用my_write()
函数。
所以实际上,异步IO还是要先实现一遍.read
和.write
的接口函数,然后在.aio_read
和.aio_write
的接口实现中,去调用之前实现的.read
和.write
接口。只不过这里的主要区别是,异步IO会多次调用my_read()
和my_write()
函数。
以异步读为例,在 my_aio_read
函数中,最关键的还是调用了my_read
函数,但是my_read
函数被调用了nr_serg
次,这和分散/聚集操作是类似的,即一次读操作实际上是分多次进行的,每次读取一定的字节数(iov[i].iov_len
),然后分别将读到的数据放入分散的内存区域中(iov[i].iov_base
)。
从驱动中不难发现,异步IO可以在驱动中阻塞,但是上层的操作却是非阻塞的。
end
猜你喜欢: