本页目录
VFS — 虚拟文件系统层
覆盖: VFS 对象模型 (super_block/inode/dentry/file) → 系统调用路径 (open/read/write/close) → file_operations → RCU path walk → mount 机制 内核版本: 2.6 ~ 6.x
概述
VFS (Virtual File System / Virtual Filesystem Switch) 是 Linux "一切皆文件" 的基石。它定义了一组抽象对象和操作接口,让 ext4、NFS、procfs 等完全不同的文件系统可以统一暴露给用户空间。一个 read() 系统调用不需要知道底层是本地磁盘还是网络挂载——VFS 层负责路由到正确的文件系统实现。
VFS 的核心设计哲学是面向对象的 C 语言实现:每个 VFS 对象内嵌一个 *_operations 函数表,文件系统通过填充这些函数表来"实现接口"。
VFS 四大对象
super_block: "这个文件系统是什么?" — 一个挂载点对应一个 super_block
inode: "这个文件是谁?" — 每个文件/目录一个 (存于磁盘或内存)
dentry: "这个路径名指向谁?" — 路径名到 inode 的缓存映射
file: "这个打开的文件在哪里?" — 每次 open() 创建一个, 含当前偏移
super_block
// include/linux/fs.h
;
inode
;
dentry — 路径名缓存
;
Negative dentry: d_inode == NULL。表示"这个路径名确认不存在"。常见于查找未找到的文件——如果每次 open("/tmp/noexist") 都要重新遍历,会浪费大量 IO。
file — 打开的文件描述
;
系统调用路径
open(): 从路径名到 file
flowchart TD
SYSCALL["open() 系统调用"] --> GETNAME["getname()<br/>从用户空间复制路径名"]
GETNAME --> GETFD["get_unused_fd_flags()<br/>分配 fd number"]
GETFD --> DO_OPEN["do_filp_open()<br/>核心路径解析"]
DO_OPEN --> PATH["path_openat()"]
PATH --> WALK["link_path_walk()<br/>逐分量遍历路径"]
WALK --> LOOKUP{"lookup_fast()<br/>查 dcache (RCU walk)"}
LOOKUP -->|"命中"| DENTRY["返回 dentry<br/>无锁, 极快 ✅"]
LOOKUP -->|"未命中"| SLOW["lookup_slow()<br/>调用 i_op->lookup()<br/>从磁盘/inode 块中找<br/>分配新 dentry → 加入 dcache"]
DENTRY --> LAST{"最后分量?"}
SLOW --> LAST
LAST -->|"是"| DO_LAST["do_last()"]
LAST -->|"否, 继续下一分量"| LOOKUP
DO_LAST --> CASE{"文件状态?"}
CASE -->|"已存在"| VFS_OPEN["vfs_open()<br/>→ f_op->open()"]
CASE -->|"O_CREAT"| VFS_CREATE["vfs_create()<br/>→ i_op->create()"]
CASE -->|"O_TMPFILE"| TMPFILE["创建无名临时文件"]
VFS_OPEN --> INSTALL["fd_install()<br/>file 安装到 fdtable"]
VFS_CREATE --> INSTALL
TMPFILE --> INSTALL
INSTALL --> RET["返回 fd ✅"]
classDef sys call fill:#e3f2fd,stroke:#1565c0
classDef step fill:#f3e5f5,stroke:#7b1fa2
classDef decision fill:#fff3e0,stroke:#ef6c00
classDef done fill:#e8f5e9,stroke:#2e7d32
class SYSCALL syscall
class GETNAME,GETFD,DO_OPEN,PATH,WALK,DENTRY,SLOW,VFS_OPEN,VFS_CREATE,TMPFILE,INSTALL step
class LOOKUP,LAST,CASE decision
class RET done
RCU Path Walk — VFS 的性能秘密
传统路径解析 (2.6 之前):
每个分量都要拿锁 → 高竞争 → 多核性能差
RCU path walk (2.6.38+):
1. 在 RCU read lock 下遍历 dcache
2. 只需要读 dentry 和 inode (不修改)
3. 如果所有分量都在 dcache 中 → 全程无锁
4. 如果遇到不在 dcache 的分量 → 退化为 ref-walk (加锁)
性能: 多核系统上 open() 吞吐量提升数倍
实现: fs/namei.c 的 link_path_walk() 中两套逻辑 (RCU/REF)
read(): 从 page cache 读
// fs/read_write.c
→ →
├─
├─ 调用 f_op-> 或 f_op->
│ └─ 对于常规文件:
│ └─
│ ├─ 对每个 page-aligned 范围:
│ │ ├─ →
│ │ │ ├─ page cache 命中 →
│ │ │ └─ page cache miss →
│ │ │ └─ 从块层读取 → 页面插入 page cache
│ │ └─ →
│ └─ 更新 f_pos
└─ 返回已读字节数
write(): 写 page cache + 回写
└─ f_op-> →
└─
├─
│ ├─ 对每个 page-aligned 范围:
│ │ ├─ → 找到或创建 page cache 页
│ │ ├─ →
│ │ └─ 标记页面
│ └─ 更新 f_pos
│
└─ 写回的时机?
不是这里! generic_file_write_iter 只写到 page cache
writeback 由后台 flusher
file_operations
// include/linux/fs.h
;
文件系统可以只填充一部分——VFS 会补齐默认实现。例如没有 read_iter 但有 read,VFS 会自动包装。
Mount 机制
// fs/namespace.c
// mount 将一个文件系统附着到 VFS 树的一个点:
;
// mount /dev/sda1 /mnt:
// 1. 读 /dev/sda1 的 superblock → 创建 super_block
// 2. 创建 vfsmount → mnt_root = super_block->s_root
// 3. 挂到 /mnt 上 → /mnt 的 dentry 变为 mountpoint
// 4. 路径遍历遇到 mountpoint → 切换到 vfsmount->mnt_root 继续
// namespace:
// 每个进程属于一个 mount namespace
// CLONE_NEWNS → 新 mount namespace (容器的基础)
调试
# VFS 统计
# nr_dentry, nr_unused, age_limit
# 当前挂载
# 文件系统类型
# 追踪 open/read/write
参考与延伸
- 内核文档:
Documentation/filesystems/vfs.rst,Documentation/filesystems/path-lookup.rst - LWN:
- "Pathname lookup in Linux" (lwn.net/Articles/649115/)
- "RCU-walk: faster pathname lookup" (lwn.net/Articles/649294/)
- 源码文件:
fs/namei.c— path walk, open()fs/open.c— do_sys_open()fs/read_write.c— vfs_read/vfs_writeinclude/linux/fs.h— super_block, inode, dentry, file, file_operationsfs/namespace.c— mount 机制fs/dcache.c— dentry cache
关键词: VFS, super_block, inode, dentry, file, file_operations, RCU path walk, dcache, negative dentry, mount namespace