前言
本文是笔者的第一篇博文,在这篇文章的大部分内容基于steven大神的《Unix Network Programming》。一来是对书本内容的整理与归纳。二来也是为接下来的博文奠定基础。
在实际应用中,数据操作通常分为输入和输出,那么以输入为例,在操作系统中,一个数据的输入通常分为以下两个过程:
- 等待数据准备好.
- 将准备好的数据从内核拷贝到用户空间
下面我们将会分别讨论 I/O 模型中的两个大类,即 同步 I/O 与 异步 I/O。
图 1. 基本I/O 模型的简单矩阵
同步阻塞I/O
最常用的一个模型是同步阻塞 I/O 模型。其行为非常容易理解,其用法对于典型的应用程序来说都非常有效。在调用 read
系统调用时,应用程序会阻塞并对内核进行上下文切换。然后会触发读操作,当响应返回时(从我们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。然后应用程序就会解除阻塞(read
调用返回)。
图 2. 同步阻塞 I/O 模型
同步非阻塞 I/O
同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read
操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN
或 EWOULDBLOCK
)
图 3. 同步非阻塞 I/O 模型
当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询(polling)。应用进程只需轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间。
I/O复用模型
I/O 复用有时又被称为 事件驱动 I/O, 它的最大优势在于,我们可以将感兴趣的多个I/O事件(更精确的说,应该是 I/O 所对应的文件描述符)注册到 select/poll/epoll/kqueue 之中某一个系统调用上(很多时候,这些系统调用又被称为多路复用器。假设此时我们选择了 select() )。此后,调用进程会阻塞在 select() 系统调用之上(而不是阻塞在真正的 I/O 系统调用(如 read(), write() 等)上)。select() 会负责监视所有已注册的 I/O 事件,一旦有任意一个事件的数据准备好,那么 select() 会立即返回,此时我们的用户进程便能够进行数据的复制操作。
图 4. I/O复用模型
总而言之,I/O 复用的优点就在于可以同时等待多个I/O事件;而缺点是会进行两次系统调用(一次 select(), 一次 read() )。
信号驱动式I/O模型
在这种模型下,我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。改系统调用将立即返回,我们的进程继续工作,也就是说他没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后就可以在信号处理函数中调用read读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。
图 5. 信号驱动I/O模型
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等到来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
异步非阻塞 I/O
异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。读请求会立即返回,说明 read
请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read
的响应到达时,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
图 6. 异步非阻塞I/O模型
在一个进程中为了执行多个 I/O 请求而对计算操作和 I/O 处理进行重叠处理的能力利用了处理速度与 I/O 速度之间的差异。当一个或多个 I/O 请求挂起时,CPU 可以执行其他任务;或者更为常见的是,在发起其他 I/O 的同时对已经完成的 I/O 进行操作。
各种I/O模型的比较
通过上面的讨论可以清楚的看到,同步 I/O 总会有阻塞的过程,这就是“同步”最本质的特征。而如前文所说,异步 I/O 的最大特点在于用户进程均不阻塞。 用户进程告知内核启动某一 I/O 操作, 并让内核全权代为执行(包括等待数据及拷贝数据至用户空间),此后用户进程可以立即执行其它的任何操作。等到所有 I/O 过程执行完成后, 内核会通知用户程。由此可见,在整个过程中,用户进程均不阻塞。
图 7. 各种I/O模型的比较1
图 8 . I/O 模型比较2
I/O模型 | 读写操作和阻塞阶段 |
阻塞I/O | 应用阻塞于读写函数 |
I/O复用 | 应用阻塞于I/O复用系统调用,但可同时监听多个I/O事件。对I/O本身的读写操作是非阻塞的 |
信号驱动I/O | 信号触发读写就绪事件,用户程序执行读写操作。应用没有阻塞阶段 |
异步I/O | 内核执行读写操作并触发读写完成事件。应用没有阻塞阶段 |
参考
《Unix Network Programming》(volume 1)
《使用异步 I/O 大大提高应用程序的性能》