送分来了,华为一面,介绍下五种 IO 模型( 二 )


需要注意的是,当 recvfrom 系统调用进行拷贝数据的时候,用户进程同样是被阻塞住的 。
因此,Non-Blocking I/O 的特点就是用户进程需要不断的主动询问内核数据准备好了没有,可以简单记忆为 “IO 执行的第一阶段用户进程非阻塞,第二阶段用户进程阻塞”
I/O Multiplexing由于 Non-Blocking I/O 需要不断主动轮询,轮询会消耗大量的 CPU 时间,而后台可能有多个任务在同时进行,人们就想到了循环查询多个任务的完成状态,只要有任何一个任务完成,就去处理它 。这就是 I/O Multiplexing 。
I/O Multiplexing 引入了新的系统调用 select/poll/epoll(也成为多路复用器),这几个系统调用也是重点,不过本文就不过多阐述了 。
具体来说,I/O Multiplexing 就是将多个应用进程的 Socket 注册到一个多路复用器(select/poll/epoll)上,然后使用一个进程来监听该多路复用器,多路复用器会不断的轮询所有注册进来的 Socket,只要有一个 Socket 的数据准备好,就会返回该 Socket 。再由应用进程发起真正的 IO 系统调用(也就是 recvfrom,和 Blocking I/O 一样),来完成数据读取 。
简单来说,I/O Multiplexing 就是同时阻塞了多个应用进程,而且可以同时对多个 Socket 进行检测,直到有数据可读或可写时,才真正开始 I/O 操作 。

送分来了,华为一面,介绍下五种 IO 模型

文章插图
比较上图和 Blocking I/O,你会发现 I/O Multiplexing 的 I/O 操作和 Blocking I/O 似乎差不多,事实上,IO 多路复用还更差一些,因为这里需要使用两个系统调用 (select? 和 recvfrom?),而 Blocking IO 只需要一个系统调用 (recvfrom) 。
但是,IO 多路复用的优势并不是对单个连接能处理得更快,而是只需要一个进程就可以同时处理多个 I/O,能同时处理更多的连接 。
Signal Blocking I/OSignal Blocking I/O 就是当用户进程发起 I/O 操作的时候,首先通过系统调用 sigaction? 向内核注册一个信号处理函数,这个系统调用会立即返回不会阻塞用户进程;当内核数据准备好了就会发送一个 SIGIO 信号给用户进程,这样用户进程就知道内核数据准备好了,可以开始执行 I/O 系统调用了 。
送分来了,华为一面,介绍下五种 IO 模型

文章插图
和 Non-Blocking I/O 一样,信号驱动 IO 的用户进程在 I/O 的第一阶段准备数据是非阻塞的,在第二阶段数据拷贝是阻塞的
不过信号驱动 IO 基于回调机制,其实现和开发应用难度大,因此在实际中并不常用 。
Asynchronous I/O异步 I/O,先来解释下什么是异步?
POSIX 的定义如下:
  • 同步 I/O 操作(synchronous I/O operation):导致请求进程阻塞,直到 I/O 操作完成
  • 异步 I/O 操作(asynchronous I/O operation):不导致请求进程阻塞
根据这个定义,我们可以做一个分类了,那就是上述四种 I/O 都是同步 I/O!因为它们无一例外都会在第二阶段阻塞住用户进程直到 I/O 操作完成 。
这就是为什么你会看见有人把 “阻塞 I/O” 称之为 “同步 阻塞 I/O”,把 “非阻塞 I/O” 称之为 “同步 非阻塞 I/O” 了
而异步 IO 所谓的在整个 I/O 操作期间都不会阻塞用户进程,其通常的工作机制是:
用户进程告知内核启动某个 I/O 操作,并让内核在整个操作(包括将数据从内核复制到用户缓冲区)完成后通知用户进程 。
这与 Signal Blocking I/O 的本质区别就是:
  • Signal Blocking I/O 是在数据准备好了之后进行通知,告知应用进程可以启动 I/O 操作进行拷贝数据了
  • Asynchronous I/O 是在整个 I/O 操作完成了之后进行通知,告知应用进程 I/O 操作已经完成了
下图给出了一个异步调用的例子:
送分来了,华为一面,介绍下五种 IO 模型

文章插图
用户进程进行异步系统调用 aio_read? 之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户进程可以去做别的事情 。等到数据准备好了,内核直接拷贝数据给用户进程(不需要用户进程再主动发起 recvfrom 系统调用),拷贝完毕后内核才会给用户进程发送通知,告诉用户进程操作已经完成了 。
所以,异步 IO 的两个阶段,用户进程都是非阻塞的,用户进程将整个 IO 操作都交由内核完成,内核完成后会发送通知 。在此期间,用户进程不需要去检查 IO 操作的状态,也不需要主动的去拷贝数据 。


推荐阅读