📖Linux|IO
2024-11-10|2025-1-11
黎明
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直接将数据搬运到网络设备
性能比较:‣
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 惊群)
Loading...
- 关于
黎明
当机器会思考时,会不会失业?!
博客 已经上线🎉
但行耕耘,不问收获 🤪
-- 感谢您的支持 ---