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

所谓 I/O,就是 Input/Output,输入/输出,在操作系统中,输入输出操作其实并不简单
工作在用户态的应用程序想要读取磁盘中的具体文件内容,就需要经过 System Call(系统调用)陷入内核态
因此,在操作系统中,输入输出操作通常都会包括以下两个阶段:

  1. 准备数据:内核缓冲区准备数据,等待其准备好
  2. 数据拷贝:从内核缓冲区向用户缓冲区复制数据
以网络通信即 Socket 上的输入操作为例,对应的第一阶就是等待数据从网络中到达网卡(对于网络 I/O 来说,很多时候数据在一开始还没有到达 。比如,还没有收到一个完整的 TCP 包 。这个时候内核就要等待足够的数据到来),然后从网卡中将数据拷贝到内核缓冲区,这样,数据就准备就完成了;第二阶段就是把数据从内核缓冲区复制到用户缓冲区 。
送分来了,华为一面,介绍下五种 IO 模型

文章插图
操作系统系统如何去管理输入和输出,从而获取输入和输出的数据?这就是 I/O 模型 。
linux 中有以下五种 I/O 模型:
  • Blocking I/O:阻塞 I/O
  • Non-Blocking I/O:非阻塞 I/O
  • I/O Multiplexing:I/O 多路复用
  • Signal Blocking I/O:信号驱动 I/O
  • Asynchronous I/O:异步 I/O
Blocking I/O
在 Linux 中,默认情况下所有的 Socket 都是 Blocking,它符合人们最常见的思考逻辑 。
上面我们介绍了输入输出操作通常都会包括两个阶段,并不是凭空想想,而是对应具体的 I/O 系统调用的,以网络通信为例,Blocking I/O 就对应阻塞的系统调用 recvfrom
第一阶段,准备数据:当用户进程通过系统调用 recvfrom 进行数据读取,操作系统就开始了 I/O 的第一个阶段-准备数据 。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的 。而在用户进程这边,整个进程会被阻塞住
解释下 阻塞 的概念:源自操作系统对进程/线程状态的描述概念,其定义为:操作系统把进程/线程从“运行(running)状态” 挂起为 “阻塞(blocked)状态”(又称“等待(waiting)状态”) 。当进程/线程处于阻塞状态,则意味着其处于暂停运行状态,暂时不会被 CPU 调度执行
【送分来了,华为一面,介绍下五种 IO 模型】第二阶段,数据拷贝:当内核一直等到数据准备好了,它就会将数据从内核空间中拷贝到用户空间,然后系统调用 recvfrom 返回结果,用户进程才解除阻塞的状态,重新运行起来
送分来了,华为一面,介绍下五种 IO 模型

文章插图
在上述步骤中,用户进程调用 recvfrom,该系统调用直到数据准备好且被复制到用户缓冲区中才返回 。
从调用 recvfrom 开始,到它返回数据的整段时间,用户进程都是被阻塞住的!这就是 Blocking I/O 的特点,可以简单记忆为 “IO 执行的两个阶段用户进程都被阻塞住了”
recvfrom 成功返回后,用户进程才开始继续处理 。
Non-Blocking I/O参考《Unix 网络编程:第一卷》,书中是这样描述 Non-Blocking I/O 的:
"进程把一个套接字设置成非阻塞是在通知内核,当所请求的 I/O 操作非得把本进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误"
意思就是,如果某个用户进程进行系统调用 recvform 尝试获取数据,但这时候数据还没准备好:
  • 如果操作系统把这个进程挂起,那就是 Blocking I/O
  • 如果操作系统选择立即给用户进程返回错误信息,那就是 Non-Blocking I/O
如下图所示:
送分来了,华为一面,介绍下五种 IO 模型

文章插图
非阻塞的 recvform? 系统调用之后,如果数据还没准备好,应用进程不会被阻塞住,recvfrom? 立即返回一个 EWOULDBLOCK? 错误 。用户进程在收到 recvfrom 调用的返回信息之后,可以干点别的事情,然后再发起 recvform 系统调用 。
重复上面的过程,不断地进行 recvform 系统调用 。这个过程通常被称之为**轮询 (polling)** 。轮询检查内核数据,直到数据准备好,再拷贝数据到用户进程,进行数据处理 。


推荐阅读