CS-Wiki CS-Wiki
Home
知识体系总览
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • MySQL
  • Redis
  • 设计模式
  • Java 基础
  • Java 集合
  • Java 并发
  • Java 虚拟机
  • Spring
  • Kafka
  • 校招扫盲
  • 项目推荐
  • 唠唠嗑儿
  • 读书笔记
归档
GitHub (opens new window)
Home
知识体系总览
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • MySQL
  • Redis
  • 设计模式
  • Java 基础
  • Java 集合
  • Java 并发
  • Java 虚拟机
  • Spring
  • Kafka
  • 校招扫盲
  • 项目推荐
  • 唠唠嗑儿
  • 读书笔记
归档
GitHub (opens new window)
  • 小牛肉的操作系统知识体系结构
  • 入门

  • 进程管理

  • 内存管理

  • 文件管理

  • 输入输出管理

  • 网络通信

    • 背了一年的计网八股,还不知道什么是 Socket?
    • 送分来了,华为一面,介绍下五种 IO 模型
      • Blocking I/O
      • Non-Blocking I/O
      • I/O Multiplexing
      • Signal Blocking I/O
      • Asynchronous I/O
      • 五种 I/O 模型比较
  • 20-操作系统
  • 网络通信
小牛肉
2023-02-16
目录

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

所谓 I/O,就是 Input/Output,输入/输出,在操作系统中,输入输出操作其实并不简单

工作在用户态的应用程序想要读取磁盘中的具体文件内容,就需要经过 System Call(系统调用)陷入内核态

因此,在操作系统中,输入输出操作通常都会包括以下两个阶段:

  1. 准备数据:内核缓冲区准备数据,等待其准备好
  2. 数据拷贝:从内核缓冲区向用户缓冲区复制数据

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

操作系统系统如何去管理输入和输出,从而获取输入和输出的数据?这就是 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 调度执行

第二阶段,数据拷贝:当内核一直等到数据准备好了,它就会将数据从内核空间中拷贝到用户空间,然后系统调用 recvfrom 返回结果,用户进程才解除阻塞的状态,重新运行起来

在上述步骤中,用户进程调用 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

如下图所示:

非阻塞的 recvform 系统调用之后,如果数据还没准备好,应用进程不会被阻塞住,recvfrom 立即返回一个 EWOULDBLOCK 错误。用户进程在收到 recvfrom 调用的返回信息之后,可以干点别的事情,然后再发起 recvform 系统调用。

重复上面的过程,不断地进行 recvform 系统调用。这个过程通常被称之为轮询 (polling)。轮询检查内核数据,直到数据准备好,再拷贝数据到用户进程,进行数据处理。

需要注意的是,当 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 操作。

比较上图和 Blocking I/O,你会发现 I/O Multiplexing 的 I/O 操作和 Blocking I/O 似乎差不多,事实上,IO 多路复用还更差一些,因为这里需要使用两个系统调用 (select 和 recvfrom),而 Blocking IO 只需要一个系统调用 (recvfrom)。

但是,IO 多路复用的优势并不是对单个连接能处理得更快,而是只需要一个进程就可以同时处理多个 I/O,能同时处理更多的连接。

# Signal Blocking I/O

Signal Blocking I/O 就是当用户进程发起 I/O 操作的时候,首先通过系统调用 sigaction 向内核注册一个信号处理函数,这个系统调用会立即返回不会阻塞用户进程;当内核数据准备好了就会发送一个 SIGIO 信号给用户进程,这样用户进程就知道内核数据准备好了,可以开始执行 I/O 系统调用了。

和 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 操作已经完成了

下图给出了一个异步调用的例子:

用户进程进行异步系统调用 aio_read 之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户进程可以去做别的事情。等到数据准备好了,内核直接拷贝数据给用户进程(不需要用户进程再主动发起 recvfrom 系统调用),拷贝完毕后内核才会给用户进程发送通知,告诉用户进程操作已经完成了。

所以,异步 IO 的两个阶段,用户进程都是非阻塞的,用户进程将整个 IO 操作都交由内核完成,内核完成后会发送通知。在此期间,用户进程不需要去检查 IO 操作的状态,也不需要主动的去拷贝数据。

# 五种 I/O 模型比较

本文理清了五种 I/O 模型,并区分了阻塞/非阻塞、同步和异步的概念

最后上张图对比下,加深印象

🎁 公众号

各位小伙伴大家好呀,叫我小牛肉就行,目前在读东南大学硕士,上方扫码关注公众号「飞天小牛肉」,与你分享我的成长历程与技术感悟~

帮助小牛肉改善此页面 (opens new window)
Last Updated: 2023/03/14, 15:26:21
背了一年的计网八股,还不知道什么是 Socket?

← 背了一年的计网八股,还不知道什么是 Socket?

最近更新
01
02
线上环境 CPU 使用率飙升如何快速排查?
03-05
03
面试官再跟你说中国没有根服务器,雪人计划让他了解下
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2023 飞天小牛肉 | 皖ICP备2022008966号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式