网络I/O模型

Unix 系统下共有 5 种 I/O 模型:同步阻塞式 I/O、同步非阻塞式 I/O、I/O 多路复用(select 和 poll)、信号驱动式 I/O(SIGIO) 和异步 I/O(POSIX 的 aio_系列函数)

要了解 I/O 模型,首先要知道什么是 I/O。查看 wikipedia,能看到如下解释

I/O(英语:Input/Output),即输入/输出,通常指数据存储器(内部和外部)或其他周边设备之间的输入和输出,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。该术语也可以用作行动的一部分;到“运行I/O”是运行输入或输出的操作。

简单来说,I/O 就是指计算机内存与外部设备之间的数据交互过程。比如说:将磁盘上的文件读入内存中、将内存中的数据写入磁盘。这里所说的外部设备还可以是网卡、显示设备等等。

由于 CPU的速度 > 内存的速度 > 外部设备的速度,所以在 I/O 过程中,就会存在速度不匹配的问题。在发出 I/O 请求后,CPU 可以选择不同的处理策略:可以是继续等待结果;也可以是直接返回,然后不断轮询看数据是否到达;还可以注册一个函数等待数据到达后的回调。不同的策略对 CPU 的利用率有很大的影响。

考虑一次网络 I/O 过程,当用户线程通过系统函数调用(recvfrom、select等)发起 I/O 请求后,需要等待网络数据到达后,内核将数据复制到内核空间,然后再将数据从内核空间复制到用户空间。不同 I/O 模型的主要区别就在于对这两个过程的处理。

网络I/O过程

IO 模型

同步阻塞式 I/O

用户线程发起 I/O 请求后阻塞,内核等待数据到达后,将数据复制到内核空间,然后再复制到用户空间,完成后将用户线程唤醒

image-20200916014552991

同步非阻塞式 I/O

用户线程把 socket 设置为非阻塞,然后发起 I/O 请求,当数据未准备好时,内核会直接返回一个错误标志。用户线程持续轮询内核,一旦数据数据已准备好,本次请求就需要等待数据从内核空间复制到用户空间,之后会返回一个成功标志

image-20200916014926196

I/O 多路复用

I/O 多路复用是通过 select(或者 poll)管理多个 I/O 请求,当没有数据准备好时,用户线程阻塞在 select 调用上,若某个数据已准备好,则可以发起 recvfrom 系统调用,将数据从内核空间复制到用户空间

image-20200916015631779

I/O 多路复用与同步阻塞式 I/O 有些相似,区别是阻塞式 I/O 中,用户线程是阻塞在一个 I/O 请求上,而 I/O 多路复用中,用户线程是阻塞在 select 调用中,select 本身管理了多个 I/O 请求。

信号驱动式 I/O

通过 sigaction 系统调用向内核注册 SIGIO 处理函数,让内核在数据准备就绪时通知用户线程,用户线程在收到通知后,直接通过 recvfrom 系统调用发起 I/O 请求,将数据从内核空间复制到用户空间。和同步阻塞式 I/O 相比,在等待数据准备好的这段时间,用户线程不用持续轮询内核,可以去处理自己的事情。

image-20200916020218622

异步 I/O

通过 aio_read 系统调用通知内核开启 I/O 操作,内核会完成所有的 I/O 操作,在将数据从内核空间复制到用户空间后,内核会通知用户线程 I/O 已完成。与信号驱动式 I/O 的区别是,异步 I/O 整个过程都是由内核完成的。

image-20200916020705812

总结

一次网络 I/O 可分为两个阶段:发起 I/O 请求、执行 I/O 处理。

对于 I/O 的执行过程,可以再分为两个阶段:等待数据就绪写入内核缓冲区、将数据从内核缓冲区拷贝到用户缓冲区。

最简单的 I/O 模型是 BIO,调用者在发起 I/O 后一直阻塞,直到数据复制到用户缓冲区。缺点是阻塞调用者线程,若要提高并发度,需要创建较多的线程,而线程上下文切换是需要代价的。

在 NIO 模型中,调用者发起 I/O 后直接返回,不会阻塞,需要调用者不断轮询。待数据复制到内核缓冲区后,再次轮询则会阻塞,等待数据复制到用户缓冲区。缺点是在数据未准备好时需要轮询,空轮询会增大额外开销。

在 I/O 多路复用模型中,一个 selector 管理了多个 channel。只有当所有的 channel 上的数据都未准备好时,调用 select 操作才会阻塞,否则可以取到一个准备好数据的 channel 来处理。数据从内核空间复制到用户空间的过程还是会阻塞。这是最常用的一种 I/O 模型。

在信号驱动 I/O 模型中,调用者需要注册信号处理程序。待数据准备好后,内核会调用该处理程序,此时可以将数据从内核空间复制到用户空间。等待数据复制的过程中仍然是阻塞的。

异步 I/O 实现了真正的异步操作,调用者发起请求后由内核处理整个 I/O 过程,待数据从内核空间复制到用户空间后再通知调用者。