Linux VFS 完整篇:从path_openat、dentry/inode 到page cache 与挂载排障

open 返回诡异 errno、海量小文件把内存打满、O_DIRECT 对齐失败、容器 overlay 写放大被误判成「磁盘慢」------根因多半落在 VFS 对象模型与挂载/缓存策略 ,而不是某一个具体 FS 的业务逻辑。本文把路径查找、打开绑定、读写与 page cache、挂载选项与观测命令合成一篇闭环,便于对照源码与 strace/findmnt 验证。


源码锚点

路径 作用
fs/namei.c path_openat、link_path_walk、lookup 快/慢路径
fs/open.c do_sys_open / do_filp_open、vfs_open
fs/read_write.c vfs_read / vfs_write、vfs_iter_*
fs/dcache.c dentry 缓存、negative dentry、回收协作
fs/inode.c inode 生命周期、权限相关入口
fs/super.c super_block、挂载/卸载协作
mm/filemap.c page cache:filemap_read / generic_file_*
include/linux/fs.h inode、file、file_operations、super_operations
include/linux/dcache.h struct dentry
Documentation/filesystems/vfs.rst VFS 方法表总览

打开后用户态 fd 对应的内核对象:

c 复制代码
/* include/linux/fs.h */
struct file {
	const struct file_operations *f_op;
	struct inode *f_inode;
	void *private_data;
	loff_t f_pos;
	/* ... */
};

struct file_operations {
	struct module *owner;
	ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
	int (*open)(struct inode *, struct file *);
	/* ... */
};

路径打开逻辑骨架(版本细节以树内符号为准):

c 复制代码
/* 用户态 openat → 系统调用 → fs/namei.c / fs/open.c */
path_openat(...)
  → link_path_walk          /* 逐组件 */
  → lookup_fast(dcache) 或 lookup_slow(→ inode->i_op->lookup)
  → 处理 O_CREAT / O_TRUNC / 尾随斜线等
  → vfs_open
      → file->f_op = fops_get(inode->i_fop)
      → f_op->open(inode, file)   /* 具体 FS 或设备 fops */

调用链

打开与读写主路径

#mermaid-svg-3mn6ZN8DZJIigoMN{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-3mn6ZN8DZJIigoMN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3mn6ZN8DZJIigoMN .error-icon{fill:#552222;}#mermaid-svg-3mn6ZN8DZJIigoMN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3mn6ZN8DZJIigoMN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3mn6ZN8DZJIigoMN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3mn6ZN8DZJIigoMN .marker.cross{stroke:#333333;}#mermaid-svg-3mn6ZN8DZJIigoMN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3mn6ZN8DZJIigoMN p{margin:0;}#mermaid-svg-3mn6ZN8DZJIigoMN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN .cluster-label text{fill:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN .cluster-label span{color:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN .cluster-label span p{background-color:transparent;}#mermaid-svg-3mn6ZN8DZJIigoMN .label text,#mermaid-svg-3mn6ZN8DZJIigoMN span{fill:#333;color:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN .node rect,#mermaid-svg-3mn6ZN8DZJIigoMN .node circle,#mermaid-svg-3mn6ZN8DZJIigoMN .node ellipse,#mermaid-svg-3mn6ZN8DZJIigoMN .node polygon,#mermaid-svg-3mn6ZN8DZJIigoMN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3mn6ZN8DZJIigoMN .rough-node .label text,#mermaid-svg-3mn6ZN8DZJIigoMN .node .label text,#mermaid-svg-3mn6ZN8DZJIigoMN .image-shape .label,#mermaid-svg-3mn6ZN8DZJIigoMN .icon-shape .label{text-anchor:middle;}#mermaid-svg-3mn6ZN8DZJIigoMN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3mn6ZN8DZJIigoMN .rough-node .label,#mermaid-svg-3mn6ZN8DZJIigoMN .node .label,#mermaid-svg-3mn6ZN8DZJIigoMN .image-shape .label,#mermaid-svg-3mn6ZN8DZJIigoMN .icon-shape .label{text-align:center;}#mermaid-svg-3mn6ZN8DZJIigoMN .node.clickable{cursor:pointer;}#mermaid-svg-3mn6ZN8DZJIigoMN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3mn6ZN8DZJIigoMN .arrowheadPath{fill:#333333;}#mermaid-svg-3mn6ZN8DZJIigoMN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3mn6ZN8DZJIigoMN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3mn6ZN8DZJIigoMN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3mn6ZN8DZJIigoMN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3mn6ZN8DZJIigoMN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3mn6ZN8DZJIigoMN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3mn6ZN8DZJIigoMN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3mn6ZN8DZJIigoMN .cluster text{fill:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN .cluster span{color:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN 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-3mn6ZN8DZJIigoMN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3mn6ZN8DZJIigoMN rect.text{fill:none;stroke-width:0;}#mermaid-svg-3mn6ZN8DZJIigoMN .icon-shape,#mermaid-svg-3mn6ZN8DZJIigoMN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3mn6ZN8DZJIigoMN .icon-shape p,#mermaid-svg-3mn6ZN8DZJIigoMN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3mn6ZN8DZJIigoMN .icon-shape .label rect,#mermaid-svg-3mn6ZN8DZJIigoMN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3mn6ZN8DZJIigoMN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3mn6ZN8DZJIigoMN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3mn6ZN8DZJIigoMN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
命中
未命中
openat(dfd, path, flags)
do_sys_open / do_filp_open
path_openat
link_path_walk
dcache 命中?
lookup_fast
lookup_slow → i_op->lookup
权限 / 标志处理
vfs_open
绑定 file->f_op = inode->i_fop
f_op->open
read → vfs_read / read_iter
page cache?
直接拷贝用户态
generic_file_* → FS → 块层

对象分层与缓存协作

#mermaid-svg-o5knMfQSXEY1EFIJ{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-o5knMfQSXEY1EFIJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o5knMfQSXEY1EFIJ .error-icon{fill:#552222;}#mermaid-svg-o5knMfQSXEY1EFIJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o5knMfQSXEY1EFIJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o5knMfQSXEY1EFIJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o5knMfQSXEY1EFIJ .marker.cross{stroke:#333333;}#mermaid-svg-o5knMfQSXEY1EFIJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o5knMfQSXEY1EFIJ p{margin:0;}#mermaid-svg-o5knMfQSXEY1EFIJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ .cluster-label text{fill:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ .cluster-label span{color:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ .cluster-label span p{background-color:transparent;}#mermaid-svg-o5knMfQSXEY1EFIJ .label text,#mermaid-svg-o5knMfQSXEY1EFIJ span{fill:#333;color:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ .node rect,#mermaid-svg-o5knMfQSXEY1EFIJ .node circle,#mermaid-svg-o5knMfQSXEY1EFIJ .node ellipse,#mermaid-svg-o5knMfQSXEY1EFIJ .node polygon,#mermaid-svg-o5knMfQSXEY1EFIJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o5knMfQSXEY1EFIJ .rough-node .label text,#mermaid-svg-o5knMfQSXEY1EFIJ .node .label text,#mermaid-svg-o5knMfQSXEY1EFIJ .image-shape .label,#mermaid-svg-o5knMfQSXEY1EFIJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-o5knMfQSXEY1EFIJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o5knMfQSXEY1EFIJ .rough-node .label,#mermaid-svg-o5knMfQSXEY1EFIJ .node .label,#mermaid-svg-o5knMfQSXEY1EFIJ .image-shape .label,#mermaid-svg-o5knMfQSXEY1EFIJ .icon-shape .label{text-align:center;}#mermaid-svg-o5knMfQSXEY1EFIJ .node.clickable{cursor:pointer;}#mermaid-svg-o5knMfQSXEY1EFIJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o5knMfQSXEY1EFIJ .arrowheadPath{fill:#333333;}#mermaid-svg-o5knMfQSXEY1EFIJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o5knMfQSXEY1EFIJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o5knMfQSXEY1EFIJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o5knMfQSXEY1EFIJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o5knMfQSXEY1EFIJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o5knMfQSXEY1EFIJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o5knMfQSXEY1EFIJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o5knMfQSXEY1EFIJ .cluster text{fill:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ .cluster span{color:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ 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-o5knMfQSXEY1EFIJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o5knMfQSXEY1EFIJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-o5knMfQSXEY1EFIJ .icon-shape,#mermaid-svg-o5knMfQSXEY1EFIJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o5knMfQSXEY1EFIJ .icon-shape p,#mermaid-svg-o5knMfQSXEY1EFIJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o5knMfQSXEY1EFIJ .icon-shape .label rect,#mermaid-svg-o5knMfQSXEY1EFIJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o5knMfQSXEY1EFIJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o5knMfQSXEY1EFIJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o5knMfQSXEY1EFIJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 缓存与后端
VFS对象
用户态
进程 fd 表
struct file
struct dentry
struct inode
struct super_block
dcache
page cache / i_mapping
具体 FS: ext4/XFS/...
块层 bio


重点知识

1. 四件套分工先分清,再谈「慢」

对象 职责 排障信号
dentry 路径组件缓存(含 negative) 找不到路径、海量小文件内存涨
inode 元数据 + i_op/i_fop + i_mapping 权限/大小/时间戳异常
file 一次打开会话(位置、模式、private_data) 打开后读写/ioctl 失败
super_block 一个挂载实例的 FS 全局状态 挂载只读、配额、冻结

故障先归类:路径找不到 (dentry/权限)还是 打开后读写失败(f_op/块层),避免一上来改 FS 调优参数。

2. file->f_op 在 open 时钉死

后续 read/write/ioctl 都走这张表。设备节点会在字符/块层替换 fops(如 chrdev_open);普通文件则是具体 FS 的 file_operations。挂错类型、overlay 层错乱、错误的 inode,都会进错操作集。

3. dcache、negative dentry 与内存压力

海量小文件遍历会堆 dentry/inode;vm.vfs_cache_pressure 影响回收积极性。negative dentry 加速「反复查不存在路径」,也能在并发创建场景下制造短暂「看不见刚创建的文件」的错觉------结合业务与 strace 看是 ENOENT 还是竞态。

4. page cache、回写与 O_DIRECT

  • 缓冲 I/O:命中 i_mapping 则少读盘;脏页经 writeback 回盘。
  • O_DIRECT:绕过 page cache,偏移/长度需按逻辑块对齐,否则常见 EINVAL。
  • 观测脏页与回写:/proc/meminfo 的 Dirty/Writeback,配合 iostat -x。

5. 挂载选项与容器场景

bash 复制代码
findmnt -T /path/to/file
mount | grep <mp>
# 常见:noatime 降元数据写;ro/nodev/nosuid 安全边界
cat /proc/sys/vm/vfs_cache_pressure
场景 建议 常见坑
数据库数据目录 单独挂载、评估 noatime atime 更新放大写
容器 rootfs 理解 overlay 上层写放大 误判底层磁盘慢
SSD discard/fstrim 策略明确 盲目 discard 拖延迟
只读根 ro + 可写目录分离 应用写日志失败

6. 挂载与超级块:VFS 的另一半入口

打开路径管「文件」,挂载路径管「一棵树挂到哪里」。用户态 mount 最终落到 VFS 的挂载点与 super_block 装配:类型、标志(MS_RDONLY 等)、选项字符串交给具体 FS 的 fill_super/get_tree 一类入口(现代用 fs_context)。排障时:

  • findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS -T <path> 看实际生效选项;
  • 只读根上写失败先看挂载 flags,再看应用路径是否写到只读层;
  • 绑定挂载/rbind 容易造成「同一 inode 多个路径」的认知错乱,删文件前先 findmnt。

7. 写路径与回写(和读对称)

text 复制代码
write → vfs_write / write_iter
  → 缓冲写:标记 page dirty → 稍后 writeback → FS → bio
  → O_SYNC/fdatasync:在返回前推进完整性语义(细节随 FS)

脏页过高时,应用线程可能被卡住参与回写------表象是「业务变慢」,根因在写放大或落盘瓶颈。配合 Dirty/Writeback 与 FS journal 模式(如 ext4 data=ordered)一起看。

8. 观测与排障命令

bash 复制代码
strace -e openat,open,stat,read,write cat /path/to/file
cat /proc/meminfo | grep -E 'Cached|Dirty|Writeback'
iostat -x 1
slabtop -o | head   # 关注 dentry/inode 相关 slab
# 权限与挂载
namei -l /path/to/file
ls -ld $(dirname /path/to/file)
现象 优先怀疑 核对点
ENOENT/EACCES 路径/权限/挂载 namei、findmnt
EINVAL on direct I/O 对齐 块大小、缓冲地址
内存持续升高 dentry/inode/page cache slabtop、vfs_cache_pressure
写延迟尖刺 脏页回写/日志 Dirty、journal、iostat
容器内写爆盘 overlay 上层 df 分层、docker system df 等

Checklist

  • 能口述 path_openat → dentry lookup → vfs_open → f_op->open 的链
  • 分清故障在路径查找、权限、还是 f_op/块层
  • strace 的 open 标志与 errno 能对应到 VFS 分支
  • 理解 dentry/inode/file/super_block 各自职责
  • 海量小文件场景评估过 vfs_cache_pressure 与目录布局
  • O_DIRECT/挂载选项(noatime 等)按业务验证,而非照抄博客
  • 容器 overlay 写放大与底层磁盘延迟能区分排查

小结

VFS 的设计意图是:用统一对象模型让 ext4/XFS/NFS/proc 共享系统调用入口。排障按「路径 → 绑定 f_op → 缓存/块层 → 挂载选项」分层推进;调优先改可观测、可回滚的挂载与压力参数,再碰具体 FS 内部。

相关推荐
雯宝1 小时前
mnt_init 函数
linux
律宏阔7 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔7 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴7 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千7 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei377 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei377 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei377 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh7 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW7 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux