2. VFS 数据结构详解:超级块、inode、dentry 与 file 的关系与生命周期
VFS(Virtual File System)层通过四种核心数据结构抽象所有文件系统:超级块(super_block)、索引节点(inode)、目录项(dentry)和文件结构(file)。它们共同构成了 VFS 的对象模型,管理从存储设备到进程文件描述符的完整路径。理解这四者的关系与生命周期,是掌握 VFS 缓存策略和性能优化的基础。
2.1 超级块(super_block)—— 文件系统的元数据锚点
super_block 是 VFS 中最顶层的结构,代表一个已挂载的文件系统实例。它存储文件系统的全局信息,如块大小、最大文件大小、操作函数表等。每个挂载点对应一个超级块,所有 I/O 操作最终都通过超级块找到具体的文件系统实现。
| 字段 | 类型 | 说明 |
|---|---|---|
s_dev |
dev_t | 文件系统所在设备号 |
s_blocksize |
unsigned int | 文件系统块大小(通常 4KB) |
s_op |
struct super_operations * | 超级块操作函数表(alloc_inode, destroy_inode, write_inode 等) |
s_inodes |
struct list_head | 该文件系统所有 inode 的链表头 |
s_dirty |
struct list_head | 脏 inode 链表,用于回写 |
s_fs_info |
void * | 指向具体文件系统私有数据(如 ext4 的 ext4_sb_info) |
s_dirty 链表是回写线程(pdflush / writeback)的核心扫描对象。当脏 inode 数量过多时,回写压力增大,可能触发 I/O 瓶颈。调整 dirty_background_ratio 和 dirty_ratio 可以控制回写触发阈值。
2.2 inode 结构 —— 文件的元数据核心
inode(索引节点)是 VFS 中描述文件元数据的核心结构。每个文件(包括目录)在 VFS 中都有一个唯一的 inode。它不包含文件名,只包含权限、所有者、时间戳、数据块指针等信息。inode 的生命周期从文件被首次访问(如 open、stat)开始,到所有引用释放后结束。
// include/linux/fs.h (简化)
struct inode {
umode_t i_mode; // 文件类型与权限
uid_t i_uid; // 所有者 UID
gid_t i_gid; // 所属组 GID
loff_t i_size; // 文件大小(字节)
struct timespec64 i_atime; // 最后访问时间
struct timespec64 i_mtime; // 最后修改时间
struct timespec64 i_ctime; // 状态变更时间
unsigned long i_ino; // inode 编号
atomic_t i_count; // 引用计数
struct address_space *i_mapping; // 地址空间(页缓存核心)
struct inode_operations *i_op; // inode 操作函数表
struct super_block *i_sb; // 所属超级块
struct list_head i_dentry; // 指向此 inode 的 dentry 链表
union {
struct ext4_inode_info ext4_i; // ext4 私有数据
struct btrfs_inode btrfs_i; // btrfs 私有数据
// ... 其他文件系统
};
};
i_mapping 指向 address_space,这是页缓存(page cache)的入口。所有文件读写最终都通过 i_mapping 操作页缓存。inode 的 i_count 引用计数决定了它何时可以被回收,i_dentry 链表则关联了所有指向该 inode 的目录项。
2.3 dentry 结构 —— 路径名到 inode 的桥梁
dentry(目录项)是 VFS 路径解析的核心缓存结构。它将路径名(如 /home/user/file.txt)的每个分量映射到对应的 inode。dentry 不直接对应磁盘上的数据结构,而是 VFS 在内存中动态构建的缓存,用于加速路径查找。dcache(目录项缓存)是 VFS 最重要的缓存之一。
| 字段 | 类型 | 说明 |
|---|---|---|
d_name |
struct qstr | 目录项名称(如 "file.txt") |
d_parent |
struct dentry * | 父目录项指针 |
d_inode |
struct inode * | 指向对应的 inode(NULL 表示负缓存) |
d_op |
struct dentry_operations * | dentry 操作函数表(d_hash, d_compare, d_delete 等) |
d_flags |
unsigned int | 标志位(DCACHE_DISCONNECTED, DCACHE_NFSFS_RENAMED 等) |
d_lru |
struct list_head | LRU 链表,用于 dcache 回收 |
d_subdirs |
struct list_head | 子目录项链表(仅对目录有效) |
d_inode == NULL 的 dentry 并缓存起来。下次查找同一路径时,直接返回“不存在”,避免重复磁盘访问。负缓存可以显著提升文件不存在场景的性能,但也会占用内存,可通过 /proc/sys/fs/dentry-state 监控。
2.4 file 结构 —— 进程视角的打开文件
file 结构代表进程打开的一个文件描述符上下文。它与进程生命周期绑定,每次 open() 系统调用创建一个新的 file 实例。同一个文件可以被多个进程打开,每个进程拥有独立的 file 结构,但共享底层的 inode。file 结构记录了读写位置、打开模式、标志位等信息。
// include/linux/fs.h (简化)
struct file {
struct path f_path; // 包含 dentry 和 mount 信息
struct inode *f_inode; // 指向文件对应的 inode(快捷方式)
const struct file_operations *f_op; // 文件操作函数表
loff_t f_pos; // 当前读写位置(文件偏移量)
unsigned int f_flags; // 打开标志(O_RDONLY, O_SYNC 等)
fmode_t f_mode; // 打开模式(FMODE_READ, FMODE_WRITE)
atomic_long_t f_count; // 引用计数
void *private_data; // 文件系统私有数据
};
file 结构共享同一个 inode,但各自维护独立的 f_pos(读写位置)。多线程并发读写同一文件描述符时,f_pos 的竞争可能导致数据错乱,需要使用 pread()/pwrite() 或加锁保护。
2.5 四者关系与生命周期总结
四个结构的关系可以概括为:super_block 是根,inode 是体,dentry 是名,file 是门。它们通过指针相互关联,形成一个完整的访问链路。
2.6 缓存策略与性能影响
VFS 对这四个结构实施了不同的缓存策略,直接影响文件系统的读写性能:
| 缓存类型 | 管理对象 | 淘汰策略 | 性能影响 |
|---|---|---|---|
| dcache | dentry | LRU(最近最少使用) | 加速路径解析,避免重复磁盘目录项读取 |
| inode cache | inode | 引用计数 + LRU | 减少 inode 读取和释放的开销 |
| page cache | 文件数据页 | LRU + 脏页回写 | 读写性能的核心,减少磁盘 I/O 次数 |
| buffer cache | 块设备元数据 | LRU | 加速超级块、inode 表等元数据访问 |
vm.vfs_cache_pressure 可以加速 dentry 和 inode 缓存的回收,释放内存给 page cache;对于低频访问场景,减小该值可以保留更多缓存,提升响应速度。通过 /proc/sys/fs/file-nr 和 /proc/sys/fs/dentry-state 可以实时监控缓存使用情况。
2.7 实战:通过 /proc 观察 VFS 缓存状态
Linux 提供了多个 /proc 接口用于观察 VFS 缓存状态,以下命令可以帮助你实时了解系统缓存情况:
# 查看 dentry 和 inode 缓存状态
$ cat /proc/sys/fs/dentry-state
60345 52134 45 0 0 0
# 字段含义:总 dentry 数 / 未使用 dentry 数 / 最大 dentry 数 / ...
# 查看文件句柄使用情况
$ cat /proc/sys/fs/file-nr
1024 0 1048576
# 字段含义:已分配文件句柄 / 未使用文件句柄 / 最大文件句柄
# 查看超级块信息(需要 root)
$ cat /proc/fs/ext4/sda1/options
# 显示 ext4 文件系统的挂载参数和超级块状态
# 查看页缓存统计
$ cat /proc/meminfo | grep -E "^(Cached|Dirty|Writeback)"
Cached: 1024568 kB
Dirty: 1234 kB
Writeback: 0 kB
Dirty 值持续增长且 Writeback 不为 0 时,说明回写压力大,可能触发 I/O 瓶颈。此时应检查 dirty_background_ratio 和 dirty_ratio 的配置是否合理。当 dentry-state 中的未使用 dentry 数占比过高时,说明 dcache 占用内存过多,可适当调高 vfs_cache_pressure。