五种 I/O 模型:简介与比较 gaunthan Posted on Apr 23 2017 ? I/O ? ? Server Development ? ## 概述 ### 5种 I/O 模型简介 Unix 下有5种可用的I/O模型: I/O 模型|全称|简介 --|-- 阻塞 I/O|blocking I/O|导致请求进程阻塞,直到 I/O 操作完成 非阻塞 I/O|nonblocking I/O|不导致请求进程阻塞 |[I/O 多路复用](http://gaunthan.leanote.com/post/%E6%9E%84%E9%80%A0%E5%B9%B6%E5%8F%91%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%96%B9%E6%B3%95)|I/O multiplexing|select, poll, epoll 等。阻塞请求进程直到指定的多个描述符有状态变更 信号驱动 I/O(SIGIO)|signal-driven I/O|使用信号让内核在描述符就绪时发送 SIGIO 信号通知我们 异步 I/O|asynchronous operation|POXIS的 aio_ 系列函数。不会导致请求进程阻塞,请求提交后立即返回,在 I/O 操作完成后回调| ### 几种 I/O 模型的对比 下图比较了上述5种不同的 I/O 模型。可以看出,前4种模型的主要区别在于第一阶段,它们的第二阶段都是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞于 `recvfrom` 调用。相反,异步 I/O 模型在这两个阶段都要处理,即进程无需阻塞在请求上和数据拷贝上,从而不同于其他4种模型:  ### 阻塞/非阻塞,同步/异步 I/O 模型可以根据**阻塞/非阻塞**,**同步/异步**进行分类。上面提到的5种模型中,前面4种都是同步 I/O 模型,因为其中真正的 I/O 操作(recvfrom)将阻塞进程。只有异步 I/O 模型与 POSIX 定义的异步 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 **阻塞 I/O**(blocking I/O)模型是最流行的 I/O 模型,默认情况下,所有套接字和文件描述符就是阻塞的。阻塞 I/O 将使请求进程阻塞,直到请求完成或出错。  阻塞 I/O 分两个阶段: 1. 等待数据就绪。例如套接字有数据到来,文件描述符可读。 2. 将数据从内核空间拷贝到用户空间。 ## 非阻塞 I/O **非阻塞 I/O**(nonblocking I/O)的含义是显而易见的:如果 I/O 操作会导致请求进程睡眠,则不要把它挂起,而是返回一个错误告诉它(这个错误可能是 EWOULDBLOCK 或者 EAGAIN)。  相比于阻塞 I/O,非阻塞 I/O 的第一个阶段不会阻塞,相反是一直在对非阻塞描述符调用`read` 或 `recvfrom` 等操作。当一个应用进程像这样对一个非阻塞描述符调用 recvfrom 时,我们称之为**轮询**(polling)。 应用进程持续轮询内核,以查看某个操作是否就绪。这样做往往消费大量 CPU 时间,不过这种模型偶尔也会遇到。在嵌入式开发中,非阻塞 I/O 模型比较常见。比如在编写设备驱动的时候,常常会短暂地轮询设备状态寄存器,以等待设备就绪。 ## I/O 多路复用 **[I/O 多路复用](http://gaunthan.leanote.com/post/%E6%9E%84%E9%80%A0%E5%B9%B6%E5%8F%91%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%96%B9%E6%B3%95#title-5)**(I/O multiplexing)会用到 select 或者 poll 函数,这两个函数也会使进程阻塞,但是和阻塞 I/O 所不同的的,这两个函数可以同时阻塞多个 I/O 操作。而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。  I/O 多路复用模型使用了 Reactor 设计模式实现了这一机制。”nonblocking I/O + I/O multiplexing“模型就是 Reactor 模式。[^Reactor] [^Reactor]:[Reactor - An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events](http://www.cs.wustl.edu/~schmidt/PDF/Reactor.pdf) ## 信号驱动 I/O **信号驱动 I/O**(signal-driver I/O)使用信号,让内核在描述符就绪时发送 SIGIO 信号通知我们进行处理。实际上这个处理是自动的,只不过我们可以指定这个处理函数:  这种方式要求进程执行以下3个步骤: 1. 建立 SIGIO 信号的信号处理函数。 2. 设置该套接字的属主,通常使用 `fcntl` 的 F_SETOWN 命令设置。 3. 开启套接字的信号驱动 I/O 功能,通常通过使用 `fcntl` 的 F_SETFL 命令打开 O_ASYNC 标志完成。也可以改用 `ioctl` 的 FIOASYNC 请求。 信号驱动 I/O 模型的优势在于等待数据到达期间进程不被阻塞,进程可以继续执行。相比于非阻塞 I/O,信号驱动 I/O 没有询轮带来的昂贵的 CPU 代价。 ## 异步 I/O **异步 I/O**(asynchronous I/O)由 POSIX 规范定义,包含一系列以 aio 开头的接口。一般地说,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核空间拷贝到用户空间)完成后通知我们。这种模型与信号驱动模型的主要区别是:信号驱动 I/O 是由内核通知我们何时可以启动一个 I/O 操作,而异步 I/O 模型是由内核通知我们 I/O 操作何时完成。  异步 I/O 模型使用了 Proactor 设计模式实现了这一机制。[^Proactor] [^Proactor]:[Proactor - An Object Behavioral Pattern for Demultiplexing and Dispatching Handlers for Asynchronous Events](http://www.cs.wustl.edu/~schmidt/PDF/proactor.pdf) ## References - W. Richard Stevens, Bill Fenner, Andrew M. Rudoff. UNIX 网络编程 [M]. 人民邮电出版社, 2010. - [Java I/O 模型的演进](http://www.importnew.com/21383.html) 赏 Wechat Pay Alipay 客户端/服务器(C/S) 程序设计范式 为 Linux 启用色温和亮度调节工具