一次普通的 read 为什么有时只需微秒,有时却要等待毫秒?write 已经成功返回,断电后数据为什么仍可能丢失?磁盘空间明明充足,创建文件为什么会报 No space left on device?理解这些问题,需要把文件描述符、VFS、inode、页缓存、文件系统和块设备串成一条完整链路。
本文以 Linux 为例,解释文件访问的核心对象、读写路径、缓存与持久化语义,并给出可用于生产排障的观测方法。
1. 一次文件访问经过哪些层次
应用不会直接操作磁盘。系统调用进入内核后,VFS 提供统一接口,具体文件系统负责布局与一致性,块层和设备驱动最终把请求交给存储设备。
#mermaid-svg-PZgCd1Xxn0VUYbEI{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-PZgCd1Xxn0VUYbEI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PZgCd1Xxn0VUYbEI .error-icon{fill:#552222;}#mermaid-svg-PZgCd1Xxn0VUYbEI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PZgCd1Xxn0VUYbEI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .marker.cross{stroke:#333333;}#mermaid-svg-PZgCd1Xxn0VUYbEI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PZgCd1Xxn0VUYbEI p{margin:0;}#mermaid-svg-PZgCd1Xxn0VUYbEI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .cluster-label text{fill:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .cluster-label span{color:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .cluster-label span p{background-color:transparent;}#mermaid-svg-PZgCd1Xxn0VUYbEI .label text,#mermaid-svg-PZgCd1Xxn0VUYbEI span{fill:#333;color:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .node rect,#mermaid-svg-PZgCd1Xxn0VUYbEI .node circle,#mermaid-svg-PZgCd1Xxn0VUYbEI .node ellipse,#mermaid-svg-PZgCd1Xxn0VUYbEI .node polygon,#mermaid-svg-PZgCd1Xxn0VUYbEI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .rough-node .label text,#mermaid-svg-PZgCd1Xxn0VUYbEI .node .label text,#mermaid-svg-PZgCd1Xxn0VUYbEI .image-shape .label,#mermaid-svg-PZgCd1Xxn0VUYbEI .icon-shape .label{text-anchor:middle;}#mermaid-svg-PZgCd1Xxn0VUYbEI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .rough-node .label,#mermaid-svg-PZgCd1Xxn0VUYbEI .node .label,#mermaid-svg-PZgCd1Xxn0VUYbEI .image-shape .label,#mermaid-svg-PZgCd1Xxn0VUYbEI .icon-shape .label{text-align:center;}#mermaid-svg-PZgCd1Xxn0VUYbEI .node.clickable{cursor:pointer;}#mermaid-svg-PZgCd1Xxn0VUYbEI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .arrowheadPath{fill:#333333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZgCd1Xxn0VUYbEI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PZgCd1Xxn0VUYbEI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZgCd1Xxn0VUYbEI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PZgCd1Xxn0VUYbEI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .cluster text{fill:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI .cluster span{color:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI 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-PZgCd1Xxn0VUYbEI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PZgCd1Xxn0VUYbEI rect.text{fill:none;stroke-width:0;}#mermaid-svg-PZgCd1Xxn0VUYbEI .icon-shape,#mermaid-svg-PZgCd1Xxn0VUYbEI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PZgCd1Xxn0VUYbEI .icon-shape p,#mermaid-svg-PZgCd1Xxn0VUYbEI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PZgCd1Xxn0VUYbEI .icon-shape .label rect,#mermaid-svg-PZgCd1Xxn0VUYbEI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PZgCd1Xxn0VUYbEI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PZgCd1Xxn0VUYbEI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PZgCd1Xxn0VUYbEI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-PZgCd1Xxn0VUYbEI .app>*{fill:#e8f1ff!important;stroke:#2563eb!important;color:#172554!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .app span{fill:#e8f1ff!important;stroke:#2563eb!important;color:#172554!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .app tspan{fill:#172554!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .kernel>*{fill:#ecfdf5!important;stroke:#059669!important;color:#064e3b!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .kernel span{fill:#ecfdf5!important;stroke:#059669!important;color:#064e3b!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .kernel tspan{fill:#064e3b!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .device>*{fill:#fff7ed!important;stroke:#ea580c!important;color:#7c2d12!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .device span{fill:#fff7ed!important;stroke:#ea580c!important;color:#7c2d12!important;}#mermaid-svg-PZgCd1Xxn0VUYbEI .device tspan{fill:#7c2d12!important;}
read / write
应用程序
系统调用
VFS 虚拟文件系统
ext4 / XFS 等文件系统
Page Cache
通用块层
I/O 调度与设备驱动
SSD / HDD / 云盘
这套分层让应用可以用相同的接口访问不同文件系统,但也意味着“磁盘慢”只是众多可能性之一。路径解析、锁竞争、页缓存回收、文件系统日志和设备队列都可能影响延迟。
2. 文件、inode 与目录项
Linux 中,文件名并不直接保存文件内容。几个核心对象各司其职:
| inode | 保存文件类型、权限、所有者、大小、时间戳和数据块位置等元数据 | 否 |
| dentry | 缓存“目录 + 名称”到 inode 的映射,加速路径解析 | 是 |
| file | 表示一次打开实例,包含当前偏移、打开标志等状态 | 否 |
| file descriptor | 进程文件描述符表中的整数索引 | 否 |
执行 open("/data/report.log", O_RDONLY) 时,内核需要逐级解析 /、data 和 report.log。路径解析可能命中 dentry 缓存,也可能需要读取目录数据。成功后,进程得到一个文件描述符,例如 3。
flowchart TD
P[路径 /data/report.log] –> R[解析根目录 /]
R –> D[查找目录项 data]
D –> L[查找目录项 report.log]
L –> I[定位 inode]
I –> O[创建打开文件对象 file]
O –> F[写入进程文件描述符表]
F –> N[返回 fd = 3]
M{目录项缓存命中?}
D -.查询.-> M
M –>|是| L
M –>|否| X[读取文件系统目录数据]
X –> L
文件描述符是进程局部的。不同进程中的 fd 3 可以指向完全不同的对象;同一文件也可以被打开多次,形成多个具有独立偏移的打开实例。
3. 硬链接与符号链接
硬链接是在目录中增加一个指向同一 inode 的名称。删除其中一个名称只会减少链接计数,当链接计数归零且没有进程继续打开该文件时,文件数据才可被真正回收。
符号链接则是一个独立文件,其内容是目标路径。它可以跨文件系统,也可以指向目录,但目标被移动或删除后可能成为悬空链接。
ln report.log report-hard.log
ln -s report.log report-soft.log
ls -li report*.log
“删除正在写入的日志后磁盘空间没有释放”经常与上述语义有关。目录项虽然消失,进程仍持有打开文件对象,inode 和数据块不能释放。可以用下面的命令定位:
lsof +L1
正确处理方式通常是让进程重新打开日志或正常重启,而不是继续删除其他文件。
4. read 的两条路径
普通缓冲 I/O 首先查询页缓存。如果目标页已在内存,内核可以直接把数据复制到用户缓冲区;若未命中,则提交设备 I/O,等待数据进入页缓存后再完成读取。
#mermaid-svg-9bz37tQv9CL5GlCi{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-9bz37tQv9CL5GlCi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9bz37tQv9CL5GlCi .error-icon{fill:#552222;}#mermaid-svg-9bz37tQv9CL5GlCi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9bz37tQv9CL5GlCi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9bz37tQv9CL5GlCi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9bz37tQv9CL5GlCi .marker.cross{stroke:#333333;}#mermaid-svg-9bz37tQv9CL5GlCi svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9bz37tQv9CL5GlCi p{margin:0;}#mermaid-svg-9bz37tQv9CL5GlCi .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-9bz37tQv9CL5GlCi .cluster-label text{fill:#333;}#mermaid-svg-9bz37tQv9CL5GlCi .cluster-label span{color:#333;}#mermaid-svg-9bz37tQv9CL5GlCi .cluster-label span p{background-color:transparent;}#mermaid-svg-9bz37tQv9CL5GlCi .label text,#mermaid-svg-9bz37tQv9CL5GlCi span{fill:#333;color:#333;}#mermaid-svg-9bz37tQv9CL5GlCi .node rect,#mermaid-svg-9bz37tQv9CL5GlCi .node circle,#mermaid-svg-9bz37tQv9CL5GlCi .node ellipse,#mermaid-svg-9bz37tQv9CL5GlCi .node polygon,#mermaid-svg-9bz37tQv9CL5GlCi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9bz37tQv9CL5GlCi .rough-node .label text,#mermaid-svg-9bz37tQv9CL5GlCi .node .label text,#mermaid-svg-9bz37tQv9CL5GlCi .image-shape .label,#mermaid-svg-9bz37tQv9CL5GlCi .icon-shape .label{text-anchor:middle;}#mermaid-svg-9bz37tQv9CL5GlCi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9bz37tQv9CL5GlCi .rough-node .label,#mermaid-svg-9bz37tQv9CL5GlCi .node .label,#mermaid-svg-9bz37tQv9CL5GlCi .image-shape .label,#mermaid-svg-9bz37tQv9CL5GlCi .icon-shape .label{text-align:center;}#mermaid-svg-9bz37tQv9CL5GlCi .node.clickable{cursor:pointer;}#mermaid-svg-9bz37tQv9CL5GlCi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9bz37tQv9CL5GlCi .arrowheadPath{fill:#333333;}#mermaid-svg-9bz37tQv9CL5GlCi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9bz37tQv9CL5GlCi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9bz37tQv9CL5GlCi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9bz37tQv9CL5GlCi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9bz37tQv9CL5GlCi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9bz37tQv9CL5GlCi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9bz37tQv9CL5GlCi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9bz37tQv9CL5GlCi .cluster text{fill:#333;}#mermaid-svg-9bz37tQv9CL5GlCi .cluster span{color:#333;}#mermaid-svg-9bz37tQv9CL5GlCi 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-9bz37tQv9CL5GlCi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9bz37tQv9CL5GlCi rect.text{fill:none;stroke-width:0;}#mermaid-svg-9bz37tQv9CL5GlCi .icon-shape,#mermaid-svg-9bz37tQv9CL5GlCi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9bz37tQv9CL5GlCi .icon-shape p,#mermaid-svg-9bz37tQv9CL5GlCi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9bz37tQv9CL5GlCi .icon-shape .label rect,#mermaid-svg-9bz37tQv9CL5GlCi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9bz37tQv9CL5GlCi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9bz37tQv9CL5GlCi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9bz37tQv9CL5GlCi :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-9bz37tQv9CL5GlCi .fast>*{fill:#ecfdf5!important;stroke:#059669!important;color:#064e3b!important;}#mermaid-svg-9bz37tQv9CL5GlCi .fast span{fill:#ecfdf5!important;stroke:#059669!important;color:#064e3b!important;}#mermaid-svg-9bz37tQv9CL5GlCi .fast tspan{fill:#064e3b!important;}#mermaid-svg-9bz37tQv9CL5GlCi .slow>*{fill:#fff7ed!important;stroke:#ea580c!important;color:#7c2d12!important;}#mermaid-svg-9bz37tQv9CL5GlCi .slow span{fill:#fff7ed!important;stroke:#ea580c!important;color:#7c2d12!important;}#mermaid-svg-9bz37tQv9CL5GlCi .slow tspan{fill:#7c2d12!important;}
命中
未命中
应用调用 read
Page Cache 命中?
复制到用户缓冲区
read 返回
提交块设备读取
线程等待 I/O
设备完成请求
数据进入 Page Cache
顺序读取时,内核可能预读后续页面,以设备吞吐换取更低的未来等待时间。随机读取若跨度大、工作集超过内存,则更容易产生主缺页和设备 I/O。
缓存命中也不代表没有成本:数据复制、页表访问、内存带宽、NUMA 距离和锁竞争仍会影响性能。判断瓶颈必须基于实际指标,而不是只看设备利用率。
5. write 返回不代表数据已经持久化
普通 write 通常把数据复制到页缓存中的脏页,随后即可返回。内核回写线程稍后把脏页写入文件系统和块设备。因此,write 成功主要表示数据已被内核接收,并不等价于断电后仍可恢复。
存储设备文件系统内核页缓存应用存储设备文件系统内核页缓存应用#mermaid-svg-0Mlk8knplQwn9AVi{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-0Mlk8knplQwn9AVi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0Mlk8knplQwn9AVi .error-icon{fill:#552222;}#mermaid-svg-0Mlk8knplQwn9AVi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0Mlk8knplQwn9AVi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0Mlk8knplQwn9AVi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0Mlk8knplQwn9AVi .marker.cross{stroke:#333333;}#mermaid-svg-0Mlk8knplQwn9AVi svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0Mlk8knplQwn9AVi p{margin:0;}#mermaid-svg-0Mlk8knplQwn9AVi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0Mlk8knplQwn9AVi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0Mlk8knplQwn9AVi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0Mlk8knplQwn9AVi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0Mlk8knplQwn9AVi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0Mlk8knplQwn9AVi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0Mlk8knplQwn9AVi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0Mlk8knplQwn9AVi .sequenceNumber{fill:white;}#mermaid-svg-0Mlk8knplQwn9AVi #sequencenumber{fill:#333;}#mermaid-svg-0Mlk8knplQwn9AVi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0Mlk8knplQwn9AVi .messageText{fill:#333;stroke:none;}#mermaid-svg-0Mlk8knplQwn9AVi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0Mlk8knplQwn9AVi .labelText,#mermaid-svg-0Mlk8knplQwn9AVi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0Mlk8knplQwn9AVi .loopText,#mermaid-svg-0Mlk8knplQwn9AVi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0Mlk8knplQwn9AVi .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-0Mlk8knplQwn9AVi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0Mlk8knplQwn9AVi .noteText,#mermaid-svg-0Mlk8knplQwn9AVi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0Mlk8knplQwn9AVi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0Mlk8knplQwn9AVi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0Mlk8knplQwn9AVi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0Mlk8knplQwn9AVi .actorPopupMenu{position:absolute;}#mermaid-svg-0Mlk8knplQwn9AVi .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-0Mlk8knplQwn9AVi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0Mlk8knplQwn9AVi .actor-man circle,#mermaid-svg-0Mlk8knplQwn9AVi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0Mlk8knplQwn9AVi :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}页面被标记为 dirtywrite(fd, data)返回成功后台回写提交写请求完成fsync(fd)刷新文件数据和必要元数据flush / barrier持久化完成fsync 返回
需要持久化保证时,应理解几个接口的差异:
- fsync(fd):要求文件数据及恢复一致性所需的元数据落到稳定存储;
- fdatasync(fd):重点同步文件数据和影响读取结果的必要元数据;
- sync():触发系统范围的脏数据同步,粒度较粗;
- O_SYNC:让写入带有更强同步语义,但会显著影响吞吐与延迟。
即使调用 fsync,最终保证仍依赖文件系统、设备固件、写缓存和虚拟化存储是否正确实现刷新语义。对数据库、消息日志等关键数据,必须做断电与故障注入测试。
6. 原子更新一个配置文件
直接覆盖原文件可能在崩溃时留下半截内容。更稳妥的流程是:在同一目录创建临时文件,写完并同步临时文件,再原子重命名,最后同步目录。
#mermaid-svg-VZKQ1dcDaprfKX1h{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-VZKQ1dcDaprfKX1h .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VZKQ1dcDaprfKX1h .error-icon{fill:#552222;}#mermaid-svg-VZKQ1dcDaprfKX1h .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VZKQ1dcDaprfKX1h .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VZKQ1dcDaprfKX1h .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VZKQ1dcDaprfKX1h .marker.cross{stroke:#333333;}#mermaid-svg-VZKQ1dcDaprfKX1h svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VZKQ1dcDaprfKX1h p{margin:0;}#mermaid-svg-VZKQ1dcDaprfKX1h .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h .cluster-label text{fill:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h .cluster-label span{color:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h .cluster-label span p{background-color:transparent;}#mermaid-svg-VZKQ1dcDaprfKX1h .label text,#mermaid-svg-VZKQ1dcDaprfKX1h span{fill:#333;color:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h .node rect,#mermaid-svg-VZKQ1dcDaprfKX1h .node circle,#mermaid-svg-VZKQ1dcDaprfKX1h .node ellipse,#mermaid-svg-VZKQ1dcDaprfKX1h .node polygon,#mermaid-svg-VZKQ1dcDaprfKX1h .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VZKQ1dcDaprfKX1h .rough-node .label text,#mermaid-svg-VZKQ1dcDaprfKX1h .node .label text,#mermaid-svg-VZKQ1dcDaprfKX1h .image-shape .label,#mermaid-svg-VZKQ1dcDaprfKX1h .icon-shape .label{text-anchor:middle;}#mermaid-svg-VZKQ1dcDaprfKX1h .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VZKQ1dcDaprfKX1h .rough-node .label,#mermaid-svg-VZKQ1dcDaprfKX1h .node .label,#mermaid-svg-VZKQ1dcDaprfKX1h .image-shape .label,#mermaid-svg-VZKQ1dcDaprfKX1h .icon-shape .label{text-align:center;}#mermaid-svg-VZKQ1dcDaprfKX1h .node.clickable{cursor:pointer;}#mermaid-svg-VZKQ1dcDaprfKX1h .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VZKQ1dcDaprfKX1h .arrowheadPath{fill:#333333;}#mermaid-svg-VZKQ1dcDaprfKX1h .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VZKQ1dcDaprfKX1h .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VZKQ1dcDaprfKX1h .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VZKQ1dcDaprfKX1h .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VZKQ1dcDaprfKX1h .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VZKQ1dcDaprfKX1h .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VZKQ1dcDaprfKX1h .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VZKQ1dcDaprfKX1h .cluster text{fill:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h .cluster span{color:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h 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-VZKQ1dcDaprfKX1h .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VZKQ1dcDaprfKX1h rect.text{fill:none;stroke-width:0;}#mermaid-svg-VZKQ1dcDaprfKX1h .icon-shape,#mermaid-svg-VZKQ1dcDaprfKX1h .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VZKQ1dcDaprfKX1h .icon-shape p,#mermaid-svg-VZKQ1dcDaprfKX1h .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VZKQ1dcDaprfKX1h .icon-shape .label rect,#mermaid-svg-VZKQ1dcDaprfKX1h .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VZKQ1dcDaprfKX1h .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VZKQ1dcDaprfKX1h .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VZKQ1dcDaprfKX1h :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
创建同目录临时文件
写入完整内容
fsync 临时文件
rename 替换目标文件
fsync 父目录
更新完成
之所以要求“同一目录”,是因为跨文件系统的 rename 不能提供同样的原子替换语义。同步父目录用于确保目录项变更具备所需的持久性。
这个流程保证读者看到旧版本或新版本,而不是中间状态,但它并不自动解决多写者并发覆盖,多个写者仍需要版本检查或锁。
7. 文件系统日志解决什么问题
崩溃可能发生在多步元数据更新之间。例如创建文件需要修改目录、分配 inode、更新位图。如果只完成其中一部分,文件系统结构会不一致。
日志文件系统会先把一组更新的意图或相关内容写入日志,再更新正式位置。重启后可以通过重放或回滚日志事务恢复一致状态。
要注意,文件系统日志主要保护文件系统结构一致性,不天然等于应用数据事务。应用执行了两次 write,日志文件系统并不会自动保证两段业务数据同时出现。应用仍需设计自己的提交协议并在正确位置调用同步接口。
8. Buffered I/O、Direct I/O 与 mmap
| Buffered I/O | 默认经过页缓存 | 通用文件访问、热点数据复用 | 可能产生额外复制和双重缓存 |
| Direct I/O | 尽量绕过页缓存,常有对齐要求 | 自带缓存的数据库、可控大块 I/O | 编程复杂,小 I/O 性能可能差 |
| mmap | 文件页映射进地址空间,访问时缺页 | 随机读取、共享映射 | 缺页延迟隐蔽,错误与回收更复杂 |
Direct I/O 不等于每次写入都已持久化,也不等于一定更快。它主要改变缓存路径;持久化仍要遵循对应接口和设备语义。
数据库常使用 Direct I/O 避免数据库缓冲池与操作系统页缓存重复保存同一份数据,但普通应用通常能从页缓存、预读和回写聚合中获益。
9. inode 耗尽为何也会报磁盘已满
文件系统不仅需要数据块,也需要 inode。大量极小文件可能先耗尽 inode,即使还有很多字节空间,也无法创建新文件。
df -h
df -i
du -xhd1 /var | sort -h
df 查看文件系统分配视角,du 遍历当前可见目录项统计文件大小。两者差异很大时,应检查已删除但仍打开的文件、挂载点遮挡、稀疏文件和文件系统保留空间。
10. Linux I/O 排障路径
第一步:确认是 CPU 等待还是设备拥塞
vmstat 1
iostat -xz 1
关注 await、队列深度、吞吐量和设备利用率,并结合业务延迟判断。高 util 对传统单队列磁盘通常意味着繁忙,但对现代并行设备和虚拟磁盘不能脱离队列与延迟机械判断。
第二步:定位产生 I/O 的进程
pidstat -d 1
iotop -oPa
lsof -p 12345
进程写入量大不一定立刻表现为设备写入量,因为页缓存会延迟和合并回写。观测窗口过短可能得出错误结论。
第三步:查看文件系统与内核事件
df -h
df -i
findmnt
journalctl -k –since "30 min ago"
若延迟集中在特定调用,可以用 strace -T -p <pid> 短时观察系统调用耗时;生产环境使用前应评估跟踪开销。更深入的分析可使用 perf、eBPF 工具和块层追踪。
11. 常见误区
12. 总结
Linux 文件 I/O 是一条跨越用户态、VFS、页缓存、具体文件系统、块层和设备的完整链路。inode 描述文件,目录项把名字关联到 inode,打开文件对象保存一次打开的状态,文件描述符只是进程访问它的索引。
排障时先判断请求停在哪一层:路径解析、缓存缺失、脏页回写、文件系统日志、块设备队列还是存储硬件。设计可靠写入时,则必须区分“系统调用成功”“进入设备缓存”和“稳定持久化”三个时刻。只有明确每一层提供的语义,才能同时获得性能与正确性。


