3. VFS层核心数据结构

虚拟文件系统(VFS)是Linux内核中一个极其精巧的抽象层,它通过一组通用的数据结构,将不同文件系统(如ext4、XFS、FAT32、NFS)的具体实现细节隐藏起来。对于驱动开发者而言,理解这四大核心数据结构——struct file、struct inode、struct dentry、struct super_block——是掌握驱动与VFS交互的关键。

这四种结构体共同构成了VFS的“四驾马车”,它们各自承担不同的职责,又通过指针紧密关联,形成一个完整的文件系统视图。

3.1 四大核心数据结构概览

下表从核心职责、生命周期、所属层级和关键字段四个维度,对这四种结构体进行对比:

数据结构 核心职责 生命周期 所属层级 关键字段(驱动关注)
struct file 代表一个打开的文件实例,是进程与文件交互的句柄 从 open() 到 close() 进程级 / 文件描述符层 f_op、private_data、f_pos
struct inode 代表文件系统中的一个文件或目录的元数据(物理属性) 从文件创建到被删除,常驻内存 文件系统元数据层 i_rdev、i_cdev、i_fop
struct dentry 代表路径名中的一个分量,是目录项缓存(dcache)的单元 路径解析时创建,可被缓存 目录项缓存层 d_inode、d_parent、d_name
struct super_block 代表一个已挂载的文件系统实例(超级块) 从挂载到卸载 文件系统全局管理层 s_fs_info、s_op、s_bdev
驱动开发者视角: 在字符设备驱动中,你最常打交道的是 struct file 和 struct inode。struct dentry 和 struct super_block 更多由VFS和文件系统层管理,但理解它们有助于你把握整个I/O路径的全貌。

3.2 核心关系与交互流程

下图展示了当用户程序调用 open("/dev/mydevice", O_RDWR) 时,这四种结构体之间的协作关系:

VFS 四大核心数据结构关系图(以 open 系统调用为例) 用户进程 fd = open("/dev/xxx") struct file f_op → file_operations private_data → 驱动私有 f_path.dentry struct dentry d_inode → inode struct inode i_rdev → 设备号 i_cdev → 字符设备结构 i_sb struct super_block s_op → super_operations inode 通过 i_fop 提供默认 f_op 关系说明: 1. 进程通过 fd 找到 struct file,file 通过 f_path.dentry 找到 dentry。 2. dentry 通过 d_inode 找到 inode,inode 包含文件的物理元数据。 3. inode 通过 i_sb 找到 super_block,super_block 管理整个文件系统。 4. 驱动注册时,inode 的 i_fop 被设置为驱动提供的 file_operations。 5. open() 时,VFS 将 inode 的 i_fop 复制给 file 的 f_op,完成操作绑定。 6. 驱动常用 private_data 保存设备私有结构,实现 file 与设备实例的关联。

3.3 struct file:进程视角的文件句柄

struct file 代表一个打开的文件描述,它存在于进程的文件描述符表中。每个 open() 系统调用都会创建一个新的 struct file 实例。驱动开发者最关心的字段如下:

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_NONBLOCK 等)
    void                    *private_data;  // 驱动私有数据指针(最常用!)
    struct address_space    *f_mapping;     // 地址映射(用于页缓存)
};
驱动开发关键点:
  • f_op: 这是驱动与VFS交互的“接口表”。驱动在 open() 方法中通常不需要修改它,因为VFS已经在 inode 的 i_fop 中设置好了。
  • private_data: 这是驱动开发者的“瑞士军刀”。在 open() 中,你可以将设备结构体指针赋值给 private_data,然后在 read()、write()、ioctl() 中直接取出使用,避免了全局变量的混乱。
  • f_flags: 驱动应检查 O_NONBLOCK 标志,以决定是否在 I/O 操作中阻塞。

3.4 struct inode:文件的物理元数据

struct inode 是VFS中最核心的元数据结构体,它代表文件系统中的一个对象(文件、目录、设备节点等)。每个文件在存储介质上都有一个唯一的 inode 编号。对于设备驱动,inode 是连接设备文件与驱动代码的桥梁。

struct inode {
    umode_t                 i_mode;         // 文件类型与权限(S_IFCHR 表示字符设备)
    dev_t                   i_rdev;         // 设备号(主设备号 + 次设备号)
    struct cdev             *i_cdev;        // 指向字符设备结构(驱动注册的关键)
    const struct file_operations *i_fop;    // 默认的文件操作表
    struct super_block      *i_sb;          // 所属超级块
    struct address_space    *i_mapping;     // 地址映射
    unsigned long           i_ino;          // inode 编号
    // ... 其他字段
};
常见误区: 初学者容易混淆 struct file 和 struct inode。记住:inode 代表文件本身(物理实体),file 代表打开的文件实例(动态句柄)。同一个文件(inode)可以被多个进程打开,产生多个 file 实例,但 inode 只有一个。

3.5 struct dentry:路径名与inode的粘合剂

struct dentry(目录项)是VFS中用于路径名解析的缓存单元。它不直接对应磁盘上的数据结构,而是内存中用于加速路径查找的组件。每个路径分量(如 /dev/ttyS0 中的 dev 和 ttyS0)都对应一个 dentry。

struct dentry {
    struct inode            *d_inode;       // 指向关联的 inode(关键!)
    struct dentry           *d_parent;      // 父目录项
    struct qstr             d_name;         // 目录项名称(快速比较用)
    struct list_head        d_child;        // 兄弟目录项链表
    struct list_head        d_subdirs;      // 子目录项链表
    unsigned int            d_flags;        // 标志位
    // ... 其他字段
};

dentry 的主要作用是加速路径解析。当访问 /dev/mydevice 时,VFS 会从根 dentry 开始,逐级查找,最终找到 mydevice 对应的 dentry,然后通过 d_inode 获取 inode。这个过程被 dentry 缓存(dcache)大幅加速。

3.6 struct super_block:文件系统的全局管理者

struct super_block 代表一个已挂载的文件系统实例。它包含了文件系统的全局信息,如块大小、最大文件大小、操作函数表等。对于驱动开发者,super_block 通常不直接操作,但理解它有助于把握文件系统的整体架构。

struct super_block {
    dev_t                   s_dev;          // 文件系统所在设备号
    unsigned long           s_blocksize;    // 块大小
    struct super_operations *s_op;          // 超级块操作函数表
    struct dentry           *s_root;        // 文件系统根目录的 dentry
    struct list_head        s_inodes;       // 所有 inode 的链表
    void                    *s_fs_info;     // 文件系统私有数据(如 ext4 的 super_block 扩展)
    struct block_device     *s_bdev;        // 块设备指针
    // ... 其他字段
};
驱动关联: 虽然驱动开发者很少直接操作 super_block,但当你实现一个简单的内存文件系统或伪文件系统时(如 sysfs、debugfs),你需要填充 super_operations 中的回调函数,如 alloc_inode()、destroy_inode()、write_super() 等。

3.7 实战:驱动中如何获取和使用这些结构体

在字符设备驱动的典型操作中,VFS 会将这些结构体指针传递给驱动回调函数。以下是一个典型的 open() 方法示例:

static int mydevice_open(struct inode *inode, struct file *file)
{
    // 1. 从 inode 中获取设备号,找到对应的设备结构体
    struct my_device *dev = container_of(inode->i_cdev, struct my_device, cdev);

    // 2. 将设备结构体指针保存到 file->private_data 中
    file->private_data = dev;

    // 3. 检查打开模式(可选)
    if (file->f_flags & O_NONBLOCK) {
        // 非阻塞模式处理
    }

    // 4. 初始化设备状态(如递增使用计数)
    if (!atomic_inc_not_zero(&dev->available)) {
        return -EBUSY;  // 设备忙
    }

    return 0;
}

static ssize_t mydevice_read(struct file *file, char __user *buf,
                             size_t count, loff_t *ppos)
{
    // 从 private_data 中取出设备结构体
    struct my_device *dev = (struct my_device *)file->private_data;

    // 使用 dev 进行数据读取...
    return count;
}
关键模式总结:
  1. open() 时: 通过 inode->i_cdev 找到设备结构体,存入 file->private_data。
  2. read/write/ioctl 时: 从 file->private_data 取出设备结构体,直接使用。
  3. release() 时: 清理 private_data 中保存的资源。

3.8 四种结构体的生命周期与引用关系

下表总结了四种结构体在典型场景下的生命周期和引用计数管理:

结构体 创建时机 销毁时机 引用计数 驱动注意事项
struct file open() 系统调用 close() 系统调用 f_count(由VFS管理) 不要自行释放,不要长期持有指针
struct inode 文件创建或第一次访问 文件被删除且所有引用释放 i_count(由VFS管理) 可通过 i_cdev 找到驱动结构
struct dentry 路径解析时 从 dcache 中回收 d_count(由VFS管理) 通常不直接操作,除非实现自定义文件系统
struct super_block 文件系统挂载时 文件系统卸载时 s_count(由VFS管理) 驱动中极少直接操作
重要提醒: 驱动开发者永远不要自行增加或减少 struct file 和 struct inode 的引用计数。这些由VFS核心代码精确管理。错误的引用计数操作会导致内核崩溃(use-after-free 或内存泄漏)。

3.9 本章小结

VFS 的四大核心数据结构构成了 Linux 文件系统的骨架:

  • struct super_block 是文件系统的“户口本”,管理全局信息。
  • struct inode 是文件的“身份证”,存储永久元数据。
  • struct dentry 是路径的“导航员”,加速名字到 inode 的转换。
  • struct file 是进程的“通行证”,代表一次打开会话。

在驱动开发中,你主要与 struct file 和 struct inode 打交道,通过 private_data 和 i_cdev 建立驱动与VFS的桥梁。掌握这些结构体的关系和生命周期,是编写健壮、高效设备驱动的基础。