谈谈I/O中的同步、异步、阻塞、非阻塞和I/O多路复用

谈到I/O模型,我们马上能联想到的是同步、异步,阻塞和非阻塞之类的模型。但是这些模型之间到底有什么区别,很多人只能说对一部分,但说不全,或者理解上有偏差。本文将详细剖析这其中的区别。

1.阻塞I/O (Blocking I/O)

阻塞I/O

阻塞I/O的典型执行过程如下:

  1. 用户进程发起recvfrom系统调用,内核执行该系统调用。
  2. 内核等待数据就绪。
  3. 数据就绪。
  4. 内核将数据从内核缓冲区拷贝到用户进程缓冲区,返回成功。
  5. 用户进程从用户进程缓冲区读取数据,处理数据,接着往下执行。

从中这个过程中我们可以观察到,用户进程在发起recvfrom系统调用后,直到数据被内核从内核缓冲区拷贝到用户进程缓冲区这整个过程中,都无法继续执行,处于被阻塞的状态,所以这是一个阻塞(Blocking)I/O。而且我们发现上面的I/O过程在内核部分分为两个步骤:

  1. 内核等待数据就绪(阻塞)
  2. 内核将数据从内核缓冲区拷贝到用户进程缓冲区(阻塞)

阻塞I/O在这两个步骤上都被阻塞了。

2.非阻塞I/O (Blocking I/O)

非阻塞I/O

非阻塞I/O的执行过程如下:

  1. 用户进程发起recvfrom系统调用,内核执行该系统调用,并直接返回一个数据未就绪状态给用户进程,然后内核等待数据就绪。
  2. 在内核等待数据就绪过程中,用户进程不断发起recvfrom系统调用(轮询),向内核查询数据是否就绪了,如果数据还未就绪,内核还是返回一个数据未就绪状态给用户进程。
  3. 内核的数据就绪了,内核将数据从内核缓冲区拷贝到用户进程缓冲区,并返回成功。
  4. 用户进程从用户进程缓冲区读取数据,处理数据,接着往下执行。

这里和阻塞I/O的不同点在于,用户进程发起recvfrom系统调用后,内核会直接返回,如果数据未就绪就返回未就绪的状态,如果数据就绪就从内核缓冲区拷贝数据到用户进程缓冲区中。对于I/O过程中内核执行阶段的两个部分:

  1. 内核等待数据就绪(非阻塞)
  2. 内核将数据从内核缓冲区拷贝到用户进程缓冲区(阻塞)

我们发现,在第一阶段,用户进程不会被阻塞,但在第二阶段用户进程被阻塞了。

3. I/O多路复用 (I/O multiplexing)

I/O多路复用

I/O多路复用的执行过程如下:

  1. 用户进程发起select系统调用,内核执行该系统调用。
  2. 内核等待数据就绪。
  3. 内核数据就绪,返回数据就绪。
  4. 用户进程发起recvfrom系统调用,内核将数据从内核缓冲区拷贝到用户进程缓冲区,返回成功。
  5. 用户进程从用户进程缓冲区读取数据,处理数据,接着往下执行。

当用户进程发起select系统调用时,内核会负责监控该select系统调用维护的所有socket,当有任何一个socket数据就绪时,直接返回socket就绪。之后的过程和阻塞I/O和非阻塞I/O是相同的。I/O多路复用的关键在于“多路复用”,select系统调用可以同时监控多个(Linux中一般不超过1024,这个数字定义在FD_SETSIZE中)socket的状态,当有任何一个socket就绪就处理之。

可以发现使用select系统调用时,用户进程会被阻塞在select上(也可以设置一个超时时间),当有socket就绪,用户进程调用recvfrom,用户进程又会被阻塞在数据拷贝上。因此在内核部分,I/O多路复用的执行情况如下:

  1. select系统调用(阻塞)
  2. 内核将数据从内核缓冲区拷贝到用户进程缓冲区(阻塞)

这两个阶段用户进程都被阻塞了。

4. 异步I/O (Asynchronous I/O)

异步I/O

异步I/O的执行过程如下:

  1. 用户进程发起异步I/O系统调用,内核直接返回并执行系统调用,用户进程此时可以继续往下执行而不会被阻塞。
  2. 内核等待数据就绪。
  3. 内核的数据就绪了,内核将数据从内核缓冲区拷贝到用户进程缓冲区,并发送信号通知用户进程数据可用。
  4. 用户进程的信号处理程序处理数据。

异步I/O过程中,用户进程从头到尾都完全没有被阻塞。

总结

严格来说,上面展示的I/O模型的1,2,3都属于同步I/O,因为它们多多少少在某些地方被阻塞了,真正的异步I/O是用户进程完全不能被阻塞的。人们经常谈论到的阻塞和非阻塞实际上只考虑了数据就绪这一部分而没有考虑数据复制,我们要看到I/O调用在内核中还分成了不同部分,而这些不同就是决定其到底是同步I/O还是异步I/O的关键。按照这种标准,Linux下实际上没有真正的异步I/O,因为所有I/O方案在复制阶段都有阻塞,而Windows实现了IOCP,可以说是真正的异步I/O。