一文带你彻底搞懂linux的异步IO
Linux开发架构之路 2023-07-04

一、简介

1.1 POSIX AIO

POSIX AIO是一个用户级实现,它在多个线程中执行正常的阻塞I/O,因此给出了I/O异步的错觉.这样做的主要原因是:

  • 它适用于任何文件系统

  • 它(基本上)在任何操作系统上工作(请记住,gnu的libc是可移植的)

  • 它适用于启用了缓冲的文件(即没有设置O_DIRECT标志)

主要缺点是你的队列深度(即你在实践中可以拥有的未完成操作的数量)受到你选择的线程数量的限制,这也意味着一个磁盘上的慢速操作可能会阻止一个操作进入不同的磁盘.它还会影响内核和磁盘调度程序看到的I/O(或多少)。

1.2 libaio

内核AIO(即io_submit()et.al.)是异步I/O操作的内核支持,其中io请求实际上在内核中排队,按照您拥有的任何磁盘调度程序排序,可能是其中一些被转发(我们希望将实际的磁盘作为异步操作(使用TCQ或NCQ)。这种方法的主要限制是,并非所有文件系统都能很好地工作,或者根本不能使用异步I/O(并且可能会回到阻塞语义),因此必须使用O_DIRECT打开文件,而O_DIRECT还带有许多其他限制。。I/O请求。如果您无法使用O_DIRECT打开文件,它可能仍然"正常",就像您获得正确的数据一样,但它可能不是异步完成,而是回到阻止语义。

还要记住,在某些情况下,io_submit()实际上可以阻塞磁盘。

没有aio_*系统调用(http://linux.die.net/man/2/syscalls)。您在vfs中看到的aio_*函数可能是内核aio的一部分。用户级*aio_*函数不会将1:1映射到系统调用。

二、POSIX AIO(用户层级)

2.1 异步IO基本API


2.2 重要结构体

上述API调用都会用到 struct aiocb 结构体:

1 struct aiocb { 2 int aio_fildes; //文件描述符 3 off_t aio_offset; //文件偏移量 4 volatile void *aio_buf; //缓冲区 5 size_t aio_nbytes; //数据长度 6 int aio_reqprio; //请求优先级 7 struct sigevent aio_sigevent; //通知方式 8 int aio_lio_opcode; //要执行的操作 9 };

2.3 使用时注意事项

编译时加参数 -lrt

2.4 aio_error

2.4.1 功能

检查异步请求状态

2.4.2 函数原型

int aio_error(const struct aiocb *aiocbp);

2.4.3 返回值


2.5 aio_read

2.5.1 功能

  异步读操作,aio_read函数请求对一个文件进行读操作,所请求文件对应的文件描述符可以是文件,套接字,甚至管道。

2.5.2 函数原型

int aio_error(const struct aiocb *aiocbp);

2.5.3 返回值

该函数请求对文件进行异步读操作,若请求失败返回-1,成功则返回0,并将该请求进行排队,然后就开始对文件的异步读操作。需要注意的是,我们得先对aiocb结构体进行必要的初始化。

2.5.4 使用示例

2.5.4.1 代码

特别提醒在编译上述程序时必须在编译时再加一个-lrt,如gcc test.c -o test -lrt。

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 14 15 #define BUFFER_SIZE 1024 16 17 int MAX_LIST = 2; 18 19 int main(int argc,char **argv) 20 { 21 //aio操作所需结构体 22 struct aiocb rd; 23 24 int fd,ret,couter; 25 26 fd = open("test.txt",O_RDONLY); 27 if(fd < 0) 28 { 29 perror("test.txt"); 30 } 31 32 33 34 //将rd结构体清空 35 bzero(&rd,sizeof(rd)); 36 37 38 //为rd.aio_buf分配空间 39 rd.aio_buf = malloc(BUFFER_SIZE + 1); 40 41 //填充rd结构体 42 rd.aio_fildes = fd; 43 rd.aio_nbytes = BUFFER_SIZE; 44 rd.aio_offset = 0; 45 46 //进行异步读操作 47 ret = aio_read(&rd); 48 if(ret < 0) 49 { 50 perror("aio_read"); 51 exit(1); 52 } 53 54 couter = 0; 55 // 循环等待异步读操作结束 56 while(aio_error(&rd) == EINPROGRESS) 57 { 58 printf("第%d次\n",++couter); 59 } 60 //获取异步读返回值 61 ret = aio_return(&rd); 62 63 printf("\n\n返回值为:%d",ret); 64 65 66 return 0; 67 }

2.5.4.2 代码分析

上述实例中aiocb结构体用来表示某一次特定的读写操作,在异步读操作时我们只需要注意4点内容

  • 1.确定所要读的文件描述符,并写入aiocb结构体中(下面几条一样不再赘余)

  • 2.确定读所需的缓冲区

  • 3.确定读的字节数

  • 4.确定文件的偏移量

总结以上注意事项:基本上和我们的read函数所需的条件相似,唯一的区别就是多一个文件偏移量。

2.5.4.3 执行结果分析

运行结果如下:

从上图看出,循环检查了3次异步读写的状态(不同机器的检查次数可能不一样,和具体的计算机性能有关系),指定的256字节才读取完毕,最后返回读取的字节数256。

如果注释掉异步读的状态检查:

1 ... 2 3 //查看异步读取的状态,直到读取请求完成 4 /* for(i = 1;aio_error(&cbp) == EINPROGRESS;i++) 5 { 6 printf("No.%3d\n",i); 7 } 8 ret = aio_return(&cbp); 9 printf("return %d\n",ret); 10 */ 11 ...

此时的运行结果:


发现什么都没输出,这是因为程序结束的时候,异步读请求还没完成,所以buf缓冲区还没有读进去数据。

如果将上面代码中的 sleep 的注释去掉,让异步请求发起后,程序等待1秒后再输出,就会发现成功读取到了数据。

用GDB单步跟踪上面程序,当发起异步读请求时:


看到发起一个异步请求时,Linux实际上是创建了一个线程去处理,当请求完成后结束线程。


2.6 aio_write

2.6.1 功能

aio_writr用来请求异步写操作

2.6.2 函数原型

int aio_write(struct aiocb *paiocb);

2.6.3 返回值

aio_write和aio_read函数类似,当该函数返回成功时,说明该写请求以进行排队(成功0,失败-1)。

2.6.4 使用示例

2.6.4.1 代码

特别提醒在编译上述程序时必须在编译时再加一个-lrt,如gcc test.c -o test -lrt。

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 14 #define BUFFER_SIZE 1025 15 16 int main(int argc,char **argv) 17 { 18 //定义aio控制块结构体 19 struct aiocb wr; 20 21 int ret,fd; 22 23 char str[20] = {"hello,world"}; 24 25 //置零wr结构体 26 bzero(&wr,sizeof(wr)); 27 28 fd = open("test.txt",O_WRONLY | O_APPEND); 29 if(fd < 0) 30 { 31 perror("test.txt"); 32 } 33 34 //为aio.buf申请空间 35 wr.aio_buf = (char *)malloc(BUFFER_SIZE); 36 if(wr.aio_buf == NULL) 37 { 38 perror("buf"); 39 } 40 41 wr.aio_buf = str; 42 43 //填充aiocb结构 44 wr.aio_fildes = fd; 45 wr.aio_nbytes = 1024; 46 47 //异步写操作 48 ret = aio_write(&wr); 49 if(ret < 0) 50 { 51 perror("aio_write"); 52 } 53 54 //等待异步写完成 55 while(aio_error(&wr) == EINPROGRESS) 56 { 57 printf("hello,world\n"); 58 } 59 60 //获得异步写的返回值 61 ret = aio_return(&wr); 62 printf("\n\n\n返回值为:%d\n",ret); 63 64 return 0; 65 }

2.6.4.2 代码分析

上面test.txt文件是以追加的方式打开的,所以不需要设置aio_offset 文件偏移量,不管文件偏移的值是多少都会在文件末尾写入。可以去掉O_APPEND, 不以追加方式打开文件,这样就可以设置 aio_offset ,指定从何处位置开始写入文件。

2.6 aio_suspend

2.6.1 功能

aio_suspend函数可以将当前进程挂起,直到有向其注册的异步事件完成为止。

2.6.2 函数原型

1 int aio_suspend(const struct aiocb * const aiocb_list[], 2 int nitems, const struct timespec *timeout);

2.6.3 返回值

如果在定时时间达到之前,因为完成了IO请求导致函数返回,此时返回值是0;否则,返回值为-1,并且会设置errno。

2.6.4 使用实例

2.6.4.1 代码

前面2个例子发起IO请求都是非阻塞的,即使IO请求未完成,也不影响调用程序继续执行后面的语句。但是,我们也可以调用aio_suspend 来阻塞一个或多个异步IO, 只需要将IO请求加入阻塞列表。

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 #define BUFSIZE 1024 11 #define MAX 2 12 13 //异步读请求 14 int aio_read_file(struct aiocb *cbp,int fd,int size) 15 { 16 int ret; 17 bzero(cbp,sizeof(struct aiocb)); 18 19 cbp->aio_buf = (volatile void*)malloc(size+1); 20 cbp->aio_nbytes = size; 21 cbp->aio_offset = 0; 22 cbp->aio_fildes = fd; 23 24 ret = aio_read(cbp); 25 if(ret < 0) 26 { 27 perror("aio_read error\n"); 28 exit(1); 29 } 30 } 31 32 int main() 33 { 34 struct aiocb cbp1,cbp2; 35 int fd1,fd2,ret; 36 int i = 0; 37 //异步阻塞列表 38 struct aiocb* aiocb_list[2]; 39 40 fd1 = open("test.txt",O_RDONLY); 41 if(fd1 < 0) 42 { 43 perror("open error\n"); 44 } 45 aio_read_file(&cbp1,fd1,BUFSIZE); 46 47 fd2 = open("test.txt",O_RDONLY); 48 if(fd2 < 0) 49 { 50 perror("open error\n"); 51 } 52 aio_read_file(&cbp2,fd2,BUFSIZE*4); 53 54 //向列表加入两个请求 55 aiocb_list[0] = &cbp1; 56 aiocb_list[1] = &cbp2; 57 //阻塞,直到请求完成才会继续执行后面的语句 58 aio_suspend((const struct aiocb* const*)aiocb_list,MAX,NULL); 59 printf("read1:%s\n",(char*)cbp1.aio_buf); 60 printf("read2:%s\n",(char*)cbp2.aio_buf); 61 62 close(fd1); 63 close(fd2); 64 return 0; 65 }

运行结果


可以看到只有read2读到了数据,因为只要有IO请求完成阻塞函数aio_suspend就会直接就返回用户线程了,继续执行下面的语句。

2.7 lio_listio

2.7.1 功能

aio同时还为我们提供了一个可以发起多个或多种I/O请求的接口lio_listio。这个函数效率很高,因为我们只需一次系统调用(一次内核上下位切换)就可以完成大量的I/O操作。第一个参数:LIO_WAIT (阻塞) 或 LIO_NOWAIT(非阻塞),第二个参数:异步IO请求列表

2.7.2 函数原型

int lio_listio(int mode,struct aiocb *list[],int nent,struct sigevent *sig);

2.7.3 返回值

如果mode是LIO_NOWAIT,当所有IO请求成功入队后返回0,否则,返回-1,并设置errno。如果mode是LIO_WAIT,当所有IO请求完成之后返回0,否则,返回-1,并设置errno。

2.7.4 使用示例

2.7.4.1 代码

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 #define BUFSIZE 100 11 #define MAX 2 12 13 //异步读结构体 14 int aio_read_file(struct aiocb *cbp,int fd,int size) 15 { 16 int ret; 17 bzero(cbp,sizeof(struct aiocb)); 18 19 cbp->aio_buf = (volatile void*)malloc(size+1); 20 cbp->aio_nbytes = size; 21 cbp->aio_offset = 0; 22 cbp->aio_fildes = fd; 23 cbp->aio_lio_opcode = LIO_READ; 24 } 25 26 int main() 27 { 28 struct aiocb cbp1,cbp2; 29 int fd1,fd2,ret; 30 int i = 0; 31 //异步请求列表 32 struct aiocb* io_list[2]; 33 34 fd1 = open("test.txt",O_RDONLY); 35 if(fd1 < 0) 36 { 37 perror("open error\n"); 38 } 39 aio_read_file(&cbp1,fd1,BUFSIZE); 40 41 fd2 = open("test.txt",O_RDONLY); 42 if(fd2 < 0) 43 { 44 perror("open error\n"); 45 } 46 aio_read_file(&cbp2,fd2,BUFSIZE*4); 47 48 io_list[0] = &cbp1; 49 io_list[1] = &cbp2; 50 51 lio_listio(LIO_WAIT,(struct aiocb* const*)io_list,MAX,NULL); 52 printf("read1:%s\n",(char*)cbp1.aio_buf); 53 printf("read2:%s\n",(char*)cbp2.aio_buf); 54 55 close(fd1); 56 close(fd2); 57 return 0; 58 }

运行结果:

2.7 异步IO通知机制

2.7.1 简介

异步与同步的区别就是我们不需要等待异步操作返回就可以继续干其他的事情,当异步操作完成时可以通知我们去处理它。

以下两种方式可以处理异步通知:

  • 信号处理

  • 线程回调

2.7.2 信号处理

2.7.2.1 示例

在发起异步请求时,可以指定当异步操作完成时给调用进程发送什么信号,这样调用收到此信号就会执行相应的信号处理函数。

代码:

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 #define BUFSIZE 256 12 13 //信号处理函数,参数signo接收的对应的信号值 14 void aio_handler(int signo) 15 { 16 int ret; 17 printf("异步操作完成,收到通知\n"); 18 } 19 20 int main() 21 { 22 struct aiocb cbp; 23 int fd,ret; 24 int i = 0; 25 26 fd = open("test.txt",O_RDONLY); 27 28 if(fd < 0) 29 { 30 perror("open error\n"); 31 } 32 33 //填充struct aiocb 结构体 34 bzero(&cbp,sizeof(cbp)); 35 //指定缓冲区 36 cbp.aio_buf = (volatile void*)malloc(BUFSIZE+1); 37 //请求读取的字节数 38 cbp.aio_nbytes = BUFSIZE; 39 //文件偏移 40 cbp.aio_offset = 0; 41 //读取的文件描述符 42 cbp.aio_fildes = fd; 43 //发起读请求 44 45 //设置异步通知方式 46 //用信号通知 47 cbp.aio_sigevent.sigev_notify = SIGEV_SIGNAL; 48 //发送异步信号 49 cbp.aio_sigevent.sigev_signo = SIGIO; 50 //传入aiocb 结构体 51 cbp.aio_sigevent.sigev_value.sival_ptr = &cbp; 52 53 //安装信号 54 signal(SIGIO,aio_handler); 55 //发起异步读请求 56 ret = aio_read(&cbp); 57 if(ret < 0) 58 { 59 perror("aio_read error\n"); 60 exit(1); 61 } 62 //暂停4秒,保证异步请求完成 63 sleep(4); 64 close(fd); 65 return 0; 66 }

运行结果:


2.7.2.2 小结

SIGIO 是系统专门用来表示异步通知的信号,当然也可以发送其他的信号,比如将上面的SIGIO替换为 SIGRTMIN+1 也是可以的。安装信号可以使用signal(),但是signal不可以传递参数进去。所以推荐使用sigaction()函数,可以为信号处理设置更多的东西。

2.7.3 线程回调

顾名思义就是当调用进程收到异步操作完成的通知时,开线程执行设定好的回调函数去处理。无需专门安装信号。在Glibc AIO 的实现中, 用多线程同步来模拟 异步IO ,以上述代码为例,它牵涉了3个线程, 主线程(23908)新建 一个线程(23909)来调用阻塞的pread函数,当pread返回时,又创建了一个线程(23910)来执行我们预设的异步回调函数, 23909 等待23910结束返回,然后23909也结束执行。与信号处理方式相比,如上图,采用线程回调的方式可以使得在处理异步通知的时候不会阻塞当前调用进程。

实际上,为了避免线程的频繁创建、销毁,当有多个请求时,Glibc AIO 会使用线程池,但以上原理是不会变的,尤其要注意的是:我们的回调函数是在一个单独线程中执行的.

Glibc AIO 广受非议,存在一些难以忍受的缺陷和bug,饱受诟病,是极不推荐使用的.

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 #define BUFSIZE 256 12 13 //回调函数 14 void aio_handler(sigval_t sigval) 15 { 16 struct aiocb *cbp; 17 int ret; 18 19 printf("异步操作完成,收到通知\n"); 20 //获取aiocb 结构体的信息 21 cbp = (struct aiocb*)sigval.sival_ptr; 22 23 if(aio_error(cbp) == 0) 24 { 25 ret = aio_return(cbp); 26 printf("读请求返回值:%d\n",ret); 27 } 28 while(1) 29 { 30 printf("正在执行回调函数。。。\n"); 31 sleep(1); 32 } 33 } 34 35 int main() 36 { 37 struct aiocb cbp; 38 int fd,ret; 39 int i = 0; 40 41 fd = open("test.txt",O_RDONLY); 42 43 if(fd < 0) 44 { 45 perror("open error\n"); 46 } 47 48 //填充struct aiocb 结构体 49 bzero(&cbp,sizeof(cbp)); 50 //指定缓冲区 51 cbp.aio_buf = (volatile void*)malloc(BUFSIZE+1); 52 //请求读取的字节数 53 cbp.aio_nbytes = BUFSIZE; 54 //文件偏移 55 cbp.aio_offset = 0; 56 //读取的文件描述符 57 cbp.aio_fildes = fd; 58 //发起读请求 59 60 //设置异步通知方式 61 //用线程回调 62 cbp.aio_sigevent.sigev_notify = SIGEV_THREAD; 63 //设置回调函数 64 cbp.aio_sigevent.sigev_notify_function = aio_handler; 65 //传入aiocb 结构体 66 cbp.aio_sigevent.sigev_value.sival_ptr = &cbp; 67 //设置属性为默认 68 cbp.aio_sigevent.sigev_notify_attributes = NULL; 69 70 //发起异步读请求 71 ret = aio_read(&cbp); 72 if(ret < 0) 73 { 74 perror("aio_read error\n"); 75 exit(1); 76 } 77 //调用进程继续执行 78 while(1) 79 { 80 printf("主线程继续执行。。。\n"); 81 sleep(1); 82 } 83 close(fd); 84 return 0; 85 }

运行结果:


三、libaio

3.1 简介

上面介绍的aio其实是用户层使用线程模拟的异步io,缺点是占用线程资源而且受可用线程的数量限制。Linux2.6版本后有了libaio,这完全是内核级别的异步IO,IO请求完全由底层自由调度(以最佳次序的磁盘调度方式)。

libaio的缺点是,想要使用该种方式的文件必须支持以O_DIRECT标志打开,然而并不是所有的文件系统都支持。如果你没有使用O_DIRECT打开文件,它可能仍然“工作”,但它可能不是异步完成的,而是变为了阻塞的。

3.2 安装

sudo apt-get install libaio-dev

3.3 结构体

3.3.1 aiocb

aiocb结构体:

1 struct aiocb 2 { 3 //要异步操作的文件描述符 4 int aio_fildes; 5 //用于lio操作时选择操作何种异步I/O类型 6 int aio_lio_opcode; 7 //异步读或写的缓冲区的缓冲区 8 volatile void *aio_buf; 9 //异步读或写的字节数 10 size_t aio_nbytes; 11 //异步通知的结构体 12 struct sigevent aio_sigevent; 13 }

3.3.2 timespec

3.3.2.1 源码

timespec结构体

1 struct timespec { 2 time_t tv_sec; /* seconds */ 3 long tv_nsec; /* nanoseconds [0 .. 999999999] */ 4 };

3.3.2.2 成员分析

tv_sec:秒数

tv_nsec:毫秒数

3.3.3 io_event

3.3.3.1 源码

io_event结构体

1 struct io_event { 2 void *data; 3 struct iocb *obj; 4 unsigned long res; 5 unsigned long res2; 6 };

3.3.3.2 成员分析

io_event是用来描述返回结果的:

obj就是之前提交IO任务时的iocb; res和res2来表示IO任务完成的状态。

3.3.4 io_iocb_common

3.3.4.1 源码

io_iocb_common结构体

1 struct io_iocb_common { // most import structure, either io and iov are both initialized based on this 2 3 PADDEDptr(void *buf, __pad1); // pointer to buffer for io, pointer to iov for io vector 4 5 PADDEDul(nbytes, __pad2); // number of bytes in one io, or number of ios for io vector 6 7 long long offset; //disk offset 8 9 long long __pad3; 10 11 unsigned flags; // interface for set_eventfd(), flag to use eventfd 12 13 unsigned resfd;// interface for set_eventfd(), set to eventfd 14 15 }; /* result code is the amount read or -'ve errno */

io_iocb_common是也是异步IO库里面最重要的数据结构,上面iocb数据结构中的联合u,通常就是按照io_iocb_common的格式来初始化的。

3.3.4.2 成员分析

成员 buf:  在批量IO的模式下,表示一个iovector 数组的起始地址;在单一IO的模式下,表示数据的起始地址; 成员 nbytes:  在批量IO的模式下,表示一个iovector 数组的元素个数;在单一IO的模式下,表示数据的长度; 成员 offset:   表示磁盘或者文件上IO开始的起始地址(偏移) 成员 _pad3:   填充字段,目前可以忽略 成员 flags:     表示是否实用eventfd 机制 成员 resfd:    如果使用eventfd机制,就设置成之前初始化的eventfd的值, io_set_eventfd()就是用来封装上面两个域的。

3.3.5 io_iocb

3.3.5.1 源码

1 struct iocb { 2 PADDEDptr(void *data, __pad1); /* Return in the io completion event */ /// will passed to its io finished events, channel of callback 3 4 PADDED(unsigned key, __pad2); /* For use in identifying io requests */ /// identifier of which iocb, initialized with iocb address? 5 6 short aio_lio_opcode; // io opration code, R/W SYNC/DSYNC/POOL and so on 7 8 short aio_reqprio; // priority 9 10 int aio_fildes; // aio file descriptor, show which file/device the operation will take on 11 12 union { 13 struct io_iocb_common c; // most important, common initialization interface 14 15 struct io_iocb_vector v; 16 17 struct io_iocb_poll poll; 18 19 struct io_iocb_sockaddr saddr; 20 21 } u; 22 23 };

3.3.5.2 成员分析

iocb是异步IO库里面最重要的数据结构,大部分异步IO函数都带着这个参数。

成员 data: 当前iocb对应的IO完成之后,这个data的值会自动传递到这个IO生成的event的data这个域上去

成员 key: 标识哪一个iocb,通常没用

成员 ail_lio_opcode:指示当前IO操作的类型,可以初始化为下面的类型之一:

1 typedef enum io_iocb_cmd { // IO Operation type, used to initialize aio_lio_opcode 2 3 IO_CMD_PREAD = 0, 4 5 IO_CMD_PWRITE = 1, 6 7 IO_CMD_FSYNC = 2, 8 9 IO_CMD_FDSYNC = 3, 10 11 IO_CMD_POLL = 5, /* Never implemented in mainline, see io_prep_poll */ 12 13 IO_CMD_NOOP = 6, 14 15 IO_CMD_PREADV = 7, 16 17 IO_CMD_PWRITEV = 8, 18 19 } io_iocb_cmd_t;

成员 aio_reqprio: 异步IO请求的优先级,可能和io queue相关

成员 aio_filds: 接受IO的打开文件描述符,注意返回这个描述符的open()函数所带的CREATE/RDWR参数需要和上面的opcod相匹配,满足读写权限属性检查规则。

成员 u: 指定IO对应的数据起始地址,或者IO向量数组的起始地址

3.4 libaio系统调用

linux kernel 提供了5个系统调用来实现异步IO。文中最后介绍的是包装了这些系统调用的用户空间的函数。

1 int io_setup(unsigned nr_events, aio_context_t *ctxp); 2 int io_destroy(aio_context_t ctx); 3 int io_submit(aio_context_t ctx, long nr, struct iocb *cbp[]); 4 int io_cancel(aio_context_t ctx, struct iocb *, struct io_event *result); 5 int io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

1、建立IO任务

int io_setup (int maxevents, io_context_t *ctxp);

io_context_t对应内核中一个结构,为异步IO请求提供上下文环境。注意在setup前必须将io_context_t初始化为0。当然,这里也需要open需要操作的文件,注意设置O_DIRECT标志。

2、提交IO任务

long io_submit (aio_context_t ctx_id, long nr, struct iocb **iocbpp);

提交任务之前必须先填充iocb结构体,libaio提供的包装函数说明了需要完成的工作:

3.获取完成的IO long io_getevents (aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout); 这里最重要的就是提供一个io_event数组给内核来copy完成的IO请求到这里,数组的大小是io_setup时指定的maxevents。timeout是指等待IO完成的超时时间,设置为NULL表示一直等待所有到IO的完成。 4.销毁IO任务 int io_destrory (io_context_t ctx)

3.5 异步IO上下文 aio_context_t

代码:

1 #define _GNU_SOURCE /* syscall() is not POSIX */ 2 #include /* for perror() */ 3 #include /* for syscall() */ 4 #include /* for __NR_* definitions */ 5 #include /* for AIO types and constants */ 6 inline int io_setup(unsigned nr, aio_context_t *ctxp) 7 { 8 return syscall(__NR_io_setup, nr, ctxp); 9 } 10 inline int io_destroy(aio_context_t ctx) 11 { 12 return syscall(__NR_io_destroy, ctx); 13 } 14 int main() 15 { 16 aio_context_t ctx; 17 int ret; 18 ctx = 0; 19 ret = io_setup(128, &ctx); 20 if (ret < 0) { 21 perror("io_setup error"); 22 return -1; 23 } 24 printf("after io_setup ctx:%Ld\n",ctx); 25 ret = io_destroy(ctx); 26 if (ret < 0) { 27 perror("io_destroy error"); 28 return -1; 29 } 30 printf("after io_destroy ctx:%Ld\n",ctx); 31 return 0; 32 }

系统调用io_setup会创建一个所谓的"AIO上下文"(即aio_context,后文也叫‘AIO context’等)结构体到在内核中。aio_context是用以内核实现异步AIO的数据结构。它其实是一个无符号整形,位于头文件 /usr/include/linux/aio_abi.h。

typedef unsigned long aio_context_t;

每个进程都可以有多个aio_context_t。传入io_setup的第一个参数在这里是128,表示同时驻留在上下文中的IO请求的个数;第二个参数是一个指针,内核会填充这个值。io_destroy的作用是销毁这个上下文aio_context_t。上面的例子很简单,创建一个aio_context_t并销毁。

3.6 提交并查询IO

流程:

  • 每一个提交的IO请求用结构体struct iocb来表示。

  • 首先初始化这个结构体为全零: memset(&cb, 0, sizeof(cb));

  • 然后初始化文件描述符(cb.aio_fildes = fd)和AIO 命令(cb.aio_lio_opcode = IOCB_CMD_PWRITE)

  • 文件描述符对应上文所打开的文件。本例中是./testfile.

代码:

1 #define _GNU_SOURCE /* syscall() is not POSIX */ 2 #include /* for perror() */ 3 #include /* for syscall() */ 4 #include /* for __NR_* definitions */ 5 #include /* for AIO types and constants */ 6 #include /* O_RDWR */ 7 #include /* memset() */ 8 #include /* uint64_t */ 9 inline int io_setup(unsigned nr, aio_context_t *ctxp) 10 { 11 return syscall(__NR_io_setup, nr, ctxp); 12 } 13 14 inline int io_destroy(aio_context_t ctx) 15 { 16 return syscall(__NR_io_destroy, ctx); 17 } 18 19 inline int io_submit(aio_context_t ctx, long nr, struct iocb **iocbpp) 20 { 21 return syscall(__NR_io_submit, ctx, nr, iocbpp); 22 } 23 24 inline int io_getevents(aio_context_t ctx, long min_nr, long max_nr, struct io_event *events, struct timespec *timeout) 25 { 26 return syscall(__NR_io_getevents, ctx, min_nr, max_nr, events, timeout); 27 } 28 29 int main() 30 { 31 aio_context_t ctx; 32 struct iocb cb; struct iocb *cbs[1]; 33 char data[4096]; 34 struct io_event events[1]; 35 int ret; 36 int fd; 37 int i ; 38 for(i=0;i<4096;i++) 39 { 40 data[i]=i%50+60; 41 } 42 fd = open("./testfile", O_RDWR | O_CREAT,S_IRWXU); 43 if (fd < 0) 44 { 45 perror("open error"); 46 return -1; 47 } 48 49 ctx = 0; 50 ret = io_setup(128, &ctx); 51 printf("after io_setup ctx:%ld",ctx); 52 if (ret < 0) 53 { 54 perror("io_setup error"); 55 return -1; 56 } /* setup I/O control block */ 57 memset(&cb, 0, sizeof(cb)); 58 cb.aio_fildes = fd; 59 cb.aio_lio_opcode = IOCB_CMD_PWRITE;/* command-specific options */ 60 cb.aio_buf = (uint64_t)data; 61 cb.aio_offset = 0; 62 cb.aio_nbytes = 4096; 63 cbs[0] = &cb; 64 ret = io_submit(ctx, 1, cbs); 65 if (ret != 1) 66 { 67 if (ret < 0) 68 perror("io_submit error"); 69 else 70 fprintf(stderr, "could not sumbit IOs"); 71 return -1; 72 } /* get the reply */ 73 74 ret = io_getevents(ctx, 1, 1, events, NULL); 75 printf("%d\n", ret); 76 struct iocb * result = (struct iocb *)events[0].obj; 77 printf("reusult:%Ld",result->aio_buf); 78 ret = io_destroy(ctx); 79 if (ret < 0) 80 { 81 perror("io_destroy error"); 82 return -1; 83 } 84 return 0; 85 }

3.7 内核当前支持的AIO 命令

内核当前支持的AIO 命令有

1 IOCB_CMD_PREAD 读; 对应系统调用pread(). 2 IOCB_CMD_PWRITE 写,对应系统调用pwrite(). 3 IOCB_CMD_FSYNC 同步文件数据到磁盘,对应系统调用fsync() 4 IOCB_CMD_FDSYNC 同步文件数据到磁盘,对应系统调用fdatasync() 5 IOCB_CMD_PREADV 读,对应系统调用readv() 6 IOCB_CMD_PWRITEV 写,对应系统调用writev() 7 IOCB_CMD_NOOP 只是内核使用



声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 【7.24 深圳】2025国际AI+IoT生态发展大会/2025全球 MCU及嵌入式技术论坛


  • 相关技术文库
  • C语言
  • 编程
  • 软件开发
  • 程序
  • 光立方程序编写步骤

    基于51单片机的4*4*4光立方程序实现原理及程序代码。LED光立方的复位电路、时钟电路、每层LED灯电路控制逻辑,系统总原理图,工作流程及相关C语言源码实现。希望能够对你学习了解LED光立方程序编写及LED立方实体制...

    07-04
  • 封装继承多态

    封装: 封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。被封装的对象通常被称为抽象数据类型。 封装的意义: 封装的意义在于保护或者防止代码(数据)被我们无意中...

    07-04
  • 封装是什么意思?

    即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中...

    07-04
  • 超声波模块测距51程序_单片机超声波测距c语言

    超声波检测原理 超声波测距的程序流程图 程序如下: //超声波模块程序 //超声波模块程序 //Trig = P2^0 //Echo = P3^2 #include #define uchar unsigned char #define uint unsigned int // void delay(uint z) {...

    07-01
  • 大佬带你看嵌入式系统,嵌入式系统该学习什么?

    嵌入式系统是当今的热门系统之一,在诸多领域,嵌入式系统都有所应用。为增进大家对嵌入式系统的认识,小编将为大家介绍嵌入式系统是一个什么样的专业,以及学习嵌入式系统该学习哪些内容。如果你对嵌入式系统具有...

    06-27
  • c51单片机编程要点总结

    c51单片机编程要点总结 1、头文件:#include (我用的是 STC 89C54RD+) 2、预定义:sbit LED = P1^0// 定义 P1 口的 0 位为 LED 注:“P1^0”这个写法,与 A51 不同(A51 是 P1.0),P1 是一组端口,端口号范围 0~7 注2...

    06-25
  • C语言基础知识点汇总

    总结C语言基础知识点

    06-23
  • Keil使用中的若干问题

      一、混合编程  1、模块内接口:  使用如下标志符:  #pragma asm  汇编语句  #pragma endasm  注意:如果在c51程序中使用了汇编语言,注意在keil编译器中需要激活Properties中的“Generate Assembler...

    06-23
  • ESP32-finsh

    esp32c2添加finsh实现了ping指令和AT指令解析

    06-13
  • 一文讲通C语言位域,快速掌握!

    在嵌入式系统的开发中,内存是最程序员非常需要关注的对象,尤其是MCU开发、网络协议解析、硬件寄存器操作等领域,能否对内存进行高效的利用和合理的管理,将直接影响程序的性能和硬件的稳定性。

    06-10
下载排行榜
更多
评测报告
更多
广告