当前位置: 首页 > 科技观察

异步IO:新时代IO处理利器

时间:2023-03-19 13:33:16 科技观察

不管是非阻塞IO,IO多路复用,还是信号驱动IO,都不是真正意义上的IO。真正的异步IO是数据从内核空间拷贝到用户空间,也是异步的。处理完成后,拷贝完成,然后通知应用进程,应用进程直接读取用户空间中的数据进行操作。至此,我们介绍了阻塞IO、非阻塞IO、信号驱动IO、IO多路复用。下面我们打个比方来区分一下这几种IO。我们在网上购物时,下单后,可以有以下处理方式:下单后,在门口等快递小哥送快递上门,这就是同步阻塞IO;下单后就不用管了,直到快递小哥打电话通知你去取快递。这就是同步非阻塞IO中的信号驱动IO;下单后,你会定期去物流应用程序查看所有快递员的状态。送到寄存点后,你去取。这就是同步非阻塞IO中的IO多路复用;下单后你不用管,直到快递小哥送到你手上,你不用出门直接拿到快递。可以拿快递,这就是异步IO。异步IO最关键的一点就是在读取数据时将IO缓冲区提交给内核,让内核将数据写入这个缓冲区。本节我们将介绍异步IO模型和相关API,顺便介绍一下最新的更高性能的IO模型。看完这篇文章,你就会明白:异步IO的原理;POSIX定义的异步IO接口及其使用方法;异步IO的发展方向。一、异步I/O模型介绍下面是异步IO(asynchronousI/O)的执行流程:通过aio_read等异步处理函数告诉内核开始一个动作,让内核通知应用进程整个操作完成后。通知将在数据复制到用户空间缓冲区后完成。整个IO进程应用进程不会被阻塞。异步IO最大的优化点是系统调用开销大??,异步IO结合了轮询等待数据的系统调用(如select、poll、epoll)和读取数据的操作。下面我们将通过具体的例子来演示异步IO程序的处理流程。2.异步IO相关函数的用例本节介绍POSIX定义的异步操作接口。2.1.异步IO相关API每个异步函数都需要传入一个aiocb结构体(异步IO控制块)。这个结构体的格式如下:structaiocb{/*Theorderofthesefieldsisimplementation-dependent*/intaio_fildes;/*Filedescriptor*/off_taio_offset;/*Fileoffset*/volatilevoid*aio_buf;/*Locationofbuffer*/size_taio_nbytes;/*Lengthoftransfer*/intaio_reqprio;/*请求优先级*/structsieventaio_sigevent;/*通知方法*/intaio_lio_opcode;/*要执行的操作;};这个结构指定了异步操作的socket描述符,以及操作过程中使用的buffer,其中aio_sigevent告诉AIO在IO操作完成时指向什么操作。常见的异步IO相关函数如下:INTAIO_READ(STRUCTAIOCB*AIOCBP)请求异步读操作,将aiocbp指向的缓冲区描述的I/O请求排队。注意:offset必须在aio_read的aiocbp中设置。在传统的非异步读取操作中,偏移量是在文件描述符的上下文中维护的。对于每个操作,都需要为后续操作更新偏移量。可以寻址下一个数据块。对于异步读操作,可以同时进行很多异步IO读操作,所以这里需要的是处理文件的偏移量aiocbp->aio_offset和异步读内容的长度aiocbp->aio_nbytes。在aio_read调用之后,文件偏移量变为未设置。INTAIO_WRITE(STRUCTAIOCB*AIOCBP)请求异步写操作。此函数将aiocbp指向的缓冲区描述的I/O请求排队。aio_write不必设置偏移量。如果打开的文件设置了O_APPEND选项,偏移量将被忽略,数据将追加到文件末尾;如果未设置O_APPEND,则从aiocbp->aio_offset数据开始写入,而不考虑文件的偏移量。SSIZE_TAIO_RETURN(STRUCTAIOCB*AIOCBP)获取完成的异步请求的返回状态。由于IO是异步的,所以需要专门的函数来获取异步处理的状态。aio_return的返回值相当于read或write等系统调用的返回值。出错时返回-1,并且正确设置了errno。可能的响应值:成功时,将返回处理的字节数;-1:发生错误,设置errno表示错误原因;此函数只能在aio_error调用返回EINPROGRESS以外的值后调用,并且只允许调用一次。INTAIO_ERROR(CONSTSTRUCTAIOCB*AIOCBP)检查异步请求的状态,可能的响应值:EINPROGRESS:如果请求还没有完成;ECANCELLED:如果请求已被取消;0:如果请求已经完成;如果异步IO操作失败,则为正错误号,对应于同步读(2)、写(2)、fsync(2)或fdatasync(2)系统的errorno。AIO_SUSPENDintaio_suspend(conststructaiocb*constaiocb_list[],intnitems,conststructtimespec*timeout);挂起调用进程,直到一个或多个异步请求完成或失败。需要等待的异步请求存放在aiocb_list中,如:structaioct*cblist[MAX_LIST];...cblist[0]=&aiocb1;ret=aio_read(my_aiocb1);ret=aio_suspend(cblist,MAX_LIST,NULL);INTAIO_CANCEL(INTFD,STRUCTAIOCB*AIOCBP)取消异步IO请求。LIO_LISTIOintlio_listio(intmode,structaiocb*constaiocb_list[],intnitems,structsigevent*sevp);发起一系列IO操作,启动数组aiocb_list描述的I/O操作列表。下面通过具体的例子展示aio的用法。2.2.aio_read的例子下面是一个使用aio_read的例子:我把所有重要的处理步骤都标了出来,并在代码中做了解释。我不会在这里重复描述。需要注意几点:aio_read的aiocbp必须设置偏移量;调用aio_error返回值不为EINPROGRESS后,调用aio_return,进入异步IO处理状态。以上就是目前异步IOAPI的设计和基本用法。3.操作系统对异步IO的支持3.1。Linux下的异步IO上一节介绍了POSIX定义的异步操作接口,但是遗憾的是,Linux的aio操作并不是操作系统层面真正支持的,而是利用空间借助于GNU库函数通过pthread来实现的,并且不支持套接字IO。基于以上原因,Linux下大多采用epoll多路复用技术和非阻塞IO,通过事件分发模型来构建高性能的网络程序。3.2.Windows下的异步IOWindows实现了一套完整的异步编程接口,称为IOCP(I/OCompletionPorts,IO完成端口)[1]。IOCP提供了一个高效的线程模型来处理多处理器系统上的多个异步I/O请求。当进程创建IOCP时,系统会为请求创建一个关联的队列对象,其唯一目的是为这些请求提供服务。一个进程可以通过使用IOCP和一个预分配的线程池来处理许多并发的异步IO请求,这比在接收IO请求时创建线程更快更高效。基于IOCP,产生了Proactor模式,这种模式类似于Reactor模式但更高效。看不懂也没关系。我们将在后续的高性能网络编程范式章节中详细介绍这两种模式。4.更高效的IO4.1.背景由于Linux下没有广泛使用的AIO技术,aio系列的功能是POSIX定义的异步操作接口,并不是真正的操作系统内核所支持的异步IO。目前最流行的是基于epoll的多路复用技术,以及基于多路复用技术的Reactor模式。为了促进AIO在Linux系统中的发展,实现更高效的IO,后来改了io_uring。4.2、io_uringio_uring是在LinuxKernel5.1中新增的,用于替代AIO和io_submit,构造一个通用的异步系统调用接口。这是对异步IO的介绍。在下一篇文章中,我们将详细讨论使用各种IO模型的高性能网络编程范式。博客链接:https://www.itzhai.com