Linux文件IO底层原理详解
执行 cat test.txt 或调用 read()/write() 时,数据往往并未直接落在磁盘上 :读路径经过 Page Cache ,写路径常先标记脏页 再由内核异步刷盘。理解 VFS、inode、文件描述符、页缓存与系统调用 的分工,才能解释「write 已成功但断电丢数据」「第二次读同一文件更快」等现象,并为高并发下的 epoll、零拷贝 选型打底。
速览
- 一切皆文件 :普通文件、设备、管道、套接字等经 VFS 统一抽象。
- inode :文件元数据与数据块指针;目录 是「文件名 → inode」映射;FD 是进程内的打开实例(偏移、模式)。
- 读 :磁盘 → Page Cache → 用户缓冲区(通常两次拷贝);写 :先写 Page Cache,writeback 异步落盘。
- 持久化 :
write返回 ≠ 落盘;需fsync/fdatasync或O_SYNC等才更强保证。- 进阶 :阻塞/非阻塞/多路复用/AIO/零拷贝;高并发文件发送常配合 sendfile 、mmap。
text
逻辑链条:VFS 抽象 → FD 操作 inode → open/read/write/close
→ Page Cache 加速 → syscall 进入内核 → 多种 IO 模型
阅读导航
全文约 6000 字,可按目标跳读,不必一次读完。
| 目标 | 建议章节 | 预计时间 |
|---|---|---|
| 建立心智模型(VFS / inode / FD / 读写路径) | [1](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟)--[7](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟) | ~15 分钟 |
| 缓存、syscall、性能 | [8](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟)、[9](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟)、[13](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟) | ~10 分钟 |
| 面试 / 复盘 | [12](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟)、[14](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟) | ~5 分钟 |
| 高并发与零拷贝 | [10](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟)、[11](#目标 建议章节 预计时间 建立心智模型(VFS / inode / FD / 读写路径) 1–7 ~15 分钟 缓存、syscall、性能 8、9、13 ~10 分钟 面试 / 复盘 12、14 ~5 分钟 高并发与零拷贝 10、11 ~10 分钟) | ~10 分钟 |
第 9 节含 x86_64 寄存器示意,不关心汇编者可只读 9.1 时序图 + 9.3 性能含义 ,细节见 [附录 A](#附录 A)。
目录
一、对象与抽象
- [1. VFS 与「一切皆文件」](#1. VFS 与「一切皆文件」)
- [2. inode、目录与链接](#2. inode、目录与链接)
- [3. 文件描述符与 struct file](#3. 文件描述符与 struct file)
二、一次 IO 的生命周期
- [4. open:路径解析到分配 FD](#4. open:路径解析到分配 FD)
- [5. read:Page Cache 与两次拷贝](#5. read:Page Cache 与两次拷贝)
- [6. write、脏页与持久化](#6. write、脏页与持久化)
- [7. close 与 FD 泄露](#7. close 与 FD 泄露)
三、缓存与进入内核
- [8. 三层缓冲与数据路径](#8. 三层缓冲与数据路径)
- [9. 系统调用路径](#9. 系统调用路径)
四、IO 模型与延伸
- [10. 五种 IO 模型对照](#10. 五种 IO 模型对照)
- [11. read 与 mmap、零拷贝](#11. read 与 mmap、零拷贝)
- [12. 常见误区](#12. 常见误区)
- [13. 性能调优要点](#13. 性能调优要点)
- [14. 速查与验证](#14. 速查与验证)
附录
- [附录 A:x86_64 系统调用传参示意](#附录 A:x86_64 系统调用传参示意)
1. VFS 与「一切皆文件」
Linux 通过 VFS(Virtual File System) 为 ext4、xfs、btrfs、tmpfs 等具体文件系统提供统一接口。应用调用 open/read/write 时,先进入 VFS,再由具体 file_operations 访问 inode 与底层存储。
1.1 VFS 在栈中的位置
#mermaid-svg-pHwazJilOuTUz136{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pHwazJilOuTUz136 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pHwazJilOuTUz136 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pHwazJilOuTUz136 .error-icon{fill:#552222;}#mermaid-svg-pHwazJilOuTUz136 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pHwazJilOuTUz136 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pHwazJilOuTUz136 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pHwazJilOuTUz136 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pHwazJilOuTUz136 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pHwazJilOuTUz136 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pHwazJilOuTUz136 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pHwazJilOuTUz136 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pHwazJilOuTUz136 .marker.cross{stroke:#333333;}#mermaid-svg-pHwazJilOuTUz136 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pHwazJilOuTUz136 p{margin:0;}#mermaid-svg-pHwazJilOuTUz136 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pHwazJilOuTUz136 .cluster-label text{fill:#333;}#mermaid-svg-pHwazJilOuTUz136 .cluster-label span{color:#333;}#mermaid-svg-pHwazJilOuTUz136 .cluster-label span p{background-color:transparent;}#mermaid-svg-pHwazJilOuTUz136 .label text,#mermaid-svg-pHwazJilOuTUz136 span{fill:#333;color:#333;}#mermaid-svg-pHwazJilOuTUz136 .node rect,#mermaid-svg-pHwazJilOuTUz136 .node circle,#mermaid-svg-pHwazJilOuTUz136 .node ellipse,#mermaid-svg-pHwazJilOuTUz136 .node polygon,#mermaid-svg-pHwazJilOuTUz136 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pHwazJilOuTUz136 .rough-node .label text,#mermaid-svg-pHwazJilOuTUz136 .node .label text,#mermaid-svg-pHwazJilOuTUz136 .image-shape .label,#mermaid-svg-pHwazJilOuTUz136 .icon-shape .label{text-anchor:middle;}#mermaid-svg-pHwazJilOuTUz136 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pHwazJilOuTUz136 .rough-node .label,#mermaid-svg-pHwazJilOuTUz136 .node .label,#mermaid-svg-pHwazJilOuTUz136 .image-shape .label,#mermaid-svg-pHwazJilOuTUz136 .icon-shape .label{text-align:center;}#mermaid-svg-pHwazJilOuTUz136 .node.clickable{cursor:pointer;}#mermaid-svg-pHwazJilOuTUz136 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pHwazJilOuTUz136 .arrowheadPath{fill:#333333;}#mermaid-svg-pHwazJilOuTUz136 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pHwazJilOuTUz136 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pHwazJilOuTUz136 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pHwazJilOuTUz136 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pHwazJilOuTUz136 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pHwazJilOuTUz136 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pHwazJilOuTUz136 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pHwazJilOuTUz136 .cluster text{fill:#333;}#mermaid-svg-pHwazJilOuTUz136 .cluster span{color:#333;}#mermaid-svg-pHwazJilOuTUz136 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pHwazJilOuTUz136 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pHwazJilOuTUz136 rect.text{fill:none;stroke-width:0;}#mermaid-svg-pHwazJilOuTUz136 .icon-shape,#mermaid-svg-pHwazJilOuTUz136 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pHwazJilOuTUz136 .icon-shape p,#mermaid-svg-pHwazJilOuTUz136 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pHwazJilOuTUz136 .icon-shape .label rect,#mermaid-svg-pHwazJilOuTUz136 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pHwazJilOuTUz136 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pHwazJilOuTUz136 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pHwazJilOuTUz136 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 块层与设备
具体文件系统
VFS 层
用户态
应用程序
open read write
VFS 统一入口
路径解析 / 权限 / fd 表
ext4 / xfs / btrfs ...
proc / sysfs 伪 FS
socket 等特殊 file_ops
块层 / 驱动
磁盘 / SSD
| 访问对象 | 在 VFS 下的表现 |
|---|---|
| 普通文件 | 常规 inode + 数据块 / extent |
| 块/字符设备 | /dev/sda 等,走块/字符设备驱动 |
| 管道、套接字 | 专用 file_operations,无传统磁盘块 |
/proc、/sys |
伪文件系统,内容由内核按需生成 |
统一抽象的好处:同一套 API 服务磁盘、网络、设备;代价是需分清背后的真实类型------例如 socket 的 read 等待的是套接字缓冲区,不会走磁盘 Page Cache。
回顾 :VFS 是「总前台」------应用只认
open/read/write,具体是磁盘文件还是设备由下层 file_operations 决定。
2. inode、目录与链接
2.1 inode
inode 保存文件元数据,典型包括:
| 字段类 | 内容 |
|---|---|
| 身份 | inode 号、文件类型(普通/目录/链接/设备...) |
| 权限 | uid/gid、rwx 位 |
| 大小与时间 | st_size、atime/mtime/ctime |
| 数据定位 | 块指针或 extent 树(大文件) |
| 链接计数 | 硬链接数量 |
文件名不在 inode 里。删除「文件名」只是删掉目录项;inode 与数据块在链接计数为 0 后才回收。
2.2 目录与 dentry
目录本质是 「文件名 → inode 号」 的映射表。
text
用户路径: /home/user/test.txt
│
▼
┌─────────────────────────────────────┐
│ dentry 缓存(目录项,加速路径查找) │
└─────────────────────────────────────┘
│
▼
inode #12345 ──► 数据块 / extent
路径解析从根 / 起逐级查 dentry;命中缓存可避免反复读磁盘上的目录块。
2.3 硬链接与符号链接
| 类型 | 机制 | ls -l 特征 |
删除影响 |
|---|---|---|---|
| 硬链接 | 同 inode,多目录项 | 相同 inode 号、链接数 +1 | 删一名仍保留 inode,直至链接数为 0 |
| 符号链接 | 单独 inode,内容为路径字符串 | 类型为 l,箭头指向目标 |
目标删了则悬空(broken symlink) |
2.4 命令速览:看清 inode
bash
ls -li test.txt # 第一列为 inode 号
stat test.txt # 详细元数据
find . -inum 12345 # 按 inode 反查硬链接
3. 文件描述符与 struct file
FD 是进程内的非负整数,指向内核 struct file(一次「打开」),而不是 inode 本身。
3.1 四元关系
#mermaid-svg-Dzyo0riSgcL5C3AB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Dzyo0riSgcL5C3AB .error-icon{fill:#552222;}#mermaid-svg-Dzyo0riSgcL5C3AB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Dzyo0riSgcL5C3AB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Dzyo0riSgcL5C3AB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Dzyo0riSgcL5C3AB .marker.cross{stroke:#333333;}#mermaid-svg-Dzyo0riSgcL5C3AB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Dzyo0riSgcL5C3AB p{margin:0;}#mermaid-svg-Dzyo0riSgcL5C3AB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB .cluster-label text{fill:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB .cluster-label span{color:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB .cluster-label span p{background-color:transparent;}#mermaid-svg-Dzyo0riSgcL5C3AB .label text,#mermaid-svg-Dzyo0riSgcL5C3AB span{fill:#333;color:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB .node rect,#mermaid-svg-Dzyo0riSgcL5C3AB .node circle,#mermaid-svg-Dzyo0riSgcL5C3AB .node ellipse,#mermaid-svg-Dzyo0riSgcL5C3AB .node polygon,#mermaid-svg-Dzyo0riSgcL5C3AB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Dzyo0riSgcL5C3AB .rough-node .label text,#mermaid-svg-Dzyo0riSgcL5C3AB .node .label text,#mermaid-svg-Dzyo0riSgcL5C3AB .image-shape .label,#mermaid-svg-Dzyo0riSgcL5C3AB .icon-shape .label{text-anchor:middle;}#mermaid-svg-Dzyo0riSgcL5C3AB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Dzyo0riSgcL5C3AB .rough-node .label,#mermaid-svg-Dzyo0riSgcL5C3AB .node .label,#mermaid-svg-Dzyo0riSgcL5C3AB .image-shape .label,#mermaid-svg-Dzyo0riSgcL5C3AB .icon-shape .label{text-align:center;}#mermaid-svg-Dzyo0riSgcL5C3AB .node.clickable{cursor:pointer;}#mermaid-svg-Dzyo0riSgcL5C3AB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Dzyo0riSgcL5C3AB .arrowheadPath{fill:#333333;}#mermaid-svg-Dzyo0riSgcL5C3AB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Dzyo0riSgcL5C3AB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Dzyo0riSgcL5C3AB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Dzyo0riSgcL5C3AB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Dzyo0riSgcL5C3AB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Dzyo0riSgcL5C3AB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Dzyo0riSgcL5C3AB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Dzyo0riSgcL5C3AB .cluster text{fill:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB .cluster span{color:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Dzyo0riSgcL5C3AB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Dzyo0riSgcL5C3AB rect.text{fill:none;stroke-width:0;}#mermaid-svg-Dzyo0riSgcL5C3AB .icon-shape,#mermaid-svg-Dzyo0riSgcL5C3AB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Dzyo0riSgcL5C3AB .icon-shape p,#mermaid-svg-Dzyo0riSgcL5C3AB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Dzyo0riSgcL5C3AB .icon-shape .label rect,#mermaid-svg-Dzyo0riSgcL5C3AB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Dzyo0riSgcL5C3AB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Dzyo0riSgcL5C3AB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Dzyo0riSgcL5C3AB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内核
进程
fd 表
fd=3
struct file
f_pos 偏移
f_mode 标志
inode
元数据+数据块
路径名 test.txt
dentry
| 概念 | 含义 | 生命周期 |
|---|---|---|
| inode | 文件实体 | 链接计数为 0 时销毁 |
| struct file | 某次打开:offset、标志、操作表 | close 且引用计数为 0 |
| FD | 进程 fd 表中的槽位 | close 或进程退出 |
| dentry | 路径分量缓存 | 由 LRU 等回收 |
3.2 进程间与 dup
| 场景 | inode | struct file | FD 编号 |
|---|---|---|---|
两进程各 open 同一文件 |
共享 | 各一份 | 各自独立 |
dup(fd) |
共享 | 共享 | 不同号,偏移联动 |
fork |
共享 | 共享(引用计数 +1) | 子进程继承相同编号 |
- 默认 0/1/2 :stdin/stdout/stderr;新打开通常从 3 起。
- FD 编号、类型、
/proc/PID/fd、上限见《Linux文件描述符FD机制深度解析》。
本部分回顾(一 · 对象与抽象) :inode 是文件是谁,目录名 只是索引;FD 是进程手里那张「办事号」,背后连着一次打开(
struct file)和同一个 inode。
4. open:路径解析到分配 FD
c
int fd = open("test.txt", O_RDONLY);
4.1 内核步骤
#mermaid-svg-iOTFlabqq4NBqBwY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iOTFlabqq4NBqBwY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iOTFlabqq4NBqBwY .error-icon{fill:#552222;}#mermaid-svg-iOTFlabqq4NBqBwY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iOTFlabqq4NBqBwY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iOTFlabqq4NBqBwY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iOTFlabqq4NBqBwY .marker.cross{stroke:#333333;}#mermaid-svg-iOTFlabqq4NBqBwY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iOTFlabqq4NBqBwY p{margin:0;}#mermaid-svg-iOTFlabqq4NBqBwY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iOTFlabqq4NBqBwY .cluster-label text{fill:#333;}#mermaid-svg-iOTFlabqq4NBqBwY .cluster-label span{color:#333;}#mermaid-svg-iOTFlabqq4NBqBwY .cluster-label span p{background-color:transparent;}#mermaid-svg-iOTFlabqq4NBqBwY .label text,#mermaid-svg-iOTFlabqq4NBqBwY span{fill:#333;color:#333;}#mermaid-svg-iOTFlabqq4NBqBwY .node rect,#mermaid-svg-iOTFlabqq4NBqBwY .node circle,#mermaid-svg-iOTFlabqq4NBqBwY .node ellipse,#mermaid-svg-iOTFlabqq4NBqBwY .node polygon,#mermaid-svg-iOTFlabqq4NBqBwY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iOTFlabqq4NBqBwY .rough-node .label text,#mermaid-svg-iOTFlabqq4NBqBwY .node .label text,#mermaid-svg-iOTFlabqq4NBqBwY .image-shape .label,#mermaid-svg-iOTFlabqq4NBqBwY .icon-shape .label{text-anchor:middle;}#mermaid-svg-iOTFlabqq4NBqBwY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iOTFlabqq4NBqBwY .rough-node .label,#mermaid-svg-iOTFlabqq4NBqBwY .node .label,#mermaid-svg-iOTFlabqq4NBqBwY .image-shape .label,#mermaid-svg-iOTFlabqq4NBqBwY .icon-shape .label{text-align:center;}#mermaid-svg-iOTFlabqq4NBqBwY .node.clickable{cursor:pointer;}#mermaid-svg-iOTFlabqq4NBqBwY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iOTFlabqq4NBqBwY .arrowheadPath{fill:#333333;}#mermaid-svg-iOTFlabqq4NBqBwY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iOTFlabqq4NBqBwY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iOTFlabqq4NBqBwY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iOTFlabqq4NBqBwY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iOTFlabqq4NBqBwY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iOTFlabqq4NBqBwY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iOTFlabqq4NBqBwY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iOTFlabqq4NBqBwY .cluster text{fill:#333;}#mermaid-svg-iOTFlabqq4NBqBwY .cluster span{color:#333;}#mermaid-svg-iOTFlabqq4NBqBwY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iOTFlabqq4NBqBwY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iOTFlabqq4NBqBwY rect.text{fill:none;stroke-width:0;}#mermaid-svg-iOTFlabqq4NBqBwY .icon-shape,#mermaid-svg-iOTFlabqq4NBqBwY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iOTFlabqq4NBqBwY .icon-shape p,#mermaid-svg-iOTFlabqq4NBqBwY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iOTFlabqq4NBqBwY .icon-shape .label rect,#mermaid-svg-iOTFlabqq4NBqBwY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iOTFlabqq4NBqBwY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iOTFlabqq4NBqBwY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iOTFlabqq4NBqBwY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 路径字符串
dentry 查找
得到 inode
权限检查
分配 struct file
填入进程 fd 表
返回 fd
| 步骤 | 说明 |
|---|---|
| 路径解析 | 沿 /、home、test.txt 查 dentry;openat 可相对目录 fd |
| 权限检查 | inode 模式与进程 cred(含 CAP) |
创建 struct file |
f_pos=0、打开标志、指向 file_operations |
| 分配 FD | 通常取当前进程最小未用 fd |
4.2 常用 open 标志
| 标志 | 作用 |
|---|---|
O_RDONLY / O_WRONLY / O_RDWR |
访问模式 |
O_CREAT |
不存在则创建;mode 受 umask 影响:mode & ~umask |
O_TRUNC |
写打开时截断为 0 长度 |
O_APPEND |
写时偏移固定在文件尾 |
O_NONBLOCK |
非阻塞(设备/socket 更常见) |
O_SYNC / O_DSYNC |
打开后每次 write 附带更强刷盘语义(见 [6.2](#标志 作用 O_RDONLY / O_WRONLY / O_RDWR 访问模式 O_CREAT 不存在则创建;mode 受 umask 影响:mode & ~umask O_TRUNC 写打开时截断为 0 长度 O_APPEND 写时偏移固定在文件尾 O_NONBLOCK 非阻塞(设备/socket 更常见) O_SYNC / O_DSYNC 打开后每次 write 附带更强刷盘语义(见 6.2) O_DIRECT 尽量绕过 Page Cache,需对齐(见 13) O_CLOEXEC exec 时自动关闭,防 fd 泄露)) |
O_DIRECT |
尽量绕过 Page Cache,需对齐(见 [13](#标志 作用 O_RDONLY / O_WRONLY / O_RDWR 访问模式 O_CREAT 不存在则创建;mode 受 umask 影响:mode & ~umask O_TRUNC 写打开时截断为 0 长度 O_APPEND 写时偏移固定在文件尾 O_NONBLOCK 非阻塞(设备/socket 更常见) O_SYNC / O_DSYNC 打开后每次 write 附带更强刷盘语义(见 6.2) O_DIRECT 尽量绕过 Page Cache,需对齐(见 13) O_CLOEXEC exec 时自动关闭,防 fd 泄露)) |
O_CLOEXEC |
exec 时自动关闭,防 fd 泄露 |
fcntl(F_SETFL) 可在不重新 open 的情况下调整 O_NONBLOCK、O_APPEND 等。
4.3 strace 示例
bash
strace -e openat,read,write,close cat /etc/hostname 2>&1 | head -20
典型片段(路径因系统而异):
text
openat(AT_FDCWD, "/etc/hostname", O_RDONLY) = 3
read(3, "myhost\n", 131072) = 7
write(1, "myhost\n", 7) = 7
close(3) = 0
cat 从 fd=3 读文件,再 write 到 fd=1(stdout);可见 FD 与「打开对象」一一对应。
5. read:Page Cache 与两次拷贝
c
ssize_t n = read(fd, buf, len);
5.1 典型数据路径
常见误解:磁盘 → 用户 buf。实际是:
text
┌──────────┐ 缺页/未命中 ┌─────────────┐ copy_to_user ┌──────────┐
│ 磁盘/SSD │ ──────────────────► │ Page Cache │ ─────────────────► │ 用户 buf │
└──────────┘ └─────────────┘ └──────────┘
▲
命中则跳过读盘
#mermaid-svg-ix0iyUdHYfdkMowW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ix0iyUdHYfdkMowW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ix0iyUdHYfdkMowW .error-icon{fill:#552222;}#mermaid-svg-ix0iyUdHYfdkMowW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ix0iyUdHYfdkMowW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ix0iyUdHYfdkMowW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ix0iyUdHYfdkMowW .marker.cross{stroke:#333333;}#mermaid-svg-ix0iyUdHYfdkMowW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ix0iyUdHYfdkMowW p{margin:0;}#mermaid-svg-ix0iyUdHYfdkMowW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ix0iyUdHYfdkMowW .cluster-label text{fill:#333;}#mermaid-svg-ix0iyUdHYfdkMowW .cluster-label span{color:#333;}#mermaid-svg-ix0iyUdHYfdkMowW .cluster-label span p{background-color:transparent;}#mermaid-svg-ix0iyUdHYfdkMowW .label text,#mermaid-svg-ix0iyUdHYfdkMowW span{fill:#333;color:#333;}#mermaid-svg-ix0iyUdHYfdkMowW .node rect,#mermaid-svg-ix0iyUdHYfdkMowW .node circle,#mermaid-svg-ix0iyUdHYfdkMowW .node ellipse,#mermaid-svg-ix0iyUdHYfdkMowW .node polygon,#mermaid-svg-ix0iyUdHYfdkMowW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ix0iyUdHYfdkMowW .rough-node .label text,#mermaid-svg-ix0iyUdHYfdkMowW .node .label text,#mermaid-svg-ix0iyUdHYfdkMowW .image-shape .label,#mermaid-svg-ix0iyUdHYfdkMowW .icon-shape .label{text-anchor:middle;}#mermaid-svg-ix0iyUdHYfdkMowW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ix0iyUdHYfdkMowW .rough-node .label,#mermaid-svg-ix0iyUdHYfdkMowW .node .label,#mermaid-svg-ix0iyUdHYfdkMowW .image-shape .label,#mermaid-svg-ix0iyUdHYfdkMowW .icon-shape .label{text-align:center;}#mermaid-svg-ix0iyUdHYfdkMowW .node.clickable{cursor:pointer;}#mermaid-svg-ix0iyUdHYfdkMowW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ix0iyUdHYfdkMowW .arrowheadPath{fill:#333333;}#mermaid-svg-ix0iyUdHYfdkMowW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ix0iyUdHYfdkMowW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ix0iyUdHYfdkMowW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ix0iyUdHYfdkMowW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ix0iyUdHYfdkMowW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ix0iyUdHYfdkMowW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ix0iyUdHYfdkMowW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ix0iyUdHYfdkMowW .cluster text{fill:#333;}#mermaid-svg-ix0iyUdHYfdkMowW .cluster span{color:#333;}#mermaid-svg-ix0iyUdHYfdkMowW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ix0iyUdHYfdkMowW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ix0iyUdHYfdkMowW rect.text{fill:none;stroke-width:0;}#mermaid-svg-ix0iyUdHYfdkMowW .icon-shape,#mermaid-svg-ix0iyUdHYfdkMowW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ix0iyUdHYfdkMowW .icon-shape p,#mermaid-svg-ix0iyUdHYfdkMowW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ix0iyUdHYfdkMowW .icon-shape .label rect,#mermaid-svg-ix0iyUdHYfdkMowW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ix0iyUdHYfdkMowW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ix0iyUdHYfdkMowW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ix0iyUdHYfdkMowW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 内核
用户态
read 系统调用
命中
未命中
顺序读时提前拉页
应用缓冲区 buf
Page Cache
按页 通常 4KB
readahead 预读
块设备
| 现象 | 原因 |
|---|---|
| 第二次读同一文件更快 | Page Cache 命中 |
| 服务刚启动偏慢 | 冷缓存,大量缺页读盘 |
| 多进程读同一文件 | 可共享缓存页 |
read 返回 0 |
已到文件尾(EOF) |
read 返回 -1 |
看 errno(如 EINTR 可重试) |
5.2 pread / pwrite
| API | 特点 |
|---|---|
read/write |
共享 struct file 的 当前偏移 |
pread/pwrite |
带偏移参数,不改变 f_pos,多线程读同一 fd 更安全 |
5.3 顺序读与 readahead
内核发现顺序访问 时,可用 readahead 提前把后续页调入 Page Cache,使应用下一次 read 更易命中。随机小 IO 则难以受益------这也是「拷贝一个大文件」往往比「海量小文件」更快的原因之一。
6. write、脏页与持久化
c
ssize_t n = write(fd, buf, len);
6.1 写路径
#mermaid-svg-uxVlfWTnfUlhOkVW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uxVlfWTnfUlhOkVW .error-icon{fill:#552222;}#mermaid-svg-uxVlfWTnfUlhOkVW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uxVlfWTnfUlhOkVW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uxVlfWTnfUlhOkVW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uxVlfWTnfUlhOkVW .marker.cross{stroke:#333333;}#mermaid-svg-uxVlfWTnfUlhOkVW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uxVlfWTnfUlhOkVW p{margin:0;}#mermaid-svg-uxVlfWTnfUlhOkVW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW .cluster-label text{fill:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW .cluster-label span{color:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW .cluster-label span p{background-color:transparent;}#mermaid-svg-uxVlfWTnfUlhOkVW .label text,#mermaid-svg-uxVlfWTnfUlhOkVW span{fill:#333;color:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW .node rect,#mermaid-svg-uxVlfWTnfUlhOkVW .node circle,#mermaid-svg-uxVlfWTnfUlhOkVW .node ellipse,#mermaid-svg-uxVlfWTnfUlhOkVW .node polygon,#mermaid-svg-uxVlfWTnfUlhOkVW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uxVlfWTnfUlhOkVW .rough-node .label text,#mermaid-svg-uxVlfWTnfUlhOkVW .node .label text,#mermaid-svg-uxVlfWTnfUlhOkVW .image-shape .label,#mermaid-svg-uxVlfWTnfUlhOkVW .icon-shape .label{text-anchor:middle;}#mermaid-svg-uxVlfWTnfUlhOkVW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uxVlfWTnfUlhOkVW .rough-node .label,#mermaid-svg-uxVlfWTnfUlhOkVW .node .label,#mermaid-svg-uxVlfWTnfUlhOkVW .image-shape .label,#mermaid-svg-uxVlfWTnfUlhOkVW .icon-shape .label{text-align:center;}#mermaid-svg-uxVlfWTnfUlhOkVW .node.clickable{cursor:pointer;}#mermaid-svg-uxVlfWTnfUlhOkVW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uxVlfWTnfUlhOkVW .arrowheadPath{fill:#333333;}#mermaid-svg-uxVlfWTnfUlhOkVW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uxVlfWTnfUlhOkVW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uxVlfWTnfUlhOkVW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uxVlfWTnfUlhOkVW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uxVlfWTnfUlhOkVW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uxVlfWTnfUlhOkVW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uxVlfWTnfUlhOkVW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uxVlfWTnfUlhOkVW .cluster text{fill:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW .cluster span{color:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uxVlfWTnfUlhOkVW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uxVlfWTnfUlhOkVW rect.text{fill:none;stroke-width:0;}#mermaid-svg-uxVlfWTnfUlhOkVW .icon-shape,#mermaid-svg-uxVlfWTnfUlhOkVW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uxVlfWTnfUlhOkVW .icon-shape p,#mermaid-svg-uxVlfWTnfUlhOkVW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uxVlfWTnfUlhOkVW .icon-shape .label rect,#mermaid-svg-uxVlfWTnfUlhOkVW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uxVlfWTnfUlhOkVW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uxVlfWTnfUlhOkVW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uxVlfWTnfUlhOkVW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} copy_from_user
标记 dirty
writeback 线程
用户 buf
Page Cache
脏页
磁盘
| 步骤 | 说明 |
|---|---|
| 数据进 Page Cache | copy_from_user |
| 标记脏页 | 该页需回写 |
write 返回 |
多表示数据已在缓存,不保证落盘 |
| writeback | kworker 等按水位、周期异步刷盘 |
6.2 持久化级别对照
从弱到强(概念上,具体还依赖文件系统与硬件写缓存):
| 级别 | 手段 | 应用层含义 |
|---|---|---|
① 默认 write |
无 | 数据在 Page Cache,断电可能丢 |
② fflush(stdio) |
C 库缓冲刷向内核 | 仍可能在 Page Cache,未必落盘 |
③ fdatasync |
刷文件数据 | 元数据可能稍后(如大小) |
④ fsync |
刷数据 + 元数据 | 数据库日志常用 |
⑤ open(..., O_DSYNC) |
每次写等价于带数据同步语义 | 性能开销大 |
⑥ open(..., O_SYNC) |
更强同步语义 | 比 ⑤ 更严(含更多元数据同步) |
text
应用 fwrite ──► stdio 缓冲 ──► write() ──► Page Cache ──► 磁盘
fflush fsync/O_SYNC
② ③④⑤⑥
close 不能替代 fsync:关闭 fd 只释放打开实例;脏页仍可能尚未 writeback。
6.3 观察脏页与 writeback(运维)
bash
cat /proc/meminfo | egrep 'Dirty|Writeback'
cat /proc/sys/vm/dirty_ratio
cat /proc/sys/vm/dirty_background_ratio
| 参数 | 含义(简化) |
|---|---|
dirty_ratio |
脏页占内存比例超阈值时,进程写可能被阻塞并推动回写 |
dirty_background_ratio |
后台 writeback 启动阈值 |
大文件顺序写、数据库 checkpoint、虚拟机镜像合并都会推高 Dirty ;排障时结合 iostat -x 看磁盘 Util。
7. close 与 FD 泄露
close(fd) 释放进程 fd 表项;struct file 引用计数为 0 时回收。未刷盘脏页由全局 writeback 处理,与 close 无强同步关系。
| 问题 | 后果 | 预防 |
|---|---|---|
长期不 close |
EMFILE,open 失败 |
RAII、O_CLOEXEC、泄漏检测 |
fork 后未关无用 fd |
子进程继承,epoll 误触发 | FD_CLOEXEC、显式关闭 |
异常路径未 close |
同上 | goto cleanup、defer 模式 |
本部分回顾(二 · 一次 IO 生命周期) :open 不是「打开磁盘上的名字」,而是申请一条访问 inode 的通道 ;read/write 在 Page Cache 里交接数据;write 返回 ≠ 落盘 ;close 只收通道,不替你做 fsync。
8. 三层缓冲与数据路径
文件 IO 常叠加三层缓冲,排障「数据去哪了」须逐层分清:
#mermaid-svg-HwYQBeD0HnPdnCLD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HwYQBeD0HnPdnCLD .error-icon{fill:#552222;}#mermaid-svg-HwYQBeD0HnPdnCLD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HwYQBeD0HnPdnCLD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HwYQBeD0HnPdnCLD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HwYQBeD0HnPdnCLD .marker.cross{stroke:#333333;}#mermaid-svg-HwYQBeD0HnPdnCLD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HwYQBeD0HnPdnCLD p{margin:0;}#mermaid-svg-HwYQBeD0HnPdnCLD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD .cluster-label text{fill:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD .cluster-label span{color:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD .cluster-label span p{background-color:transparent;}#mermaid-svg-HwYQBeD0HnPdnCLD .label text,#mermaid-svg-HwYQBeD0HnPdnCLD span{fill:#333;color:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD .node rect,#mermaid-svg-HwYQBeD0HnPdnCLD .node circle,#mermaid-svg-HwYQBeD0HnPdnCLD .node ellipse,#mermaid-svg-HwYQBeD0HnPdnCLD .node polygon,#mermaid-svg-HwYQBeD0HnPdnCLD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HwYQBeD0HnPdnCLD .rough-node .label text,#mermaid-svg-HwYQBeD0HnPdnCLD .node .label text,#mermaid-svg-HwYQBeD0HnPdnCLD .image-shape .label,#mermaid-svg-HwYQBeD0HnPdnCLD .icon-shape .label{text-anchor:middle;}#mermaid-svg-HwYQBeD0HnPdnCLD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HwYQBeD0HnPdnCLD .rough-node .label,#mermaid-svg-HwYQBeD0HnPdnCLD .node .label,#mermaid-svg-HwYQBeD0HnPdnCLD .image-shape .label,#mermaid-svg-HwYQBeD0HnPdnCLD .icon-shape .label{text-align:center;}#mermaid-svg-HwYQBeD0HnPdnCLD .node.clickable{cursor:pointer;}#mermaid-svg-HwYQBeD0HnPdnCLD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HwYQBeD0HnPdnCLD .arrowheadPath{fill:#333333;}#mermaid-svg-HwYQBeD0HnPdnCLD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HwYQBeD0HnPdnCLD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HwYQBeD0HnPdnCLD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HwYQBeD0HnPdnCLD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HwYQBeD0HnPdnCLD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HwYQBeD0HnPdnCLD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HwYQBeD0HnPdnCLD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HwYQBeD0HnPdnCLD .cluster text{fill:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD .cluster span{color:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HwYQBeD0HnPdnCLD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HwYQBeD0HnPdnCLD rect.text{fill:none;stroke-width:0;}#mermaid-svg-HwYQBeD0HnPdnCLD .icon-shape,#mermaid-svg-HwYQBeD0HnPdnCLD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HwYQBeD0HnPdnCLD .icon-shape p,#mermaid-svg-HwYQBeD0HnPdnCLD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HwYQBeD0HnPdnCLD .icon-shape .label rect,#mermaid-svg-HwYQBeD0HnPdnCLD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HwYQBeD0HnPdnCLD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HwYQBeD0HnPdnCLD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HwYQBeD0HnPdnCLD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第 3 层:块设备
第 2 层:内核 Page Cache
第 1 层:应用 / stdio
write/read syscall
fopen 缓冲区
setvbuf 全缓冲/行缓冲
页缓存 4KB
LRU 淘汰 / readahead
块层队列
设备驱动
fprintf / fread
磁盘
| 层次 | 位置 | 优化目标 | 典型 API |
|---|---|---|---|
| stdio | libc | 减少 syscall 次数 | setvbuf、fflush |
| Page Cache | 内核 | 减少 磁盘 IO | 自动;posix_fadvise 提示 |
| 块层 | 内核 | 合并、调度 IO | 对应用透明 |
8.1 stdio 与 syscall
c
/* 多次 fgetc 可能只触发少量 read */
FILE *fp = fopen("big.log", "r");
setvbuf(fp, buf, _IOFBF, 65536); /* 64KB 全缓冲 */
| 缓冲模式 | 行为 |
|---|---|
_IONBF |
无缓冲,每次 stdio 操作易触发 syscall |
_IOLBF |
行缓冲,遇 \n 刷新(tty 默认) |
_IOFBF |
全缓冲,满块或 fflush 才 syscall |
8.2 Page Cache 策略(概念)
- LRU 变体:active / inactive 链表,冷页被淘汰。
- 共享:多进程只读映射同一文件可共享物理页。
drop_caches:仅测试环境用于模拟冷启动,生产慎用。
9. 系统调用路径
read() 在 libc 中往往是薄包装,最终 syscall 进入内核。
跳读提示 :理解「用户态 ↔ 内核态切换 + fd 查表」即可;寄存器级细节见 [附录 A](#附录 A)。
9.1 时序
Page Cache sys_read libc 用户态 Page Cache sys_read libc 用户态 #mermaid-svg-bo91476PUQcC0PDj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bo91476PUQcC0PDj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bo91476PUQcC0PDj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bo91476PUQcC0PDj .error-icon{fill:#552222;}#mermaid-svg-bo91476PUQcC0PDj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bo91476PUQcC0PDj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bo91476PUQcC0PDj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bo91476PUQcC0PDj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bo91476PUQcC0PDj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bo91476PUQcC0PDj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bo91476PUQcC0PDj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bo91476PUQcC0PDj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bo91476PUQcC0PDj .marker.cross{stroke:#333333;}#mermaid-svg-bo91476PUQcC0PDj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bo91476PUQcC0PDj p{margin:0;}#mermaid-svg-bo91476PUQcC0PDj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bo91476PUQcC0PDj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-bo91476PUQcC0PDj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bo91476PUQcC0PDj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-bo91476PUQcC0PDj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-bo91476PUQcC0PDj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-bo91476PUQcC0PDj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-bo91476PUQcC0PDj .sequenceNumber{fill:white;}#mermaid-svg-bo91476PUQcC0PDj #sequencenumber{fill:#333;}#mermaid-svg-bo91476PUQcC0PDj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-bo91476PUQcC0PDj .messageText{fill:#333;stroke:none;}#mermaid-svg-bo91476PUQcC0PDj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bo91476PUQcC0PDj .labelText,#mermaid-svg-bo91476PUQcC0PDj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-bo91476PUQcC0PDj .loopText,#mermaid-svg-bo91476PUQcC0PDj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-bo91476PUQcC0PDj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bo91476PUQcC0PDj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-bo91476PUQcC0PDj .noteText,#mermaid-svg-bo91476PUQcC0PDj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-bo91476PUQcC0PDj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bo91476PUQcC0PDj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bo91476PUQcC0PDj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bo91476PUQcC0PDj .actorPopupMenu{position:absolute;}#mermaid-svg-bo91476PUQcC0PDj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-bo91476PUQcC0PDj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bo91476PUQcC0PDj .actor-man circle,#mermaid-svg-bo91476PUQcC0PDj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-bo91476PUQcC0PDj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 特权级切换 + 保存上下文 read(fd, buf, n) syscall fd → struct file → inode 查页 / 缺页读盘 copy_to_user 返回值 / errno
9.2 性能含义
| 模式 | 特点 |
|---|---|
每次 read 1 字节 |
syscall 极多,CPU 耗在态切换 |
每次 read 64KB+ |
syscall 少,更易吃满 Page Cache |
stdio 大缓冲 + 大块 read |
工程上常用组合 |
核心调用:openat、close、read、write、lseek、pread/pwrite、fcntl。
本部分回顾(三 · 缓存与内核) :stdio 帮你少打 syscall,Page Cache 帮你少碰磁盘;真正贵的是态切换 + 拷贝,大块 IO 比碎小 read 划算得多。
10. 五种 IO 模型对照
在阻塞 read/write 之上,Linux 为高并发与吞吐演化出多种模型(网络 IO 用得最多,文件 IO 也常组合使用)。
10.1 总览表
| 模型 | 行为概要 | 典型缺点 | 常见场景 |
|---|---|---|---|
| 阻塞 IO | 直到数据就绪并完成拷贝才返回 | 一连接一线程,扩展差 | 简单 CLI、低并发 |
| 非阻塞 IO | 未就绪返回 EAGAIN/EWOULDBLOCK |
自旋轮询浪费 CPU | 多路复用的前置 |
| IO 多路复用 | select/poll/epoll 等就绪再读写 |
epoll 需理解 LT/ET | Nginx、Redis |
| 异步 IO(AIO) | 提交后立刻返回,完成时通知 | 传统 Linux AIO 对普通文件支持有限 | 特定场景;见 io_uring |
| 零拷贝 | 数据少经用户态 | 需内核/驱动支持 | 静态文件、sendfile |
10.2 行为对比(示意图)
#mermaid-svg-tVYsU3y6RxYtXln6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tVYsU3y6RxYtXln6 .error-icon{fill:#552222;}#mermaid-svg-tVYsU3y6RxYtXln6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tVYsU3y6RxYtXln6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tVYsU3y6RxYtXln6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tVYsU3y6RxYtXln6 .marker.cross{stroke:#333333;}#mermaid-svg-tVYsU3y6RxYtXln6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tVYsU3y6RxYtXln6 p{margin:0;}#mermaid-svg-tVYsU3y6RxYtXln6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 .cluster-label text{fill:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 .cluster-label span{color:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 .cluster-label span p{background-color:transparent;}#mermaid-svg-tVYsU3y6RxYtXln6 .label text,#mermaid-svg-tVYsU3y6RxYtXln6 span{fill:#333;color:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 .node rect,#mermaid-svg-tVYsU3y6RxYtXln6 .node circle,#mermaid-svg-tVYsU3y6RxYtXln6 .node ellipse,#mermaid-svg-tVYsU3y6RxYtXln6 .node polygon,#mermaid-svg-tVYsU3y6RxYtXln6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tVYsU3y6RxYtXln6 .rough-node .label text,#mermaid-svg-tVYsU3y6RxYtXln6 .node .label text,#mermaid-svg-tVYsU3y6RxYtXln6 .image-shape .label,#mermaid-svg-tVYsU3y6RxYtXln6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-tVYsU3y6RxYtXln6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tVYsU3y6RxYtXln6 .rough-node .label,#mermaid-svg-tVYsU3y6RxYtXln6 .node .label,#mermaid-svg-tVYsU3y6RxYtXln6 .image-shape .label,#mermaid-svg-tVYsU3y6RxYtXln6 .icon-shape .label{text-align:center;}#mermaid-svg-tVYsU3y6RxYtXln6 .node.clickable{cursor:pointer;}#mermaid-svg-tVYsU3y6RxYtXln6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tVYsU3y6RxYtXln6 .arrowheadPath{fill:#333333;}#mermaid-svg-tVYsU3y6RxYtXln6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tVYsU3y6RxYtXln6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tVYsU3y6RxYtXln6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tVYsU3y6RxYtXln6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tVYsU3y6RxYtXln6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tVYsU3y6RxYtXln6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tVYsU3y6RxYtXln6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tVYsU3y6RxYtXln6 .cluster text{fill:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 .cluster span{color:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tVYsU3y6RxYtXln6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tVYsU3y6RxYtXln6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-tVYsU3y6RxYtXln6 .icon-shape,#mermaid-svg-tVYsU3y6RxYtXln6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tVYsU3y6RxYtXln6 .icon-shape p,#mermaid-svg-tVYsU3y6RxYtXln6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tVYsU3y6RxYtXln6 .icon-shape .label rect,#mermaid-svg-tVYsU3y6RxYtXln6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tVYsU3y6RxYtXln6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tVYsU3y6RxYtXln6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tVYsU3y6RxYtXln6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 多路复用 epoll
epoll_wait
就绪 fd 列表
read/write
非阻塞 IO
否
是
read
就绪?
EAGAIN 立即返回
拷贝
阻塞 IO
read
阻塞等待
数据拷贝到用户
text
阻塞: [应用] ──等待──> [内核就绪] ──> 返回
非阻塞: [应用] <──EAGAIN── [内核] (循环再问)
多路复用: epoll_wait ──就绪列表──> 再 read/write
零拷贝: 数据尽量在内核路径转发,少 copy_to_user
epoll 细节见《select、poll、epoll 深入解析》。
10.3 io_uring:现代统一接口(简注)
传统 Linux AIO 对普通文件支持有限,网络高并发长期依赖 epoll + 非阻塞 。io_uring (Linux 5.1+,持续演进)通过提交队列 SQ / 完成队列 CQ ,把 read、write、accept、send 等收拢到一次 mmap 共享环 ,减少 syscall 次数,并可在内核侧串联 poll、链接操作、缓冲区注册,向「阻塞 / 非阻塞 / 多路复用 / 部分零拷贝」收敛。
| 对比 | epoll 时代 | io_uring |
|---|---|---|
| 就绪通知 | epoll_wait 后再 read/write |
可在 CQ 直接收完成事件 |
| syscall 频率 | 通常每个操作至少一次 | 批量提交,可极低 |
| 学习成本 | 成熟、资料多 | 概念多,需内核版本与库支持 |
生产上 Nginx、Redis、传统后端仍以 epoll 为主;新项目、存储与框架(如部分数据库、代理)越来越多地评估 io_uring。深入见《Linux io_uring机制详解》。
11. read 与 mmap、零拷贝
11.1 read vs mmap
| 维度 | read/write |
mmap(MAP_SHARED/PRIVATE) |
|---|---|---|
| 数据路径 | 显式拷贝到用户 buf | 映射页,访问时缺页填入/共享 Page Cache |
| 适用 | 流式、小块随机、简单逻辑 | 大文件随机访问、多进程共享只读映射 |
| 注意 | 缓冲区自己管理 | 文件变短可能导致 SIGBUS ;需 munmap |
两者底层仍常共用 Page Cache ;mmap 不是「绕过缓存」,而是减少一次显式用户态拷贝。
11.2 sendfile 零拷贝(概念)
静态文件 HTTP 发送典型路径:
text
传统: 磁盘 → 内核缓存 → 用户 buf → 内核 socket 缓冲 → 网卡
sendfile: 磁盘 → 内核缓存 ──────────────► socket 缓冲 → 网卡
(少一次 copy_to_user / copy_from_user)
#mermaid-svg-bHG0pdxzlNzJFUen{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bHG0pdxzlNzJFUen .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bHG0pdxzlNzJFUen .error-icon{fill:#552222;}#mermaid-svg-bHG0pdxzlNzJFUen .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bHG0pdxzlNzJFUen .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bHG0pdxzlNzJFUen .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bHG0pdxzlNzJFUen .marker.cross{stroke:#333333;}#mermaid-svg-bHG0pdxzlNzJFUen svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bHG0pdxzlNzJFUen p{margin:0;}#mermaid-svg-bHG0pdxzlNzJFUen .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bHG0pdxzlNzJFUen .cluster-label text{fill:#333;}#mermaid-svg-bHG0pdxzlNzJFUen .cluster-label span{color:#333;}#mermaid-svg-bHG0pdxzlNzJFUen .cluster-label span p{background-color:transparent;}#mermaid-svg-bHG0pdxzlNzJFUen .label text,#mermaid-svg-bHG0pdxzlNzJFUen span{fill:#333;color:#333;}#mermaid-svg-bHG0pdxzlNzJFUen .node rect,#mermaid-svg-bHG0pdxzlNzJFUen .node circle,#mermaid-svg-bHG0pdxzlNzJFUen .node ellipse,#mermaid-svg-bHG0pdxzlNzJFUen .node polygon,#mermaid-svg-bHG0pdxzlNzJFUen .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bHG0pdxzlNzJFUen .rough-node .label text,#mermaid-svg-bHG0pdxzlNzJFUen .node .label text,#mermaid-svg-bHG0pdxzlNzJFUen .image-shape .label,#mermaid-svg-bHG0pdxzlNzJFUen .icon-shape .label{text-anchor:middle;}#mermaid-svg-bHG0pdxzlNzJFUen .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bHG0pdxzlNzJFUen .rough-node .label,#mermaid-svg-bHG0pdxzlNzJFUen .node .label,#mermaid-svg-bHG0pdxzlNzJFUen .image-shape .label,#mermaid-svg-bHG0pdxzlNzJFUen .icon-shape .label{text-align:center;}#mermaid-svg-bHG0pdxzlNzJFUen .node.clickable{cursor:pointer;}#mermaid-svg-bHG0pdxzlNzJFUen .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bHG0pdxzlNzJFUen .arrowheadPath{fill:#333333;}#mermaid-svg-bHG0pdxzlNzJFUen .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bHG0pdxzlNzJFUen .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bHG0pdxzlNzJFUen .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bHG0pdxzlNzJFUen .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bHG0pdxzlNzJFUen .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bHG0pdxzlNzJFUen .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bHG0pdxzlNzJFUen .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bHG0pdxzlNzJFUen .cluster text{fill:#333;}#mermaid-svg-bHG0pdxzlNzJFUen .cluster span{color:#333;}#mermaid-svg-bHG0pdxzlNzJFUen div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bHG0pdxzlNzJFUen .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bHG0pdxzlNzJFUen rect.text{fill:none;stroke-width:0;}#mermaid-svg-bHG0pdxzlNzJFUen .icon-shape,#mermaid-svg-bHG0pdxzlNzJFUen .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bHG0pdxzlNzJFUen .icon-shape p,#mermaid-svg-bHG0pdxzlNzJFUen .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bHG0pdxzlNzJFUen .icon-shape .label rect,#mermaid-svg-bHG0pdxzlNzJFUen .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bHG0pdxzlNzJFUen .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bHG0pdxzlNzJFUen .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bHG0pdxzlNzJFUen :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} sendfile
文件
Page Cache
socket 缓冲区
网卡
相关 API:sendfile、splice、tee;与用户态 mmap+write 组合相比,减少 CPU 拷贝次数,高 QPS 下载、Kafka 等场景常见。
本部分回顾(四 · 模型与延伸) :单线程阻塞 IO 够用就别复杂化;高并发网络 用多路复用;少拷贝 用 sendfile/mmap;下一代批量 IO 可关注 io_uring------但 Page Cache 与持久化语义仍是底座。
12. 常见误区
| 误区 | 事实 |
|---|---|
write 成功 = 已落盘 |
多数情况只进 Page Cache;要 fsync / O_SYNC 等 |
close = 刷盘 |
close 释放 fd;脏页由异步 writeback 处理 |
| 硬链接 = 复制文件 | 同 inode,不复制数据块 |
| 软链接删了 = 删原文件 | 只删链接 inode;原文件不受影响 |
mmap 一定比 read 快 |
小文件、顺序一次性读,可能差不多甚至更差 |
| Page Cache 浪费内存 | 空闲内存用作缓存,压力上来会被回收 |
| 所有「文件」都走磁盘缓存 | socket、pipe 等走各自缓冲区,非 Page Cache |
13. 性能调优要点
| 目标 | 建议 |
|---|---|
| 减少 syscall | stdio/应用层大缓冲;批量 read/write |
| 提高顺序读吞吐 | 大块读;依赖内核 readahead |
| 降低延迟抖动 | 注意 dirty_ratio 导致写阻塞;数据库用 O_DIRECT 自管缓冲 |
| 高并发连接 | 文件 IO 少阻塞;网络用 epoll + 非阻塞 |
| 发送静态文件 | sendfile / 反向代理零拷贝能力 |
| 多线程同一 fd | 用 pread/pwrite 或每线程独立 open |
| 容器/NFS | 在真实存储上压测;Overlay/NFS 缓存行为与本地 ext4 不同 |
O_DIRECT 注意:缓冲区、文件偏移、长度常需与块大小对齐;绕过 Page Cache 后应用须自己处理缓存一致性。
14. 速查与验证
14.1 命令速查
| 目的 | 命令 |
|---|---|
| 跟踪文件 syscall | strace -ff -e trace=file -p <pid> |
| 查看进程 fd | ls -l /proc/<pid>/fd |
| inode / 元数据 | stat、ls -li |
| 内存与缓存 | free -h;`grep -E 'Cached |
| IO 统计 | iostat -x 1 |
| 测试冷缓存(仅测试机) | sync; echo 3 > /proc/sys/vm/drop_caches |
| 强制刷盘 | fsync(fd) / sync(整机,重) |
14.2 验证 Page Cache
bash
FILE=/path/to/largefile
/usr/bin/time -f '%e sec' cat "$FILE" > /dev/null # 第 1 次
/usr/bin/time -f '%e sec' cat "$FILE" > /dev/null # 第 2 次(通常更快)
14.3 验证「写未落盘」(实验环境)
bash
# 终端 A:写入后不 fsync
dd if=/dev/zero of=/tmp/test.img bs=1M count=100 conv=fdatasync
# 对比:去掉 conv=fdatasync,断电或 kill 前数据可能只在缓存
14.4 总览速查卡
text
┌────────────────────────────────────────────────────────────┐
│ 对象: 路径 → dentry → inode ← struct file ← fd │
│ 读: 磁盘 ⇄ Page Cache ─copy_to_user→ 用户 buf │
│ 写: 用户 buf → Page Cache(脏页) → writeback → 磁盘 │
│ 持久: write < fflush < fdatasync < fsync < O_SYNC │
│ 性能: 大块 IO + 少 syscall + epoll/sendfile(按场景) │
└────────────────────────────────────────────────────────────┘
附录 A:x86_64 系统调用传参示意
仅供对照「syscall 如何带参数」,与日常文件 IO 排障无强依赖,可跳过。
以 read(fd, buf, count) 为例(Linux x86_64 ABI):
| 寄存器 | 含义(read 示例) |
|---|---|
rax |
系统调用号 |
rdi |
fd |
rsi |
buf 指针 |
rdx |
count |
返回:rax 为返回值;错误时内核返回负值,libc 转为 errno。
落地注意
- 容器 OverlayFS 、NFS 、Ceph 等下 Page Cache 与一致性语义与本地 ext4 不同,压测须在目标环境复现。
- 调试「丢数据」分层排查:stdio 未 fflush → write 未 fsync → 应用队列未刷 → 磁盘写缓存未掉电保护。
- 网络 fd 与 普通文件 fd 共用 epoll,但数据路径不同,勿用文件 IO 经验直接套 socket 阻塞行为。
- 版本与挂载选项(
noatime、barrier、FS 特性)以当前内核与man 2 open为准。
延伸阅读(仓库内)
| 主题 | 文档 |
|---|---|
| FD 机制、/proc/fd、上限 | Linux文件描述符FD机制深度解析.md |
| epoll / select / poll | select_poll_epoll详解.md |
| io_uring | Linux_io_uring机制详解_异步IO原理SQPOLL与性能调优实践.md |
| OverlayFS 与容器 | Linux_OverlayFS详解.md |
| 共享内存 vs 文件映射 | Linux共享内存机制详解_映射原理API实战与同步回收避坑.md |
| proc/sysfs 虚拟 FS | Linux_proc_sysfs与devfs_虚拟文件系统解析.md |
一句话 :Linux 文件 IO = inode 表身份 + FD 表打开方式 ,数据经 Page Cache 中转;持久化与性能要在 stdio / syscall / fsync / IO 模型 四层分别做决策。