type
status
date
slug
summary
tags
category
icon
password
进度
专业术语 📖IOIO模型Blocking IONon-Blocking IOIO MultiplexingAsynchronous I/OSignal-Driven I/O 流程化交互同步异步零拷贝磁盘高速缓存(PageCache)系统调用openwritereadfsyncmmapio_uringepoll
专业术语 📖
- 轮训 Polling
- 阻塞 Blocking
- 非阻塞 Non-Blocking
- 复用 Multiplexing ˈməltēˌpleks
- 同步 Synchronous ˈsiNGkrənəs
- 异步 Asynchronous āˈsiNGkrənəs
- 发起方 Initiator
- 接受方 Receiver
- SG DMA Scatter-Gather Direct Memory Access
Scatter: 连续内存空间 Gather:非连续地址空间。SG DMA允许在不需要CPU参与的情况下,将数据从多个源地址传输到多个目的地地址。常用于网络IO、磁盘IO等高速传输场景。
- 机械硬盘 HDD Hard Disk Drive
- 固态硬盘 SSD Solid State Drive
- 非易失性内存主机控制器接口规范 NVMe
- 串行高级技术附件 SATA
- Epoll Event Poll
IO
Linux系统主要IO有磁盘IO、网络IO、内存IO、外设IO、GPUIO等。所谓IO就是对存储设备中数据读取和写入(输入和输出)。
关键点:IO设备是否准备完成;若读取数据时,数据未准备完成,该如何处理?等待 or 返回?
IO模型
Blocking IO
为完成功能,发起方发起一个调用。若功能无法立即完成,则调用不返回,一直等待。

- 发起系统调用后,内核读取IO设备数据。若数据未准备好,则一直等待。
- 内核读取IO设备数据是同步的
- 用户进程发起IO操作,用户进程被挂起(wait for data)
- 内核发起实际IO操作,等待IO操作完成(wait for data)
- 实际IO操作完成,copy数据到用户空间(copy data from kernel to user)
- 唤醒用户进程执行
用户进程被挂起,直到实际IO操作完成。因此是阻塞IO。
Non-Blocking IO
为完成功能,发起方发起一个调用。若功能无法立即完成,但立即返回特定错误。发起方轮训。

- 发起方轮训,直到服务方数据Reader
- 用户进程发起IO操作,设置non-blocking参数
- 内核数据未Ready,返回特定错误。同时内核发起实际IO操作。用户进程轮训(wait for data)
- 内核数据Ready,用户进程将被挂起,内核复制数据到用户空间(copy data from kernel to user)
- 唤醒用户进程继续执行
相比BIO,NIO在数据non-ready时未挂起,而是轮训。
IO Multiplexing
IO多路复用表示多个IO操作复用一个进程。

- 内核提供多路复用的系统调用
select poll epoll
由内核负责监听提交的fd信息。当监听事件发生时,返回fd。
- 用户进程调用select系统调用,内核监听fd数据non-ready,挂起用户进程(wait for data)
- 内核监听的fd的数据ready时,唤醒可读的fd。(wait for data)
- 用户进程发起读取系统调用,内核复制数据到用户空间(copy data from kernel to user)
IO多路复用的优势就是单进程可以同时处理大量连接。
Asynchronous I/O

- AIO执行IO操作时,数据non-ready时直接返回,不挂起用户进程。
- 内核等待数据ready,内核复制数据到用户空间
- 重点:复制完毕通知用户进程处理(一种回调机制)
- 用户进程发起IO操作,执行系统调用,直接返回,同时内核准备数据。(wait for data)
- 数据Ready,内核复制数据到用户空间(发起时的AIO控制块)。(copy data from kernel to user)
- 复制完毕,发送信号回调到信号处理器。
Signal-Driven I/O
要点:向内核注册<信号,信号处理函数>

- 注册 <single, single handler>
- 数据ready发送信号
- 用户进程调用sigaction系统调用来注册信号处理程序,信号为SIGIO,不挂起用户进程,立即返回。
- 数据Ready,发送SIGIO
- 用户发起IO操作,准备读取数据,内核挂起用户进程,复制数据到用户进程。
- 复制完毕,唤醒用户进程。

BIO, NIO, IO Multiplexing, SIGIO 在第1阶段不同,第2阶段都是阻塞的。(看图品品)
流程化交互
同步
一个流程包含多个功能,顺序执行,完成一个任务之后才可以执行下个任务。
特点:
操作流程虽然简单,但是效率相对低
异步
一个流程包含多个功能,无序执行,无论上个任务是否完成,都可以执行下一个任务。
特点:
操作流程相对复杂,但是效率相对高
IO模型 | 理解 |
BIO | 同步阻塞IO |
NIO | 异步阻塞IO |
IO Multiplexing | 异步阻塞IO |
SIGIO | 异步阻塞IO |
AIO | 异步非阻塞IO |

CPU、Mem的访问耗时是ns级,相对而言IO设备慢很多。

- 2次CPU拷贝,2次DMA拷贝。该传输方式效率太低,目标是将磁盘数据传输到网卡中,但是却将数据搬运4次。
- 4次上下文切换(上下文耗时十几纳秒或者几微秒,高并发场景,上下文时间会积累和放大,负载越高耗时也会增大,形成螺旋)。
🤔IO操作为啥需要缓存区?当然是提效啦!
Linux IO设备主要有字符设备(键盘、鼠标、打印机)、块设备(磁盘、UDB、SSD)和网络设备(网卡)。耗时如下:
设备类型 | 类型 | 耗时估计 |
块设备 | HDD | 5-20ms |
ㅤ | SATA SSD | 0.1-1ms |
ㅤ | NVMe SSD | 0.02-0.1ms |
字符设备 | 串口 | 1-10ms |
ㅤ | 键盘鼠标 | 1-10ms |
ㅤ | 显示器/显卡 | 1-10ms |
网络设备 | 广域网 | 10-100ms |
ㅤ | 局域网 | 0.1-1ms |
ㅤ | Wi-Fi | 1-10ms |
ㅤ | 4G/5G | 20-50ms |
零拷贝
如果可以直接磁盘数据转发到网络,则完全不需要CPU参与Copy,性能则大大提高。
- mmp + write
- sendfile

- mmp + write (1次CPU Copy,4次上下文切换)
- mmp 系统调用将内核缓存地址和用户缓存区共享,减少一次CPU Copy
- write 系统调用(实际是从PageCache拷贝到Socket Buffer)
- sendfile (1次CPU Copy,2次上下文切换)
- sendfile 将数据从PageCache拷贝到SocketBuffer
- 缺点是用户进程读取不到数据,无法编辑
- sendfile + SG-DMA (0次CPU Copy,1次上下文切换)
- sendfile 由SG-DMA直接将数据搬运到网络设备
性能比较:
IBM Developer![IBM Developer]()
IBM Developer
IBM Developer is your one-stop location for getting hands-on training and learning in-demand skills on relevant technologies such as generative AI, data science, AI, and open source.
File size | Normal file transfer (ms) | transferTo (ms) |
7MB | 156 | 45 |
21MB | 337 | 128 |
63MB | 843 | 387 |
98MB | 1320 | 617 |
200MB | 2124 | 1150 |
350MB | 3631 | 1762 |
700MB | 13498 | 4422 |
1GB | 18399 | 8537 |
磁盘高速缓存(PageCache)
内存访问速度比磁盘快,容量比磁盘小。为提高读写吞吐量,想法是【读写磁盘】替换成【读写内存】,但是注定只能拷贝一部分数据,利于数据局部性原理,将相邻的数据(预读)。
Linux PageCache单位: 4KB
块存储大小基本单位 扇区: 512B
示例:Linux read 32KB 数据时,但是内核会将0-32KB,32KB-64KB数据读取出来加载到PageCache。如果下次使用到32KB-64KB间的数据,则提升效率。
优点:
- 缓存最近访问的数据
- 预读



🤔 PageCache 一定是好的吗?
大文件传输(GB):


采用PageCache:
- 注意用户进程是阻塞的
- PageCache 预读会多一次DMA Copy
- 大文件拷贝,导致PageCache耗尽和热点数据减少,反而无法发挥PageCache性能
绕过PageCache,直接读更好;
优化如下:
- 异步IO,用户进程不阻塞
- 直接 I/O
高并发文件传输时:需要根据文件大小采取不同策略
- 传输大文件 → 异步IO + 直接IO
- 传输小文件 → 零拷贝
🤔 多少字节算大文件?多少字节算小文件?
一般标准 | 小文件 | 中文件 | 大文件 | 超大文件 |
大小 | <1MB | 1MB~100MB | 100MB~1GB | >1GB |
应用 | 快速编辑
快速传输 | 高分辨图片
短视频
音频
简单文档 | 超高清图片
长视频
复杂文档 | 高清长视频
游戏包
数据库 |
系统调用
open
open负责在内核生成与文件相对应的struct file元数据结构,并且与文件系统中该文件的struct inode进行关联,装载对应文件系统的操作回调函数,然后返回一个int fd给用户进程。后续用户对该文件的相关操作,会涉及到其相关的struct file、struct inode、inode->i_op、inode->i_fop和inode->i_mapping->a_ops等。

write
write的写逻辑路径有好几条,最常使用的就是利用pagecache延迟写的这条路径,所以主要分析这个。在write调用的调用、返回之间,其负责分配新的pagecache,将数据写入pagecache,同时根据系统参数,判断pagecache中的脏数据占比来确定是否要触发回写逻辑。其详细的代码分析可以参考:《Linux 内核写文件过程》和《Linux 内核延迟写机制》。

read

fsync
fsync和fdatasync主要逻辑流程基本相同。其通过触发对应文件的pagecache脏页回写,并且阻塞等待到回写逻辑完成,以达到同步数据的目的。

mmap
用户调用mmap将文件映射到内存时,内核进行一系列的参数检查,然后创建对应的vma,然后给该vma绑定vma_ops。当用户访问到mmap对应的内存时,CPU 会触发page fault,在page fault回调中,将申请pagecache中的匿名页,读取文件到其物理内存中,然后将pagecache中所属的物理页与用户进程的vma进行映射。

io_uring
epoll
重点:一次监听多个 fd 的可读/可写状态

- 调用
epoll_create()函数创建并初始化一个eventpoll对象。
- 调用
epoll_ctl()函数把被监听的文件句柄封装成epitem对象并且添加到eventpoll对象的红黑树中进行管理。 - op:
EPOLL_CTL_ADD:表示要进行添加操作。EPOLL_CTL_DEL:表示要进行删除操作。EPOLL_CTL_MOD:表示要进行修改操作。- events
水平触发 | 边缘触发 |
EPOLLIN 可读 | EPOLLIN | EPOLLET |
EPOLLOUT 可写 | EPOLLOUT | EPOLLET |
EPOLLERR 异常 | EPOLLERR | EPOLLET |
EPOLLHUP 挂起 | EPOLLHUP | EPOLLET |
LT和ET在处理 I/O 事件方面的不同语义:
- LT 模式下,是否通知用户程序取决于状态。比如上面的
rfd,它还有数据没读完,所以其状态依然是"可读"readable,也就是还有 I/O 事件没处理完,那么 LT 就会一直通知,直到所有的 I/O 事件被处理完,也就是rfd中的所有数据都被读取出来了,状态变为"不可读" unreadable。
- ET 模式下,是否通知并取决于状态变化。还是以上面的
rfd为例,ET 模式下的第 3 步的epoll_wait(2)之所以会通知用户程序,就是因为 pipe 中写入了数据,所以rfd的状态从一开始的unreadble变成了readable,所以 ET 会触发通知;同理,第 5 步之所以不会触发通知,是因为自从第一次状态变化之后rfd的状态并没有再次发生转变,一开始是readable,现在还是readable,所以 ET 就不再通知了。
正如Linus所说的那样3This is literally an epoll() confusion about what an "edge" is.An edge is not "somebody wrote more data". An edge is "there was no data, now there is data".And a level triggered event is also not "somebody wrote more data". A level-triggered signal is simply "there is data".Notice how neither edge nor level are about "more data". One is about the edge of "no data" -> "some data", and the other is just a "data is available".
- 当被监听的文件状态发生改变时,会把文件句柄对应
epitem对象添加到eventpoll对象的就绪队列rdllist中。并且把就绪队列的文件列表复制到epoll_wait()函数的events参数中。
- 唤醒调用
epoll_wait()函数被阻塞(睡眠)的进程。(伏笔)(nginx accept 惊群)
