2. VFS核心数据结构概览:super_block、inode、dentry、file结构体的角色与关系

虚拟文件系统(VFS)是Linux内核中一个极其精巧的抽象层,它通过一组通用的数据结构,将不同文件系统(如ext4、XFS、Btrfs、NFS)的差异封装起来,为用户空间提供统一的系统调用接口(如open、read、write)。理解VFS的核心数据结构——super_block、inode、dentry和file——是掌握整个VFS层实现原理的基石。

这四种结构体分别从不同维度描述了一个文件系统及其中的文件对象:

  • super_block:代表一个已挂载的文件系统实例,包含全局元数据。
  • inode:代表一个文件或目录的物理元数据,是文件系统对象的“身份证”。
  • dentry:代表路径名中的一个分量,是目录项在内存中的缓存,负责路径解析。
  • file:代表一个打开的文件描述符,是进程与文件交互的会话句柄。
核心理解: 可以将这四个结构体类比为图书馆管理系统:super_block是图书馆的“总登记簿”,inode是每本书的“版权页”,dentry是“书架上的标签”,而file则是读者手中的“借阅卡”。

2.1 四者关系全景图

下面的SVG图展示了VFS四大核心数据结构之间的协作关系,以及它们如何共同支撑起一个文件操作流程。

VFS核心数据结构关系图 VFS 四大核心数据结构关系图 super_block 文件系统实例 inode 物理元数据 dentry 目录项缓存 file 打开文件句柄 s_inodes i_dentry d_inode f_path.dentry f_inode 说明: 1. super_block 通过 s_inodes 链表管理所有 inode。 2. dentry 通过 d_inode 指向对应的 inode;inode 通过 i_dentry 反向查找别名。 3. file 通过 f_path.dentry 找到 dentry,再通过 f_inode 直接访问 inode。

2.2 super_block:文件系统的“总登记簿”

super_block 结构体是VFS中最顶层的对象,它代表一个已挂载的文件系统实例。每当一个文件系统被挂载(如 mount /dev/sda1 /mnt),内核就会在内存中创建一个对应的 super_block 对象。它包含了文件系统的全局信息,例如块大小、最大文件大小、操作函数表等。

以下是 super_block 结构体的核心字段(简化版):

struct super_block {
    dev_t                   s_dev;          // 块设备标识符
    unsigned long           s_blocksize;    // 块大小(字节)
    unsigned char           s_blocksize_bits; // 块大小的位数
    loff_t                  s_maxbytes;     // 最大文件大小
    struct file_system_type *s_type;        // 文件系统类型(如ext4)
    struct super_operations *s_op;          // 超级块操作函数表
    struct dentry           *s_root;        // 根目录的dentry
    struct list_head        s_inodes;       // 所有inode的链表头
    struct list_head        s_dentry_lru;   // 未使用的dentry LRU链表
    // ... 其他字段
};
关键点: s_op 指向 struct super_operations,这是一个函数指针表,包含 alloc_inode、destroy_inode、write_super 等回调函数。不同的文件系统通过实现这些回调来定制自己的行为,这是VFS多态性的核心体现。

2.3 inode:文件的“身份证”

inode(索引节点)是VFS中最重要的数据结构之一,它存储了一个文件或目录的所有元数据(metadata),但不包含文件名。每个文件在磁盘上都有一个唯一的 inode 编号,VFS 在内存中维护一个 inode 缓存,以加速访问。

inode 包含的信息包括:文件类型(普通文件、目录、符号链接等)、权限、所有者、大小、时间戳、数据块指针等。

struct inode {
    umode_t                 i_mode;     // 文件类型和权限
    uid_t                   i_uid;      // 所有者用户ID
    gid_t                   i_gid;      // 所有者组ID
    loff_t                  i_size;     // 文件大小(字节)
    struct timespec64       i_atime;    // 最后访问时间
    struct timespec64       i_mtime;    // 最后修改时间
    struct timespec64       i_ctime;    // 最后状态改变时间
    unsigned long           i_ino;      // inode编号
    struct super_block      *i_sb;      // 所属超级块
    struct address_space    *i_mapping; // 地址空间映射
    struct inode_operations *i_op;      // inode操作函数表
    const struct file_operations *i_fop; // 默认文件操作函数表
    struct list_head        i_dentry;   // 指向此inode的dentry链表
    // ... 其他字段
};
注意: i_dentry 是一个链表头,用于链接所有指向该 inode 的 dentry 对象。一个文件可以有多个硬链接,每个硬链接对应一个 dentry,这些 dentry 都会链接到同一个 inode 的 i_dentry 链表上。

2.4 dentry:路径名的“缓存标签”

dentry(目录项)是VFS中用于路径名解析的核心数据结构。它不直接对应磁盘上的数据,而是内存中的缓存对象,用于将文件名(路径分量)与对应的 inode 关联起来。例如,路径 /home/user/file.txt 会被解析为四个 dentry:/、home、user、file.txt。

dentry 的主要作用是加速路径查找,避免每次访问都进行昂贵的磁盘操作。

struct dentry {
    struct qstr             d_name;         // 文件名(快速字符串)
    struct inode            *d_inode;       // 指向对应的inode(可为NULL)
    struct dentry           *d_parent;      // 父目录的dentry
    struct list_head        d_child;        // 父目录的子dentry链表
    struct list_head        d_subdirs;      // 子目录链表(仅对目录有效)
    struct dentry_operations *d_op;         // dentry操作函数表
    struct super_block      *d_sb;          // 所属超级块
    unsigned int            d_flags;        // 标志位
    // ... 其他字段
};
重要概念:负dentry(Negative Dentry)
当查找一个不存在的文件名时,VFS 会创建一个特殊的 dentry,其 d_inode 为 NULL。这种“负dentry”会被缓存起来,用于快速判断文件不存在,避免重复的磁盘查找。这是VFS性能优化的一个经典技巧。

2.5 file:进程的“打开文件会话”

file 结构体代表一个打开的文件描述符,它是进程与文件系统交互的桥梁。每次进程调用 open() 系统调用时,内核都会创建一个新的 file 对象。它记录了当前读写位置、访问模式、标志位等信息。

与 inode 不同,file 对象是进程相关的,多个进程可以同时打开同一个文件,每个进程拥有独立的 file 对象,但共享同一个 inode。

struct file {
    struct path             f_path;         // 包含dentry和挂载点
    struct inode            *f_inode;       // 指向文件的inode(快捷方式)
    const struct file_operations *f_op;     // 文件操作函数表
    loff_t                  f_pos;          // 当前读写偏移量
    unsigned int            f_flags;        // 打开标志(O_RDONLY等)
    fmode_t                 f_mode;         // 访问模式(读/写)
    struct address_space    *f_mapping;     // 地址空间映射
    // ... 其他字段
};
关键点: f_op 指向 struct file_operations,这是VFS多态性的另一个重要体现。它包含了 read、write、mmap、ioctl 等函数指针。不同的文件系统或设备驱动通过实现这些函数来提供不同的读写行为。

2.6 四者关系总结表

下表从多个维度对比了这四种核心数据结构,帮助理解它们各自的职责和相互关系。

结构体 生命周期 核心职责 关键关联 是否持久化
super_block 挂载时创建,卸载时销毁 管理文件系统全局元数据 通过 s_inodes 管理所有 inode 部分字段对应磁盘超级块
inode 文件被访问时创建,缓存回收时销毁 存储文件元数据(权限、大小等) 通过 i_dentry 关联所有硬链接 对应磁盘 inode
dentry 路径解析时创建,LRU回收时销毁 缓存路径名到 inode 的映射 通过 d_inode 指向 inode 仅内存存在
file open() 时创建,close() 时销毁 记录进程与文件的交互状态 通过 f_path.dentry 和 f_inode 关联 仅内存存在

2.7 典型操作流程:打开文件

为了更直观地理解这四个结构体如何协同工作,我们以一次 open("/home/user/file.txt", O_RDONLY) 系统调用为例,梳理其核心流程:

  1. 路径解析开始: 内核从根 dentry(current->fs->root)开始,逐层查找路径分量。
  2. 查找 dentry: 在 dentry 缓存(dcache)中查找 home、user、file.txt 对应的 dentry。如果缓存命中,直接获取 d_inode;如果未命中,则触发磁盘读取,创建新的 dentry 并关联 inode。
  3. 获取 inode: 通过 dentry 的 d_inode 指针获取文件的 inode,并检查权限。
  4. 创建 file 对象: 分配一个新的 struct file,设置 f_path.dentry 指向找到的 dentry,f_inode 指向 inode,f_op 从 inode 的 i_fop 继承。
  5. 初始化: 设置 f_pos 为 0,f_flags 为 O_RDONLY,并将 file 对象添加到进程的文件描述符表中。
  6. 返回文件描述符: 系统调用返回一个整数 fd,用户程序后续通过 fd 操作 file 对象。
性能优化点: dentry 缓存(dcache)和 inode 缓存是VFS性能的关键。频繁访问的文件和目录会一直留在缓存中,避免重复的磁盘 I/O。内核通过 LRU(最近最少使用)算法管理这些缓存,在内存压力大时回收不常用的对象。

2.8 小结

本章详细介绍了VFS层的四大核心数据结构:

  • super_block 是文件系统的“总登记簿”,管理全局信息。
  • inode 是文件的“身份证”,存储所有元数据。
  • dentry 是路径名的“缓存标签”,加速路径解析。
  • file 是进程的“打开文件会话”,记录交互状态。