Linux的五种I/O模型

一次I/O访问中,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

数据被拷贝到操作系统内核的缓冲区中称为数据准备阶段

数据从操作系统内核的缓冲区拷贝到应用程序的地址空间称之为数据处理阶段

(一)同步阻塞I/O

同步阻塞I/O模型中,应用程序执行一个系统调用,会导致应用程序阻塞,直到数据准备好。在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络IO。

(二)同步非阻塞I/O

非阻塞IO会调用recvform系统调用,进程并没有被阻塞.

调用非阻塞的recvform系统调用后,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。进程采用轮询(polling)方式来确定数据是否准备好。

(三)I/O多路复用

由于同步非阻塞方式需要不断轮询,轮询占了很大一部分过程,会消耗大量的cpu时间。后台可能有多个任务在同时进行,因此可以同时循环查询多个任务的完成状态,只要有一个任务完成,就去处理。

多个任务的查询可以交由内核来进行。unix/linux提供了poll,select,epoll来对多个读操作,多个写操作的I/O函数进行检测,当有数据准备好时,这时候用户进程就可以被唤醒。kqueue与epoll类似,mac上没有epoll,但有和epoll类似的kqueue。

这其中用户进程进入等待状态,然后调用select等函数获取数据就绪状态消息,并且其进程状态为阻塞

(四)信号驱动式IO

允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。将数据拷贝到程序空间时会阻塞。

(五)异步非阻塞IO

用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。

libevent、libev、libuv等异步库可以做到以上操作。

综合对比

同步阻塞I/O。执行系统调用,它的进程在数据准备阶段就阻塞等待数据。 同步非阻塞I/O。进程不阻塞,而是采用polling轮询的方式去不断查询数据是否准备好 I/O多路复用。由内核去不断轮询多个进程的数据准备情况,进程阻塞等待,哪个进程数据准备好了,就执行该进程。 信号驱动式。进程继续运行,当数据准备好时,内核信号通知进程。 异步非阻塞I/O。前4个I/O模型中数据处理阶段都是阻塞的,而异步非阻塞I/O数据准备好之后,内核直接拷贝到程序空间。然后通知进程。