2. VFS数据结构全景
虚拟文件系统(VFS)是Linux内核中文件系统的抽象层,它通过四个核心数据结构——超级块(super_block)、索引节点(inode)、目录项(dentry)和文件对象(file)——实现了对不同文件系统的统一访问。这四个结构分别从不同粒度描述文件系统的元数据、文件属性、路径解析和打开文件状态,构成了VFS的骨架。
2.1 四大核心结构概述
下表从职责、生命周期、关键字段和所属层次四个维度对比了四个核心结构:
| 结构 | 职责 | 生命周期 | 关键字段 | 层次 |
|---|---|---|---|---|
| super_block | 描述已挂载文件系统的全局元数据 | 挂载时创建,卸载时销毁 | s_fs_info, s_root, s_blocksize, s_op | 文件系统级 |
| inode | 描述文件或目录的物理元数据(权限、大小、位置) | 首次访问时创建,引用计数归零后回收 | i_mode, i_uid, i_size, i_ino, i_op, i_fop | 文件级 |
| dentry | 描述路径名与inode的映射关系 | 路径解析时创建,缓存保留(dcache) | d_name, d_parent, d_inode, d_op | 路径级 |
| file | 描述进程打开的文件实例(读写位置、标志) | open()时创建,close()时销毁 | f_path, f_pos, f_flags, f_op | 进程级 |
2.2 超级块(super_block)——文件系统的“户口本”
超级块是VFS中最高层级的结构,每个已挂载的文件系统对应一个super_block实例。它记录了文件系统的全局信息,包括块大小、最大文件大小、操作函数表等。
// include/linux/fs.h (简化核心字段)
struct super_block {
struct list_head s_list; // 全局超级块链表
dev_t s_dev; // 块设备标识
unsigned long s_blocksize; // 块大小(字节)
loff_t s_maxbytes; // 最大文件大小
struct file_system_type *s_type; // 文件系统类型
struct dentry *s_root; // 根目录的dentry
struct super_operations *s_op; // 超级块操作函数表
struct list_head s_inodes; // 该文件系统所有inode链表
struct list_head s_dentry_lru; // 未使用的dentry LRU链表
void *s_fs_info; // 指向具体文件系统私有数据
};
super_operations 是超级块的操作函数表,定义了文件系统层面的回调:
alloc_inode()/destroy_inode()— 分配/销毁inodewrite_super()/sync_fs()— 同步超级块/文件系统statfs()— 获取文件系统统计信息remount_fs()— 重新挂载
2.3 索引节点(inode)——文件的“身份证”
inode存储了文件或目录的所有元数据,但不包含文件名。文件名由dentry管理。每个文件(包括目录、设备文件等)在VFS中都有唯一的inode。
struct inode {
umode_t i_mode; // 文件类型与权限
kuid_t i_uid; // 所有者UID
kgid_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编号(文件系统内唯一)
struct super_block *i_sb; // 所属超级块
struct address_space *i_mapping; // 地址空间(页缓存)
const struct inode_operations *i_op; // inode操作
const struct file_operations *i_fop; // 默认文件操作
struct list_head i_sb_list; // 超级块inode链表节点
union {
struct ext2_inode_info ext2_i; // 具体文件系统私有数据
struct ext4_inode_info ext4_i;
// ... 其他文件系统
};
};
i_fop字段为目录和常规文件提供了默认的文件操作函数表。当进程通过open()打开文件时,VFS会将inode的i_fop赋值给file结构的f_op,从而实现文件操作的多态分发。
2.4 目录项(dentry)——路径解析的“缓存加速器”
dentry是VFS中最活跃的结构,它负责将路径名组件(如/usr/bin/gcc中的bin)映射到对应的inode。dentry不直接对应磁盘上的数据结构,而是VFS在内存中构建的路径缓存(dcache)。
struct dentry {
struct qstr d_name; // 目录项名称(快速比较)
struct inode *d_inode; // 指向关联的inode(可为NULL)
struct dentry *d_parent; // 父目录项
struct list_head d_child; // 父目录的子目录项链表
struct list_head d_subdirs; // 子目录项链表(仅目录)
const struct dentry_operations *d_op; // 目录项操作
unsigned int d_flags; // 标志位
struct hlist_bl_node d_hash; // dcache哈希表节点
};
dentry的生命周期状态:
- 被使用(used):d_inode非空,且d_count > 0,被某个file或进程引用
- 未使用(unused):d_inode非空,但d_count == 0,保留在LRU链表中等待回收
- 负目录项(negative):d_inode为NULL,表示路径名不存在,用于缓存“查找失败”结果
/home/user/file.txt)都需要从根目录开始逐级查找dentry。dcache通过哈希表和LRU链表加速查找,并缓存负目录项避免重复的磁盘I/O。如果dcache命中率低,系统调用如stat()、open()的性能会急剧下降。
2.5 文件对象(file)——进程与文件的“会话”
file结构代表一个打开的文件实例,是进程与文件系统交互的直接接口。每个open()系统调用都会创建一个新的file结构,多个进程可以打开同一个文件(共享同一个inode),但各自拥有独立的file实例。
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, O_SYNC等)
fmode_t f_mode; // 读写模式(FMODE_READ, FMODE_WRITE)
struct address_space *f_mapping; // 地址空间(与inode共享)
void *private_data; // 具体文件系统私有数据
};
file_operations 是VFS多态性的核心体现,每个文件系统可以自定义这些操作:
| 操作 | 对应系统调用 | 说明 |
|---|---|---|
read() |
read() / pread64() | 从文件读取数据 |
write() |
write() / pwrite64() | 向文件写入数据 |
llseek() |
lseek() | 修改文件读写位置 |
mmap() |
mmap() | 内存映射文件 |
unlocked_ioctl() |
ioctl() | 设备控制命令 |
poll() |
poll() / select() | I/O多路复用 |
2.6 四大结构的关系全景
下图展示了四个核心结构之间的引用关系和协作流程:
2.7 结构间的协作流程
以一次典型的open("/home/user/doc.txt", O_RDONLY)系统调用为例,展示四个结构的协作:
- 路径解析:VFS从当前进程的根目录dentry(或当前工作目录dentry)开始,逐级查找路径组件
home、user、doc.txt。每个组件在dcache中查找对应的dentry,若未命中则调用具体文件系统的lookup()从磁盘读取。 - 获取inode:最后一个组件
doc.txt的dentry通过d_inode指针获取文件的inode。inode中包含了文件的权限、大小、磁盘块位置等元数据。 - 创建file结构:VFS分配一个新的file结构,将
f_path.dentry指向目标dentry,f_inode指向目标inode,并将inode的i_fop赋值给f_op。 - 关联进程:将新创建的file结构指针存入当前进程的文件描述符表(
fdtable),返回文件描述符整数给用户态。
super_operations、inode_operations、file_operations)实现多态。每个具体文件系统(如ext4、btrfs、tmpfs)在初始化时注册自己的操作函数。当VFS调用file->f_op->read()时,实际执行的是ext4_read()或btrfs_read(),对上层完全透明。
2.8 驱动适配视角:如何利用四大结构
对于文件系统驱动开发者,理解四大结构的关系是编写新文件系统的前提。以下是一个伪代码示例,展示如何注册一个简单的内存文件系统:
// 伪代码:注册一个简单的内存文件系统
static const struct super_operations myfs_super_ops = {
.alloc_inode = myfs_alloc_inode,
.destroy_inode = myfs_destroy_inode,
.statfs = myfs_statfs,
};
static const struct inode_operations myfs_inode_ops = {
.lookup = myfs_lookup,
.create = myfs_create,
.mkdir = myfs_mkdir,
};
static const struct file_operations myfs_file_ops = {
.read = myfs_read,
.write = myfs_write,
.llseek = generic_file_llseek,
};
// 填充超级块
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *root_inode;
struct dentry *root_dentry;
sb->s_op = &myfs_super_ops;
sb->s_blocksize = PAGE_SIZE;
// 创建根目录inode
root_inode = new_inode(sb);
root_inode->i_mode = S_IFDIR | 0755;
root_inode->i_op = &myfs_inode_ops;
root_inode->i_fop = &simple_dir_operations;
// 创建根目录dentry
root_dentry = d_make_root(root_inode);
sb->s_root = root_dentry;
return 0;
}
- super_block:必须实现
alloc_inode和destroy_inode,用于管理inode的内存分配 - inode:通过
i_op提供目录操作(lookup、create),通过i_fop提供默认文件操作 - dentry:通常不需要自定义dentry操作,但可以通过
d_op实现哈希比较优化 - file:通过
f_op提供读写、定位等操作,是用户态与文件系统交互的直接接口
2.9 总结:四大结构的层次与关系
四个核心结构构成了VFS的四层抽象模型:
| 层次 | 结构 | 核心作用 | 生命周期管理 |
|---|---|---|---|
| 文件系统层 | super_block | 管理整个文件系统的全局状态 | 挂载/卸载 |
| 文件元数据层 | inode | 存储文件属性与磁盘映射 | 引用计数 |
| 路径解析层 | dentry | 缓存路径名到inode的映射 | dcache LRU |
| 进程接口层 | file | 提供进程与文件的交互会话 | open/close |