VFS (虚拟文件系统) 核心架构
前言
"一切皆文件"是 Linux 哲学中最著名的格言之一。无论是普通的文本文件、目录,还是设备、套接字、管道,甚至是内核的数据结构(如 /proc 和 /sys),在用户空间看来,它们都是文件。
这种统一的视角背后,最大的功臣就是 VFS (Virtual File System,虚拟文件系统) 。它作为一层抽象层,向上为用户空间提供统一的系统调用接口(如 open, read, write, mount),向下兼容几十种不同的文件系统(如 ext4, xfs, nfs, procfs)。
本文将带你深入 Linux 内核源码,抽丝剥茧,解析 VFS 的核心架构、四大核心对象以及它们之间错综复杂的爱恨纠葛。
1. VFS 整体架构全景图
VFS 位于用户空间和具体文件系统之间,起到承上启下的作用。
内核空间 (Kernel Space)
支持的文件系统类型
open/read/write
调用具体实现
VFS 核心组件
dentry cache
目录项缓存
inode cache
索引节点缓存
Mount Process
挂载管理
用户空间应用程序
系统调用接口
VFS 层 (Virtual File System)
具体文件系统 (Specific FS)
ext4
xfs
nfs
proc
tmpfs
通用块设备层
设备驱动
磁盘硬件
网络协议栈
2. 四大核心对象 (The Big Four)
Linux VFS 将文件系统的操作抽象为四个主要对象,它们是理解 VFS 的基石。
| 对象 | 英文名 | 结构体 | 描述 | 生命周期 |
|---|---|---|---|---|
| 超级块 | Super Block | struct super_block |
代表一个已挂载的文件系统实例,存储该文件系统的元数据。 | 挂载时创建,卸载时销毁 |
| 索引节点 | Inode | struct inode |
代表文件系统中一个特定的文件(数据),包含文件的元信息(权限、大小、时间等),但不包含文件名。 | 只要文件被访问就存在内存中,可被缓存 |
| 目录项 | Dentry | struct dentry |
代表路径中的一个组成部分(目录或文件名),用于将文件名解析为 inode。 | 缓存在内存中 (dcache),LRU 淘汰 |
| 文件对象 | File | struct file |
代表进程打开的一个文件上下文,包含文件偏移量、打开模式等。 | open() 创建,close() 销毁 |
2.1 核心对象关系图
这四个对象之间环环相扣:
生产
管理
根节点
映射 (硬链接)
父子关系
打开
关联
1 1 1 * * * * * * * * 1 1 1 1 1 1 1 file_system_type
+name: char
+mount()
+kill_sb()
super_block
+s_type: file_system_type
+s_op: super_operations
+s_root: dentry
+s_inodes: list_head
inode
+i_sb: super_block
+i_op: inode_operations
+i_fop: file_operations
+i_ino: unsigned long
+i_mapping: address_space
dentry
+d_inode: inode
+d_parent: dentry
+d_name: qstr
+d_op: dentry_operations
+d_sb: super_block
file
+f_path: path
+f_inode: inode
+f_op: file_operations
+f_pos: loff_t
3. 核心数据结构源码深度解析
让我们深入内核代码(基于较新版本内核),看看这些结构体长什么样。
3.1 super_block (文件系统的控制中心)
存储文件系统的元数据,比如块大小、魔数、根目录等。
c
struct super_block {
struct list_head s_list; // 全局超级块链表
dev_t s_dev; // 设备标识符
unsigned long s_blocksize; // 块大小
struct file_system_type *s_type; // 所属文件系统类型
const struct super_operations *s_op; // 超级块操作函数集合
struct dentry *s_root; // 文件系统根目录的 dentry
struct list_head s_mounts; // 挂载点链表
struct list_head s_inodes; // 该文件系统所有 inode 的链表
// ... (省略其他字段)
};
3.2 inode (文件的元神)
注意:Linux 中文件名和文件数据是分离的。inode 存储的是"文件数据在哪里"以及"文件是谁的",唯独不包含"文件叫什么"。
c
struct inode {
umode_t i_mode; // 权限和类型 (如 rwxr-x---)
kuid_t i_uid; // 用户ID
kgid_t i_gid; // 组ID
unsigned long i_ino; // inode 号 (唯一标识)
const struct inode_operations *i_op; // inode 操作 (如 mkdir, unlink)
const struct file_operations *i_fop; // 默认的文件操作 (open 之后赋值给 file->f_op)
struct super_block *i_sb; // 所属超级块
struct address_space *i_mapping; // 【重要】指向 address_space,用于页缓存
loff_t i_size; // 文件大小
// ...
};
3.3 dentry (路径的导航员)
VFS 实际上并不直接处理字符串路径。为了提高查找性能,VFS 使用 dentry 将字符串路径(如 /home/user)转换为 inode。dentry 形成了我们看到的目录树结构。
c
struct dentry {
struct dentry *d_parent; // 指向父目录的 dentry
struct qstr d_name; // 文件名
struct inode *d_inode; // 关联的 inode (如果是 NULL 则为负向 dentry)
const struct dentry_operations *d_op;// dentry 操作
struct super_block *d_sb; // 所属超级块
struct list_head d_subdirs; // 子目录项链表
// ...
};
性能关键 :访问文件时,内核不需要每次都从磁盘读取目录结构,而是首先在 dcache (Dentry Cache) 中查找。这是 Linux 文件系统高性能的关键之一。
3.4 file (进程视角的文件)
这是我们在应用程序中 open() 得到句柄对应的内核对象。它是进程级别的,不同进程打开同一个文件会有不同的 struct file,但它们指向同一个 inode。
c
struct file {
struct path f_path; // 包含 {mnt, dentry}
struct inode *f_inode; // 指向 inode (缓存,方便访问)
const struct file_operations *f_op; // 文件操作函数表 (read, write...)
loff_t f_pos; // 当前读写偏移量 (Cursor)
unsigned int f_flags; // 打开标志 (O_RDONLY, O_NONBLOCK...)
fmode_t f_mode; // 读写模式
struct address_space *f_mapping; // 再次指向页缓存
// ...
};
4. 关键操作流程图解
4.1 open() 系统调用流程
当你调用 fd = open("/home/test.txt", O_RDONLY) 时,内核发生了什么?
具体FS (ext4) 路径查找(path_lookup) VFS核心 用户进程 具体FS (ext4) 路径查找(path_lookup) VFS核心 用户进程 opt [Cache Miss] loop [每一级目录] sys_open("/home/test.txt") path_openat() 解析路径 查找 dcache inode_ops->>lookup() 从磁盘读 inode 创建 dentry 返回目标 dentry 和 inode alloc_file() 创建 struct file 关联 file->>f_op = inode->>i_fop file_ops->>open() (可选) 返回 fd (索引指向 file)
4.2 read() 系统调用流程
拿到 fd 后,调用 read(fd, buf, len)。
磁盘驱动 PageCache/MM file_operations sys_read 用户进程 磁盘驱动 PageCache/MM file_operations sys_read 用户进程 alt [Page Cache Hit] [Page Cache Miss] read(fd) fd 获取 struct file file->>f_op->>read_iter() generic_file_read_iter() 数据 copy_to_user a_ops->>readpage() 提交 BIO 请求 DMA 数据传输完成 数据 copy_to_user
5. 内存中的对象关系实例 (Instance View)
假设进程 A 打开了 /mnt/data/file.txt。内存中的对象关系如下:
text
进程 A Task Struct
│
▼
文件描述符表 (fd array)
┌───┬───┬───┐
│ 0 │ 1 │ 3 │ (fd=3)
└───┴───┴─┬─┘
│
▼
struct file (存活于 RAM)
┌────────────────────────┐
│ f_pos: 0 │ <--- 每个进程独立
│ f_mode: O_RDONLY │
│ f_path.dentry: ptr ────┼──┐
│ f_path.mnt: ptr ───────┼┐ │
│ f_op: ext4_file_ops ││ │
└────────────────────────┘│ │
│ │
┌────────────────────┘ │
│ │
▼ ▼
struct vfsmount struct dentry (缓存于 dcache)
┌─────────────┐ ┌───────────────────────────┐
│ mnt_root │ │ d_name: "file.txt" │
│ mnt_sb │ │ d_parent: ptr to "data" │
└─────────────┘ │ d_inode: ptr ─────────────┼──┐
└───────────────────────────┘ │
│
▼
struct inode (缓存于 RAM)
┌──────────────────────────┐
│ i_ino: 1024 │
│ i_size: 4096 │
│ i_bytes: 4096 │
│ i_sb: ptr to super_block │
│ i_ops: ext4_inode_ops │
│ f_ops: ext4_file_ops │
│ i_mapping: address_space │
└──────────────────────────┘
6. 总结
Linux VFS 的精妙之处在于它成功地将"接口"与"实现"分离。
- 对于开发者:只需要掌握一套标准的文件 I/O API。
- 对于内核 :通过
super_block,inode,dentry,file四大对象构建了统一的模型。 - 对于性能 :通过
dcache和inode cache极大地掩盖了底层磁盘慢速的特性。
理解 VFS 不仅有助于理解 Linux 的文件系统,更是理解 Linux 核心设计思想(如面向对象风格的 C 语言编程)的绝佳案例。