2

IO模型:同步、异步、阻塞、非阻塞

 2 years ago
source link: https://songlee24.github.io/2016/07/19/explanation-of-5-IO-models/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

IO模型:同步、异步、阻塞、非阻塞

发表于 2016-07-19

  |   分类于 System-Linux

  |  

前言:

在Linux的网络编程中,同步IO(synchronous IO)、异步IO(asynchronous IO)、阻塞IO(blocking IO)、非阻塞IO(non-blocking IO)究竟是什么?它们之间又有什么联系和区别? 本文是我对这个问题的答案整理的笔记,参考《UNIX网络编程.卷1》

一、IO模型

在《UNIX网络编程.卷1》第6.2节介绍了五种IO模型,分别是:

  • 阻塞式IO(blocking IO)
  • 非阻塞式IO(non-blocking IO)
  • IO复用(IO multiplexing)
  • 信号驱动式IO(signal driven IO)
  • 异步IO(asynchronous IO)

通常一个 socket 上的读操作包含两个阶段:

  1. 等待数据准备好;
  2. 将数据从内核拷贝到进程中。

上述几种IO模型就是在这两个阶段上各有不同的情况。

1.1 阻塞式IO

默认情况下,Linux下的所有socket都是阻塞的。以 UDP 的recvfrom调用为例:

当进程调用recvfrom时,该函数直到①数据报到达且被复制到应用进程缓冲区;②或者发生错误(比如被信号中断)才返回。

所以,阻塞式IO的特点就是在I/O执行的两个阶段都被阻塞了——阻塞等待数据,阻塞拷贝数据。

1.2 非阻塞式IO

Linux下可以通过fcntl将 socket 设置为非阻塞模式。

当对一个非阻塞 socket 执行读操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个EWOULDBLOCK错误;如果内核中有数据准备好了,它会立即将数据拷贝到用户内存,并成功返回。

由于非阻塞I/O在没有数据时会立即返回,故用户进程通常需要循环调用recvfrom,不断地主动询问内核数据是否ready。

所以,非阻塞式IO的特点是在I/O执行的第一个阶段不会阻塞线程,但在第二阶段会阻塞。

1.3 IO复用

IO复用(IO multiplexing),也称事件驱动IO(event-driven IO),就是在单个线程里同时监控多个套接字,通过 select 或 poll 轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

可以看出,进程阻塞在select调用上,等待有套接字变为可读;当有套接字可读以后,调用recvfrom把数据报从内核复制到用户进程缓冲区,此时进程阻塞在IO执行的第二个阶段。

如上图整个用户进程其实是一直被阻塞的,但IO复用的优势在于可以等待多个描述符就绪。

所以,IO复用的特点是进行了两次系统调用,进程先是阻塞在 select/poll 上,再是阻塞在读操作的第二个阶段上。

1.4 信号驱动式IO

信号驱动式IO(signal-driven IO),就是让内核在描述符就绪时发送SIGIO信号通知用户进程。

首先需要开启 socket 的信号驱动式IO功能,然后通过sigaction系统调用注册SIGIO信号处理函数 —— 该系统调用会立即返回。当数据准备好时,内核会为该进程产生一个SIGIO信号,这时就可以在信号处理函数中调用 recvfrom 读取数据了。

所以,信号驱动式IO的特点就是在等待数据ready期间进程不被阻塞,当收到信号通知时再阻塞并拷贝数据。

1.5 异步IO

异步IO(asynchronous IO)其实用得很少,在Linux 2.5 版本的内核中首次出现,在 2.6 版本的内核中才成为标准特性。

用户进程在发起aio_read操作后,该系统调用立即返回 —— 然后内核会自己等待数据ready,并自动将数据拷贝到用户内存。整个过程完成以后,内核会给用户进程发送一个信号,通知IO操作已完成。

异步IO与信号驱动式IO的主要区别是:信号驱动式IO是由内核通知我们何时启动一个IO操作,而异步IO是由内核通知我们IO操作何时完成。

所以,异步IO的特点是IO执行的两个阶段都由内核去完成,用户进程无需干预,也不会被阻塞。

1.6 五种IO模型的比较

可以看出,前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:都是阻塞于recvfrom调用,将数据从内核拷贝到用户进程缓冲区。

二、阻塞vs非阻塞,同步vs异步

回到本文开头的那个问题:同步IO、异步IO、阻塞IO、非阻塞IO究竟是什么?它们之间又有什么联系和区别?

阻塞IO vs 非阻塞IO

上面介绍阻塞式IO模型、非阻塞式IO模型时已经说明了两者的区别:

  • 阻塞I/O会一直阻塞用户进程直到操作完成
  • 非阻塞I/O在内核的数据还没准备好的情况下会立即返回

同步IO vs 异步IO

POSIX是这样定义的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes. —— 同步IO操作导致进程阻塞,直到IO操作完成。
  • An asynchronous I/O operation does not cause the requesting process to be blocked. —— 异步IO操作不导致进程阻塞。

上面定义中的I/O operation是指真正的I/O系统调用,比如recvfrom,所以阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型、信号驱动式I/O模型都属于同步I/O。—— 只有异步I/O模型属于POSIX定义的异步I/O,因为在异步I/O模型中,用户进程是将整个I/O操作都交给内核来完成,内核完成后发信号通知,在此期间用户进程完全不用去理会。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK