在Linux下有五种IO模型,分别是:阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型和异步IO模型。接下来就来细说下这五种模型。

(一)阻塞IO模型:

       在linux中,默认情况下所有的socket都是阻塞模型。应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好。如果数据没有准备好,一直等待。 数据准备好了,从内核拷贝到用户空间,IO函数返回成功指示。所以,阻塞IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被阻塞了.

wKiom1eYdELytuEZAAC6OFN3VUk716.png

(二)非阻塞IO模型:

上面所说的阻塞IO其实可以通过设置socket使其变为非阻塞IO,当对一个非阻塞IO进行读操作时,它的流程如下:

wKioL1eYdFzjOW32AAEGcUVhZkY087.png-wh_50

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
 所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有 非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。

使用如下的函数可以将某句柄fd设为非阻塞状态:  fcntl( fd, F_SETFL, O_NONBLOCK ); 

(三)IO复用模型:

IO复用模型又叫多路复用模型,或者事件驱动IO,IO复用模型会用到select或者poll函数,这两个函数也会使进程阻塞,但是和阻塞IO所不同的的,这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作, 多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数 。简单来说其基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

wKiom1eYdH7TgTQCAADr_emaqys089.png

         这个图和阻塞IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句:所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

(四)信号驱动IO模型:

首先我们允许套接口进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个wKioL1eYdKnTnzjqAADJUeK7Wvk497.png          SIGIO信号,可以在信号处理函数中调用IO操作函数处理数据。

(五)异步IO模型:

 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个信号,告诉它读操作完成了。

wKioL1eYdOPS3XWPAADJUeK7Wvk880.png

异步IO是真正非阻塞的,它不会对请求进程产生任何的阻塞,因此对高并发的网络服务器实现至关重要。

五种模型对比:

前四种模型的区别是第一阶段基本相同,第二阶段基本相同,都是将数据从内核拷贝 到调用者的缓冲区。而异步I/O的两个阶段都不同于前四个模型。 

      阻塞IO会一直阻塞住当前的进程直到操作完成。非阻塞IO,IO请求时加上O_NONBLOCK一类的标志位,立刻返回,IO没有就绪会返回错误,需要请求进程主动轮询不断发IO请求直到返回正确。IO复用同非阻塞IO本质一样,不过利用了新的select系统调用,由内核来负责本来是请求进程该做的轮询操作。看似比非阻塞IO还多了一个系统调用开销,不过因为可以支持多路IO,才算提高了效率。信号驱动IO,调用sigaltion系统调用,当内核中IO数据就绪时以SIGIO信号通知请求进程,请求进程再把数据从内核读入到用户空间,这一步是阻塞的。
      异步IO,如定义所说,不会因为IO操作阻塞,IO操作全部完成才通知请求进程。