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

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


源码锚点

路径 作用
fs/namei.c path_openatlink_path_walk、lookup 快/慢路径
fs/open.c do_sys_open / do_filp_openvfs_open
fs/read_write.c vfs_read / vfs_writevfs_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 inodefilefile_operationssuper_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/meminfoDirty/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 路径/权限/挂载 nameifindmnt
EINVAL on direct I/O 对齐 块大小、缓冲地址
内存持续升高 dentry/inode/page cache slabtopvfs_cache_pressure
写延迟尖刺 脏页回写/日志 Dirty、journal、iostat
容器内写爆盘 overlay 上层 df 分层、docker system df

Checklist

  • 能口述 path_openat → dentry lookup → vfs_openf_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 内部。

相关推荐
java_logo1 小时前
Docker 部署 Rocky Linux:轻松搭建 RHEL 兼容企业级基础镜像平台
linux·docker·容器·rocky linux·基础镜像·轩辕镜像·rhel 兼容
拂拉氏1 小时前
【知识讲解】 Linux虚拟地址空间认识
linux·虚拟地址空间
cloudfantasy3 小时前
docker直接二进制安装,非编译。KylinV10SP3 aarch64有效
运维·docker
新时代牛马3 小时前
Linux 驱动中断与定时完整篇:从 request_threaded_irq 到hrtimer 选型与排障
linux·运维·服务器
考虑考虑4 小时前
nginx打印请求日志
运维·后端·nginx
数字补给站5 小时前
Windows 10 + Hyper-V + Terraform + cloud-init:批量产出 3 台 Ubuntu 的实践笔记
运维·ai编程
分支预测失败5 小时前
RISC-V 核间中断 IPI 实战:从 MSIP 寄存器到 IMSIC 消息投递
linux·后端
这个DBA有点耶6 小时前
同样48核配置TPS差1倍?高性价比数据库一体机的“软硬协同”才是分水岭
服务器·数据库·架构
GeW6 小时前
RHCE考试避坑指南"90%考生容易忽略的细节
linux