在Unix(Linux)下,可用的I/O模型有五种:

  • 阻塞I/O

  • 非阻塞I/O

  • I/O多路复用:select和poll是属于这种I/O模型。

  • 信号(或事件)驱动I/O

  • 异步I/O

以下将是对上面五种I/O模型进行逐一介绍,但对于网络数据的接收操作而言,五种I/O模型都是分为两个阶段:

 1. 等待数据准备好。

   2. 将准备好的数据,从内核空间考到进程空间。

对于第一步,就是等待数据到达,到达之后,数据就被复制到内核缓冲区;对于第二步,将数据从内核缓冲区复制到进程缓冲区中。

阻塞I/O模型:

   阻塞I/O模型属于最常见的I/O模型,在这五种I/O模型中都可以看到阻塞I/O的身影。默认情况下,所有的网络socket都是阻塞的。下面,我们就演示一下具体的数据处理过程:

在此,对上图的流程做简单的介绍:

   进程对内核发起系统调用(recvfrom),当数据到达网卡并最终被复制到进程空间(或中途发生错误,比如对进程发送一个中断信号等)后,系统调用(recvfrom)就会返回信息给进程,之后,进程再根据返回的信息来进行相应的处理。而进程在收到recvfrom返回信息之前的整个时间段内,我们称,进程被阻塞。当recvfrom返回成功信息时,进程就开始对数据进行处理。

非阻塞I/O模型:

当I/O模型为非阻塞I/O时,那么就相当于告诉内核,当进程请求的数据没完成时,这个进程就不会进入睡眠状态,而是返回一个错误信息。

在此,对上图的流程做简单的介绍:

   前三次调用recvfrom,数据都未准备就绪,因此内核会立即返回一个EWOULDBLOCK的错误信息。第四次调用recvfrom时,数据已经准备就绪。然后数据被复制到进程缓冲区,并且recvfrom返回成功信息。最后,进程对数据进行处理。

   像这样,在非阻塞模型中一个进程反复调用recvfrom的过程,我们将它称为polling。此时,进程会不断的询问内核:是否某个操作已经准备就绪。而通常这又会浪费CPU时间片,所以,使用这种模型的很少见。

I/O多路复用模型:

在I/O多路复用模型下,我们可以使用select或poll系统调用,而此时发生的阻塞是由select或poll产生的,而不是在真正的I/O系统调用上。

在此,对上图的流程做简单的介绍:

   在调用select,进程的一个请求就阻塞了,直到数据准备就绪。当select返回数据就绪信息(readable)时,然后,在调用recvfrom将数据复制到进程缓冲区。

   通过与第一张阻塞I/O模型的图的比较,我们并没有发现多路复用I/O模型有什么优点,并且事实上,还有一个小的缺点,因为使用select时需要两种不同的系统调用。但是使用select的好处是,我们可以同时等待多个I/O的完成。

信号驱动I/O模型:

我们可以使用信号,来告诉内核当数据准备就绪的时候,使用SIGIO信号来通知我们。我们将此称为信号驱动的I/O。

在此,对上图的流程做简单的介绍:

   首先,使用sigaction系统调用安装信号处理器。然后,立即从系统调用中返回,从而进程在继续执行,而不会被阻塞。当数据准备就绪的时候,就会生成SIGIO信号并发送给进程的信号处理器,然后再通过调用recvfrom来读取数据,并最终返回OK由进程对数据进行处理。

异步I/O模型:

一般来说,异步I/O模型的实现是从操作步骤的开始到通知整个操作完成(包括将数据从内核复制到进程缓冲区中)。它和信号驱动I/O的主要不同是:信号I/O是在I/O操作正要开始的时候通知我们的,而异步I/O是当I/O操作完成时通知我们的。

在此,对上图的流程做简单的介绍:

   当调用aio_read时,会同时向内核传递描述符,缓冲区指针,缓冲区大小,文件偏移量和当整个操作完成时该如何通知我们等信息。然后,系统调用立即返回,并且进程在等待I/O完成的时候,不会发生阻塞。直到当操作完成的时候内核就会产生相应的信号,并通知给进程。