3. VFS层核心数据结构:struct file、struct inode、struct dentry、struct super_block的作用与关系
虚拟文件系统(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) 时,这四种结构体之间的协作关系:
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;
}
- open() 时: 通过
inode->i_cdev找到设备结构体,存入file->private_data。 - read/write/ioctl 时: 从
file->private_data取出设备结构体,直接使用。 - 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的桥梁。掌握这些结构体的关系和生命周期,是编写健壮、高效设备驱动的基础。