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