1. 文件系统基础:VFS(虚拟文件系统)架构、超级块、inode、dentry、文件对象的关系与生命周期
1.1 VFS 架构概述
虚拟文件系统(VFS)是 Linux 内核中一个关键的抽象层,它位于用户空间与具体文件系统实现之间。VFS 的核心价值在于:为用户空间提供统一的系统调用接口(如 open、read、write、close),同时允许内核支持多种不同的底层文件系统(如 ext4、XFS、Btrfs、NFS、tmpfs)。这种设计使得用户程序无需关心底层存储介质或文件系统格式的差异。
VFS 架构基于四个核心对象模型,它们共同构成了文件系统的“元数据骨架”:
| 核心对象 | 数据结构 | 主要职责 | 生命周期 |
|---|---|---|---|
| 超级块 (Super Block) | struct super_block |
描述已挂载文件系统的全局信息(块大小、操作函数表、根 inode 等) | 从挂载到卸载 |
| 索引节点 (inode) | struct inode |
存储文件或目录的元数据(权限、大小、时间戳、数据块指针) | 从创建到删除,或缓存回收 |
| 目录项 (dentry) | struct dentry |
维护路径名与 inode 的映射关系,实现路径查找加速 | 从路径解析到 dentry 缓存回收 |
| 文件对象 (file) | struct file |
表示一个打开的文件实例,记录读写位置、访问模式、引用计数 | 从 open() 到 close() |
ftrace 或 trace-cmd 跟踪 vfs_open、vfs_read、vfs_write 等函数,可以快速定位问题发生在 VFS 层还是底层文件系统。
1.2 VFS 架构分层与数据流
下图展示了 VFS 在 Linux I/O 栈中的位置,以及四个核心对象之间的协作关系:
1.3 超级块 (Super Block) 深度解析
超级块是文件系统实例的“身份证”,每个已挂载的文件系统在内存中对应一个 struct super_block 实例。它包含了文件系统的全局元数据和操作函数指针。
关键字段:
s_dev:文件系统所在块设备的设备号。s_blocksize:文件系统的块大小(通常为 4096 字节)。s_root:指向根目录 dentry 的指针,是路径查找的起点。s_op:超级块操作函数表(struct super_operations),包含alloc_inode、destroy_inode、write_super等回调。s_fs_info:指向具体文件系统私有数据的指针(如 ext4 的ext4_sb_info)。
super 命令可以列出所有已挂载文件系统的超级块。例如:crash> super ffff888123456789 查看指定超级块的详细信息。通过检查 s_flags 字段可以判断文件系统是否以只读方式挂载。
1.4 索引节点 (inode) 深度解析
inode 是文件系统中最核心的元数据结构,它唯一标识一个文件或目录,并存储了除文件名之外的所有元数据。每个 inode 都有一个全局唯一的 i_ino(inode 号)。
关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
i_mode |
umode_t |
文件类型(普通文件/目录/符号链接)和权限位 |
i_nlink |
nlink_t |
硬链接计数,当减为 0 时 inode 可被回收 |
i_uid / i_gid |
kuid_t / kgid_t |
文件所有者和所属组 |
i_size |
loff_t |
文件大小(字节) |
i_atime / i_mtime / i_ctime |
struct timespec64 |
访问时间、修改时间、状态变更时间 |
i_blocks |
blkcnt_t |
文件占用的磁盘块数(以 512 字节为单位) |
i_data |
struct address_space |
页缓存地址空间,管理文件数据页 |
i_op |
struct inode_operations * |
inode 操作函数表(create、lookup、link、unlink 等) |
i_nlink 为 0 但仍有进程持有文件描述符时,文件不会立即从磁盘删除,但 inode 会进入“孤儿”状态。此时通过 lsof 可以找到这些已删除但仍在使用的文件。在调试文件系统泄漏时,检查 i_nlink 和 i_count(引用计数)是关键步骤。
1.5 目录项 (dentry) 深度解析
dentry 是 VFS 中路径名解析的核心缓存结构。它将路径名中的每个分量(如 /home/user/file.txt 中的 home、user、file.txt)映射到对应的 inode。dentry 不直接对应磁盘上的数据结构,而是内存中的缓存加速层。
关键字段:
d_name:目录项的名称(struct qstr,包含名字字符串和哈希值)。d_parent:指向父目录 dentry 的指针。d_inode:指向该目录项对应的 inode(如果为 NULL,表示该 dentry 是“负”的,即路径名不存在)。d_op:dentry 操作函数表(struct dentry_operations),包含d_hash、d_compare、d_delete等回调。d_flags:标志位,如DCACHE_DISCONNECTED表示 dentry 未连接到全局树。
dentry 状态与生命周期:
| 状态 | 说明 | 典型场景 |
|---|---|---|
| 空闲 (Free) | dentry 未被使用,在 slab 缓存中 | 系统启动初期 |
| 正 (Positive) | d_inode 非 NULL,路径名有效 |
正常文件访问 |
| 负 (Negative) | d_inode 为 NULL,路径名不存在 |
查找不存在的文件时缓存“不存在”结果 |
| 未连接 (Disconnected) | dentry 不在全局 dentry 树中 | NFS 路径穿越、bind mount 场景 |
1.6 文件对象 (file) 深度解析
文件对象是进程与打开文件之间的桥梁。每次调用 open() 系统调用时,内核都会创建一个新的 struct file 实例。同一个文件可以被多个进程打开,每个进程拥有独立的文件对象,但它们共享底层的 inode。
关键字段:
f_path:文件路径(包含 dentry 和 mount 信息)。f_pos:当前读写位置(loff_t)。f_mode:打开模式(读/写/追加等)。f_flags:文件状态标志(O_NONBLOCK、O_SYNC 等)。f_count:引用计数,当减为 0 时文件对象被释放。f_op:文件操作函数表(struct file_operations),包含read、write、llseek、mmap等回调。private_data:具体文件系统或设备驱动使用的私有数据指针。
lsof -p <pid> 可以查看进程打开的所有文件对象。在 crash 工具中,files <pid> 命令可以列出进程的文件描述符表,并查看每个文件对象的 f_pos、f_mode 和 f_op。当怀疑文件描述符泄漏时,检查 f_count 是否异常偏高。
1.7 四个核心对象的生命周期与协作
理解四个对象的生命周期和协作关系,是调试文件系统问题的前提。下图展示了从 open() 系统调用到文件关闭的完整流程:
1.8 核心对象关系总结
下表总结了四个核心对象之间的主要关联关系,这是理解 VFS 数据流的基础:
| 关系 | 数据结构关联 | 说明 |
|---|---|---|
| 超级块 → inode | sb→s_inodes 链表 |
超级块管理所有属于该文件系统的 inode |
| 超级块 → dentry | sb→s_root |
超级块持有根 dentry 的指针 |
| dentry → inode | dentry→d_inode |
目录项指向对应的 inode(正 dentry) |
| dentry → 父 dentry | dentry→d_parent |
形成目录树结构 |
| file → dentry | file→f_path.dentry |
文件对象通过路径结构关联到 dentry |
| file → inode | file→f_inode |
文件对象直接持有 inode 引用(快捷方式) |
| inode → address_space | inode→i_data |
inode 管理页缓存,用于文件数据读写 |
1. 使用
lsof | grep '(deleted)' 找到已删除但仍在使用的文件。2. 使用 crash 工具检查对应 inode 的
i_nlink(应为 0)和 i_count(应大于 0)。3. 检查持有该文件描述符的进程,确认是否正常关闭文件。
4. 如果进程异常退出但文件对象未释放,检查
file→f_count 是否异常。
1.9 常见调试命令速查
| 调试目标 | 命令/工具 | 关键输出字段 |
|---|---|---|
| 查看超级块信息 | crash> super <sb_addr> |
s_dev, s_blocksize, s_root, s_op |
| 查看 inode 信息 | crash> inode <inode_addr> |
i_ino, i_mode, i_nlink, i_size, i_count |
| 查看 dentry 信息 | crash> dentry <dentry_addr> |
d_name, d_parent, d_inode, d_count |
| 查看文件对象 | crash> file <file_addr> |
f_pos, f_mode, f_count, f_op |
| 列出进程打开的文件 | crash> files <pid> |
文件描述符号、file 对象地址、路径 |
| 跟踪 VFS 函数调用 | trace-cmd record -p function -l 'vfs_*' |
函数调用序列、延迟分布 |