文章目录
- 概述
- 阻塞式I/O模型
- 非阻塞式I/O模型
- I/O多路复用模型
- 信号驱动式I/O模型
- 异步I/O模型
- 相关参考
概述
在操作系统中,I/O类操作是相对慢速的,应用发起一个I/O操作,需要等待I/O资源就绪后,才能继续后面的处理。这种简单的请求-响应
的IO模型,很明显是无法满足实际生产环境对高并发、高吞吐的要求,因此,系统通常会提供多种I/O模型,以提高I/O操作的效率。Linux系统中支持5种I/O模型:
- 阻塞式I/O;
- 非阻塞式IO;
- I/O多路复用模型
- 信号驱动式IO;
- 异步IO。
POSIX依据I/O操作是否会导致请求进程阻塞,将I/O操作分成同步I/O和异步I/O,其中:
- 同步I/O操作会导致请求进程阻塞,直到I/O操作完成;
- 异步I/O操作不会导致请求进程阻塞。
阻塞式I/O模型
阻塞式I/O是最基本的I/O模型,POSIX提供的大部分I/O类系统调用接口默认行为都是阻塞式的。以recvfrom操作为例,当应用进程发起recvfrom调用时,如果当前的数据没有就绪,那么调用进程会被阻塞并一直等待数据准备完成。recvfrom成功返回后,应用进程开始处理数据。
阻塞式I/O模型会导致应用进程大部分时间都在消耗在等待I/O上,而无法去处理其它事务。为了提升并发能力,早期的应用通常都会采用多进程或者多线程的方式来配合阻塞式I/O模型使用,但多进程和多线程带来的资源开销也很大程度上制约了系统的能力。
非阻塞式I/O模型
通过将套接字设置成非阻塞式,应用进程在执行I/O操作时,如果当前数据尚未就绪,那么调用会直接返回一个错误,在Linux中,这个错误类型通常是EWOULDBLOCK,指示数据还未准备好。应用进程可以不断选择进行重试,直到数据准备完成。
非阻塞式I/O模型不会导致应用进程阻塞,但不断重试的方案也会带来很高的CPU开销,因此非阻塞式I/O模型通常都会配合系统的I/O多路复用机制一起使用。
I/O多路复用模型
I/O多路复用是目前网络服务器编程使用最广泛的I/O模型,Reactor编程模型就是基于I/O多路复用进行实现。I/O多路复用提供了一类接口,可以等待多个描述符就绪,这样的好处在于应用进程可以只阻塞在I/O多路复用的接口中,只要监听的描述符中有一个资源准备就绪,应用进程就会被唤醒进行处理。
在Linux系统中,提供了三类I/O多路复用接口,分别为select、poll以及epoll,其中select/poll在监听的描述符较少的场景下使用;对于高并发应用,通常会使用epoll机制。
信号驱动式I/O模型
信号驱动式I/O模型在实际使用中并不多见,它通过让Linux内核在描述符就绪时发送SIGIO信号,通知应用进程可以执行I/O操作获取数据,从而避免了无谓的等待。
异步I/O模型
异步I/O在POSIX的语义中被定义为是真正的异步I/O操作类型,与前面四种I/O模型相比,异步I/O接口不需要等待数据就绪,甚至连数据读取的动作都不需要进行。异步I/O接口只是简单地提交一个I/O请求,后续所有事情都交给内核进行处理,内核会执行实际的I/O操作,并将数据从内核复制到应用的缓冲区,完成后通知应用进程进行数据处理。
异步I/O模型与信号驱动式I/O的主要区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。Linux系统最早提供的异步I/O接口主要是aio系列函数,在5.1内核版本,又引入了新的高性能异步I/O框架io_uring。
相关参考
- 《Unix网络编程》