UNIX 当中的5种 I/O 模型

  • blocking IO:阻塞 IO
  • nonblocking IO:非阻塞 IO
  • IO multiplexing:IO 多路复用
  • signal driven IO:信号驱动 IO
  • asynchronous IO:异步 IO

以下简单的描述下这5种 IO 的区别:

blocking IO

在linux中,默认情况下所有的 socket 都是 blocking,一个典型的读操作流程大概是这样:

当用户进程调用了 recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的 UDP 包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。

所以,blocking IO 的特点就是在 IO 执行的两个阶段都被 block 了。

non-blocking IO

linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行读操作时,流程是这个样子:

从图中可以看出,当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一 个read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,用户进程其实是需要不断的主动询问 kernel 数据好了没有。

IO multiplexing

IO multiplexing 这个词可能有点陌生,但是如果我说 select,epoll,大概就都能明白了。有些地方也称这种 IO 方式为 event driven IO。我们都知道,select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select/epoll 这个 function 会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流程如图:

Linux 提供 select/poll,进程通过将一个或多个 fd (file descriptor) 传递给 select 或者 poll 系统调用,阻塞在 select 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,select/poll 是顺序扫描 fd,并且支持的 fd 数量有限。Linux 还提供了 epoll 函数, epoll 使用了事件驱动方式代替顺序扫描,性能更好,当 fd 就绪,立即执行回调 rollback。

当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。

这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

用 select 的优势在于它可以同时处理多个 connection。

如上图所示,整个用户的 process 其实是一直被 block 的。只不过 process 是被 select 这个函数 block,而不是被 socket IO 给 block。

signal driven IO

首先开启 socket 接口信号驱动 IO 功能,并且通过系统调用 sigaction 执行一个信号处理函数(立即返回,进行继续工作,非阻塞)。当数据准备就绪,就为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据。

Asynchronous I/O

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

Java IO 演进

  • jdk 1.4 之前,基于 java 的 socket 通信都是 BIO。
  • jdk 1.4 提供了 NIO,可以支持非阻塞 IO。
  • jdk 1.7 升级 NIO,提供了对文件的更高效支持,并且提供了 AIO 功能。

【参考资料】

  1. http://blog.csdn.net/historyasamirror/article/details/5778378
  2. https://book.douban.com/subject/26373138/

—EOF—