如果你在 Linux 下运行任何应用,你一定在使用文件系统。
文件系统的核心能力并不只是“存文件”,更重要的是它为应用提供了统一的存储接口:
读写文件
创建/删除/重命名
目录遍历
权限控制
软硬链接
内存映射
设备访问
网络文件访问
这些看似简单的操作,背后是复杂的内核体系。
VFS 层结构示意
┌────────────────────────────────────┐│ VFS 层 ││ ││ sys_open/read/write/... ││ │ ││ ▼ ││ path_lookup / generic_file_* ││ │ ││ ▼ ││ dentry/inode/page cache ││ │ ││ ▼ ││ filesystem specific ops (ext4) │└────────────────────────────────────┘
Linux 的文件系统并不是单一的,而是多种多样的:ext4、xfs、btrfs、nfs、cifs、tmpfs、procfs、sysfs、overlayfs 等。
如果没有一层统一抽象,内核将变成“各种文件系统各自实现一套系统调用”的混乱系统。
因此,Linux 引入了 VFS(Virtual File System):
它不是一个真正的文件系统,而是一层 统一的抽象层,用于连接系统调用与具体文件系统。
1. 为什么要有 VFS?
1.1 多文件系统并存的现实需求
Linux 需要支持:
本地磁盘文件系统(ext4、xfs、btrfs)
网络文件系统(NFS、CIFS)
内存文件系统(tmpfs)
特殊虚拟文件系统(procfs、sysfs)
可插拔的文件系统(FUSE)
如果没有统一抽象,用户态程序需要针对不同文件系统编写不同代码,这显然不可行。
1.2 系统调用与文件系统实现解耦
系统调用层(如 open、read、write)不应该关心底层存储细节。
VFS 的作用是:
将系统调用统一映射到 VFS 操作
VFS 再通过函数指针调用具体文件系统实现
用户态程序无需关心底层文件系统类型
2. VFS 的核心目标与设计原则
2.1 统一接口
VFS 为每个文件系统定义了一套统一的操作集:
super_operationsinode_operationsfile_operationsdentry_operations
每个文件系统只要实现这些操作,就能被 VFS 识别与调用。
2.2 统一命名空间(Namespace)
Linux 的文件系统结构是一棵树:
/|-- bin|-- etc|-- home|-- var|-- mnt
每个挂载点(mount)都属于同一棵树。
VFS 的使命是维护这棵树的统一性。
2.3 缓存与性能优化
VFS 提供统一的缓存体系,主要包括:
dentry cache:路径解析缓存
inode cache:元数据缓存
page cache:数据缓存
这些缓存是 Linux 文件系统性能的核心。
2.4 可扩展性与可插拔性
Linux 的文件系统可以动态加载、卸载,支持:
直接内核模块(如 ext4、xfs)
FUSE(用户态文件系统)
网络文件系统(NFS、CIFS)
VFS 的抽象设计保证了系统的可扩展性。
3. VFS 的关键概念与术语
下面是 VFS 的核心概念,必须理解清楚才能读懂后续内容。
3.1 inode(索引节点)
inode 代表一个文件(或目录、设备文件等)的元数据,包括:
文件类型(普通文件、目录、字符设备、块设备等)
权限(rwx)
所属用户/组
文件大小
时间戳(atime、mtime、ctime)
数据块指针(指向磁盘块)
链接计数(硬链接数)
其他属性(ACL、SELinux 等)
注意:
inode 是文件系统级别的标识
不同文件系统的 inode 号可能重复
inode 不包含文件名(文件名存在目录项中)
3.2 dentry(目录项)
dentry 是目录项,用于实现:
一个 dentry 可能指向一个 inode,也可能是一个“负 dentry”(表示路径不存在)。
3.3 super_block(超级块)
super_block 代表一个挂载的文件系统整体信息:
文件系统类型
block size
最大文件数
根目录 dentry
相关操作集合(super_operations)
3.4 file(文件对象)
file 代表一次打开操作的结果:
由 open 创建
由 close 销毁
保存读写偏移量 f_pos
绑定 file_operations(f_op)
一个 inode 可以被多个 file 对象引用(即多个进程打开同一文件)。
3.5 vfsmount(挂载信息)
vfsmount 代表一个挂载点:
记录挂载点目录(mnt_root)
记录 super_block
形成挂载树(mount tree)
4. VFS 的核心数据结构
4.1 主要结构关系图
process | +-- fd table | +-- file (open instance) | +-- inode (metadata) | | | +-- super_block (filesystem) | +-- dentry (path cache) | +-- vfsmount (mount point)
4.2 数据结构表(关键字段与作用)
| | | |
|---|
| | | |
| | i_ino, i_mode, i_size, i_nlink | |
| | d_name, d_inode, d_parent | |
| | | |
| | | |
5. VFS 的核心调用流程
5.1 open() 的流程(核心函数与机制)
用户态:
int fd = open("/home/user/a.txt", O_RDONLY);
open() 流程图
open() |sys_open() |path_lookup() |dentry cache? | \ yes no | | v vinode fs_lookup() |vfs_open() |file struct |return fd
内核路径:
sys_open / do_sys_open
path_lookupat(路径解析)
nameidata(path 解析上下文)
dentry(路径缓存)
vfs_open(VFS open)
inode
file(创建 file struct)
file->f_op->open(调用文件系统 open)
5.1.1 路径解析的核心:path_lookup
path_lookup 是路径解析的核心函数,它会:
逐级解析路径组件
利用 dentry cache 加速
处理符号链接、挂载点、..、. 等
路径解析是 VFS 性能的重要瓶颈。
5.2 read() 的流程(含 page cache)
用户态:
ssize_t n = read(fd, buf, len);
内核路径:
sys_read
vfs_read
file->f_op->read_iter(generic_file_read_iter)
page cache 查找
缓存 miss -> filemap_read -> readpage
数据拷贝到用户空间
5.2.1 page cache 的关键:generic_file_read_iter
generic_file_read_iter 负责:
page cache 查找
page lock
处理文件末尾
调用底层 readpage 填充 page
5.3 write() 的流程(含 writeback)
用户态:
ssize_t n = write(fd, buf, len);
内核路径:
sys_write
vfs_write
file->f_op->write_iter(generic_file_write_iter)
写入 page cache(dirty page)
后台 writeback 或 sync 写盘
5.3.1 writeback:什么时候写盘?
写盘时机取决于:
6. VFS 与具体文件系统的协作
6.1 VFS 通过函数指针实现“接口调用”,熟悉内核的朋友应该都知道的
VFS 的调用链:
vfs_read -> generic_file_read_iter -> ext4_readpage
7. VFS 缓存体系:深入剖析
VFS 缓存体系是 Linux 文件系统性能的核心。下面逐个讲清楚:
7.1 dentry cache(路径解析缓存)
dentry cache 的作用是:
缓存目录项(文件名 -> inode 的映射)
加速路径解析
减少目录查找 I/O
7.1.1 dentry 的引用计数
每个 dentry 有一个引用计数 d_count。当引用计数为 0 时,dentry 可能被回收。
7.1.2 负 dentry(negative dentry)
当路径不存在时,VFS 会创建一个“负 dentry”,用于缓存“文件不存在”的结果,避免重复访问磁盘。
7.2 inode cache(元数据缓存)
inode cache 缓存 inode 的元数据,减少读取 inode 的磁盘 I/O。
inode 的生命周期:
7.3 page cache(数据缓存)
page cache 是最重要的缓存,它缓存文件内容。几乎所有的文件读写都经过 page cache。
7.3.1 page cache 的命中逻辑
读:先查 page cache,命中则直接返回
写:写入 page cache,标记 dirty
后台 flush:dirty page 写盘
7.3.2 readahead(预读)
当文件被顺序读取时,Linux 会启用 readahead 机制:
预先读取下一段数据到 page cache
提升顺序读性能
7.3.3 writeback(写回)
写回策略:
延迟写:提高写性能
但可能造成数据丢失风险(断电/崩溃)
可通过 fsync 强制写回
8. VFS 的锁与并发控制
VFS 的并发控制非常复杂,主要依靠以下锁:
8.1 inode lock(i_mutex / i_rwsem)
用于保护 inode 元数据和结构一致性。
典型使用场景:
rename
truncate
unlink
setattr
8.2 dentry lock(d_lock)
用于保护 dentry 状态,避免路径解析冲突。
8.3 page lock(page lock)
保护 page cache 的内容一致性,避免多个线程同时修改同一 page。
9. 通过典型场景,解释一下 VFS 的行为与性能影响。
9.1 文件拷贝(cp)的真实性能瓶颈
cp 的过程:
open(src)open(dst)read(src)write(dst)
其中性能瓶颈可能来自:
page cache miss(读盘)
写回压力(写盘)
目录缓存 miss(open 很慢)
文件系统碎片
设备 I/O 限制
9.1.1 性能优化建议
使用 cp --reflink(支持的文件系统,如 btrfs、xfs)
使用 dd if=... of=... bs=... 调整块大小
对大文件使用 O_DIRECT(绕过 page cache,但需要对齐)
使用 rsync --inplace(减少临时文件)
9.2 rm 删除文件的真实逻辑(为什么 rm 后仍占空间)
当执行:
VFS 做了什么?
path_lookup 找到 dentry
vfs_unlink 删除目录项
inode->i_nlink--
若 i_nlink==0 且 file 引用计数==0
释放 inode 和数据块
但如果文件仍被进程打开(file 仍存在),则:
i_nlink 变为 0
但 i_count > 0
文件仍占用磁盘空间
直到最后一个 file 关闭后才释放
9.3 硬链接与软链接(底层差异)
9.3.1 硬链接(link)
硬链接的实现:
新建一个 dentry 指向同一 inode
inode->i_nlink++
硬链接特点:
共享 inode
删除某个链接不影响其他链接
不跨文件系统
9.3.2 软链接(symlink)
软链接:
dentry 指向一个路径字符串
访问时需要再次解析路径
软链接特点:
9.4 mmap(内存映射)的真实机制
mmap 的作用:
mmap 的优势:
mmap 的风险:
需要注意同步(msync、munmap)
可能导致写回压力集中
10. VFS 调试与性能优化实践
缓存体系关系
[应用程序] |[系统调用] |[ VFS ] | \ | \[dentry cache] [inode cache] \ / \ / \ / [page cache] | [磁盘 I/O]
10.1 常用工具与命令
10.2 典型性能问题定位
10.2.1 读性能差(高 I/O 低 CPU)
原因:
page cache miss
磁盘随机读
文件系统碎片
网络文件系统延迟
解决:
增加内存(提升 page cache)
调整 readahead
使用更适合的文件系统(xfs、btrfs)
对网络文件系统优化网络参数
10.2.2 写性能差(大量 dirty page)
原因:
解决:
11. 典型源码解析(硬核部分)
下面给出一些 VFS 关键函数的逻辑解析(简化版,便于理解)。
11.1 path_lookup(路径解析)
核心逻辑:
分解路径组件
逐级查找 dentry
若 dentry cache miss,则调用文件系统 lookup
处理符号链接、挂载点、权限检查
路径解析是 VFS 的核心性能瓶颈之一。
11.2 vfs_read(读取)
vfs_read 的核心作用:
选择正确的 file_operations
处理不同类型文件(普通文件、管道、字符设备)
走 page cache 或直接调用设备读
11.3 generic_file_read_iter(通用读取)
它实现了:
page cache 查找
处理 page lock
调用底层 readpage 填充数据
处理 EOF
这是文件系统读取的通用路径。
11.4 generic_file_write_iter(通用写入)
它实现了:
写入 page cache
标记 dirty page
处理 writeback
处理 O_DIRECT 或 mmap 的写入
12. VFS 的“黑科技”与进阶特性
12.1 overlayfs(联合挂载)
overlayfs 允许将多个目录“叠加”在一起:
upperdir -> 可写层lowerdir -> 只读层merged -> 统一视图
它的核心价值:(这里在嵌入式系统中常用)
容器镜像写时复制(copy-on-write)
快速构建轻量容器文件系统
12.2 namespaces(命名空间)
Linux 支持 mount namespace:
不同进程组可以看到不同的文件系统树。
例如容器中:
看到的 / 与宿主机不同
mount 操作不会影响宿主机
13. 常见误区与工程建议
13.1 “文件系统性能只与磁盘有关”
错误。性能还与:
page cache
dentry/inode 缓存
写回策略
文件系统结构
CPU 与内存
密切相关。
13.2 “关闭缓存就能真实测试”
错误。关闭缓存会让性能指标失真,因为真实场景下缓存必然存在。正确做法是:
13.3 “fsync 一定要频繁调用”吗?
答案是不一定啊。因为频繁 fsync 会导致性能下降,但对于数据库等强一致性场景必须,工程上应权衡一致性与性能。
14. 最后我们总结一下
VFS 是统一抽象层:系统调用 -> VFS -> 具体文件系统
核心数据结构:super_block、inode、dentry、file、vfsmount
缓存体系是性能关键:dentry cache、inode cache、page cache
路径解析是性能瓶颈:dentry cache 命中率决定 open 速度
page cache 是 I/O 性能核心:读写大多数通过 page cache
锁机制是并发核心:inode lock、dentry lock、page lock
不同文件系统实现不同:ext4、xfs、nfs 等通过函数指针实现接口
工程优化要从缓存、I/O、写回策略入手
如果你觉得本文对你理解 VFS 有帮助,欢迎关注“Linux 炉边会”,我会持续输出:
内核架构解读
性能调优实战
系统工程落地
关键命令与源码分析
留言告诉我你最想看的主题:
VFS 源码逐行拆解?
page cache 与 swap 的深度关联?
文件系统性能调优实战?
NFS/CIFS 的 VFS 适配机制?
我会在后面文章中优先输出。