当前位置: 首页 > Linux

LinuxIO模型详解

时间:2023-04-06 23:24:38 Linux

IO模型通常一个完整的用户进程中的IO分为两个阶段:用户进程空间到内核空间,内核空间到设备空间(磁盘、网络等)。Linux内核IO模式1、同步阻塞IO模型流程:线程发起IO系统调用后,会被阻塞,转移到内核空间处理,整个IO处理完毕后返回数据。优缺点:一般每个IO请求都需要分配一个IO线程,系统开销大。典型应用:阻塞套接字,JavaBIO;2、同步非阻塞IO模型流程:线程不断轮询读取内核IO设备缓冲区,没有数据则立即返回EWOULDBLOCK,有数据则返回。优缺点:CPU消耗多,无效IO多。典型应用:非阻塞socket(设置为NONBLOCK)3、同步IO多路复用模型流程:线程调用select/poll/epoll传入多个设备fds,然后阻塞或轮询等待。如果有IO设备就绪,则返回可读条件,用户主动调整IO读写,如果没有,则继续阻塞。优缺点:IO多路复用与前面两种模型相比,可以监控多个IO设备,因此可以在一个线程中处理多个网络请求。典型应用:select、poll、epoll。nginx和JavaNIO都是基于这种IO模型。4.异步信号驱动IO模型流程:利用linux信号机制,使用sigaction函数将SIGIO读写信号和handler回调函数存储在内核队列中。当设备IO缓冲区可写或可读时,触发SIGIO中断,返回设备fd并回调handler。优缺点:这种异步回调方式避免了用户或内核主动轮询设备造成的资源浪费,但这种方式存在问题。首先,处理程序运行在中断环境中,多线程不稳定,必须考虑信号的平台兼容性。其次,SIGIO信号被POSIX定义为标准信号,不会进入队列,所以多个SIGIO只会同时触发第一个SIGIO。参考:http://www.man7.org/linux/man-pages/man7/signal.7.html5。异步IO模型流程:用户传入设备fd和数据结构,内核等设备缓冲区就绪。数据读写,最后通知用户完成。由于历史原因,linuxaio有两种主流实现方式。glibcaio利用内核IO的多线程封装来实现用户空间的异步。大致流程是用户调用aio_API传入aiocb(“异步I/O控制块”)结构体,aio排队操作,立即返回。当内核完成数据复制后,会触发一个信号通知用户aiocb准备好了,也可以通过调用aio_error查看aiocb状态是否完成。因为在用户空间异步实现,频繁的线程和内核态切换导致明显的性能问题,无法弹性伸缩。LinuxManual也表示这是内核aio成熟前的过渡方案。ref:API:http://man7.org/linux/man-pages/man7/aio.7.htmllibaio/kernelaio,libaio在用户空间封装了内核aio的接口,通过引入libaio允许应用程序使用aio,否则应用程序需要自己定义系统调用。过程是先调用io_setup获取aio_context,然后构造一个或多个iocb(包括fd、buffer)结构体并通过io_submit提交到内核队列,最后使用阻塞或轮询调用io_getevents获取io_event判断是否IO完成。这是linux官方的解决方案,但是由于设计缺陷,只支持O_DIRECT(绕过内核缓冲区直接IO)模式打开的文件设备。在其他情况下,它要么返回错误,要么退化为同步阻塞模式。读入io_submit写。虽然功能还不成熟,但是在性能方面,已经有人验证过,使用aio在一个进程中读写16个fd,与16个进程pread的IO吞吐量是一致的。参考:演示:https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/Linus关于aioAPI的投诉:https://lwn.net/Articles/671657/aio一些历史问题总结:https://www.aikaiyuan.com/4556.htmlKernelcommitter总结aio的问题和演变:《Linux Asynchronous I/O Design: Evolution & Challenges》glibc和aio更完整的demo:https://oxnz.github.io/2016/10/13/linux-aio/#aio-system-calls性能测试:http://blog.yufeng.info/archives/741应用IO模式1.Reactor模式主要分为以下几个步骤:应用注册更多配合reactorA设备监听(epoll/select)和回调函数,触发IOready事件,reactor分发事件执行相应的回调函数,应用在回调函数中进行IO操作。单reactor单回调线程结构图。经典应用:libevent、libuv的网络IO、Cronet的底层IOreactor扩展模型也包含单reactor多回调线程结构。经典应用:libuv的文件IO多reactor多回调线程结构,每次连接成功后的所有操作都由同一个线程处理。这确保了同一请求的所有状态和上下文都在同一个线程中,与单个回调线程相比,避免了不必要的上下文切换。经典应用:nettyref:reactor示例代码https://juejin.im/post/5b4570cce51d451984695a9b2。Proactor模式流程和reactor类似,不同的是在IOready事件触发后,proactor完成IO操作,然后通知应用回调。boostasio异步IO库等经典应用。Linux平台虽然还是基于epoll/select,但是内部实现了异步操作处理器(AsynchronousOperationProcessor)和异步事件多路分解器(AsynchronousEventDemultiplexer),将IO操作与应用回调隔离开来。boost.asio结构和流程图demo:https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/tutorial/tutdaytime3/src.htmlreactor和proactor的主要区别:例子:Reactor将句柄放入select(),等待writeable就绪,然后调用write()写入数据;写入后,处理后续逻辑;Proactor调用aio_write后立即返回,由内核负责写操作,写完后调用相应的回调函数Process后续逻辑;Reactor被动等待指示事件的到来并做出响应;它有一个等待过程,必须先将所有内容放入监听事件集合中,然后在处理程序可用时进行操作;Proactor直接调用异步读写Operation,调用后立即返回;implementReactor实现被动事件分离分发模型,服务等待请求事件的到来,然后不间断地同步处理事件响应;Proactor实现了一个主动事件分离和分发模型;这种设计允许多个任务并发执行,从而提高吞吐量;并且可以执行耗时任务(各个任务互不影响)。Reactor的优点是实现起来比较简单,适合处理时间短的场景高效;操作系统可以等待多个事件源,避免与多线程编程相关的性能开销和编程复杂性;事件的序列化对应用程序是透明的,可以在不加锁的情况下顺序同步执行;事务分离:将应用无关的解复用和分配机制与应用相关的回调函数分离,Proactor具有更高的性能,可以处理耗时的并发场景;Reactor处理耗时操作的缺点是会造成事件分发阻塞,影响后续事件的处理;Proactor实现了复杂的逻辑;它依赖于操作系统对异步操作的支持。目前实现纯异步操作的操作系统很少,像windowsIOCP这样的优秀实现,但是由于用于服务器的windows系统的局限性,Unix/Linux系统对纯异步的支持有限,而主流的应用事件驱动还是通过select/epoll来实现;适用场景Reactor:同时接收多个服务请求,并顺序同步处理Proactor:异步接收并处理多个服务请求的事件驱动;参考:https://www.cnblogs.com/losophy/p/9202815.html