4. VFS核心数据结构(下):目录项对象与文件对象
在VFS中,目录项对象(dentry)和文件对象(file)是连接进程与具体文件系统的关键桥梁。dentry负责路径解析与目录结构缓存,file则维护进程与打开文件之间的交互状态。本章深入剖析这两个对象的结构、dentry缓存(dcache)的工作原理以及文件操作表(file_operations)的注册与调用机制。
4.1 目录项对象(dentry)结构
dentry代表路径中的一个分量(如 /usr/src/linux 中的 usr、src、linux),它不直接对应磁盘上的数据,而是内存中用于加速路径查找的抽象。每个dentry关联一个inode,并参与形成目录树。
4.1.1 dentry结构体关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
d_name |
struct qstr | 目录项名称(含hash值,用于快速比较) |
d_parent |
struct dentry * | 父目录项指针 |
d_inode |
struct inode * | 关联的inode(NULL表示负dentry) |
d_sb |
struct super_block * | 所属超级块 |
d_op |
struct dentry_operations * | dentry操作函数表 |
d_flags |
unsigned int | 标志位(如DCACHE_DISCONNECTED) |
d_lru |
struct list_head | LRU链表节点,用于dcache回收 |
d_subdirs |
struct list_head | 子目录项链表头 |
d_child |
struct list_head | 兄弟目录项链表节点 |
4.1.2 dentry状态与生命周期
- 正dentry(positive):
d_inode != NULL,表示路径分量有效。 - 负dentry(negative):
d_inode == NULL,用于缓存“文件不存在”的结果,避免重复磁盘访问。 - 别名(alias):一个inode可被多个dentry引用(硬链接场景),通过
d_alias链表管理。 - 未连接(disconnected):dentry未挂入主目录树,常见于文件系统遍历或NFS回退路径。
dentry_operations 的 d_delete 回调控制回收策略。
4.2 dentry缓存(dcache)工作原理
dcache是VFS中最核心的缓存机制之一,它将解析过的路径分量dentry保存在内存中,使后续路径查找无需重复遍历磁盘目录结构。dcache由两个主要数据结构支撑:全局hash表和LRU链表。
4.2.1 dcache架构
4.2.2 dcache查找与缓存命中
当进程访问 /home/user/file.txt 时,VFS逐分量查找:
- 从根dentry(
/)开始,计算home的hash值,在dentry_hashtable中查找。 - 若命中,直接获得
home的dentry,进而获取其inode。 - 若未命中,调用底层文件系统的
lookup回调,从磁盘读取目录项,构造新dentry并插入hash表。 - 重复上述步骤处理
user和file.txt。
4.2.3 dcache回收与压力管理
当系统内存紧张时,内核通过 shrink_dcache() 回收dentry。回收策略遵循以下原则:
| 优先级 | 回收对象 | 条件 |
|---|---|---|
| 高 | 负dentry | d_inode == NULL,且最近未被访问 |
| 中 | 未打开文件的dentry | d_count == 0,且LRU链表尾部 |
| 低 | 已打开文件的dentry | d_count > 0,仅当inode被回收时联动释放 |
4.3 文件对象(file)结构
文件对象代表进程打开的一个文件实例,它位于进程与inode之间,维护了读写位置、访问模式、文件操作表等运行时状态。每个 open() 系统调用创建一个新的file对象,多个进程可打开同一文件,产生多个独立的file对象。
4.3.1 file结构体关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
f_path |
struct path | 文件路径(含dentry和挂载点) |
f_inode |
struct inode * | 关联的inode(快捷方式) |
f_op |
struct file_operations * | 文件操作函数表 |
f_pos |
loff_t | 当前读写偏移量 |
f_flags |
unsigned int | 打开标志(O_RDONLY/O_WRONLY/O_RDWR等) |
f_mode |
fmode_t | 访问模式(读/写/执行) |
f_count |
atomic_t | 引用计数,决定何时释放 |
f_private |
void * | 文件系统私有数据指针 |
4.3.2 文件对象与进程的关系
4.4 文件操作表(file_operations)
file_operations 是VFS多态设计的核心体现。每个文件系统为inode注册一组操作函数,当进程通过file对象发起系统调用时,VFS通过 f_op 指针调用对应的实现。这种设计使VFS无需关心底层文件系统的具体实现细节。
4.4.1 file_operations结构体
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *, int, loff_t, loff_t);
void (*show_fdinfo)(struct seq_file *, struct file *);
};
4.4.2 关键回调函数说明
| 回调 | 触发系统调用 | 典型实现逻辑 |
|---|---|---|
open |
open() |
检查访问权限、初始化私有数据、预分配资源 |
release |
close() |
释放file对象私有资源、回写脏数据 |
read |
read() |
从磁盘读取数据到用户缓冲区,更新f_pos |
write |
write() |
从用户缓冲区写入磁盘,处理写时复制 |
llseek |
lseek() |
修改f_pos,支持SEEK_SET/SEEK_CUR/SEEK_END |
mmap |
mmap() |
将文件映射到进程地址空间 |
fsync |
fsync() |
将文件数据与元数据刷入磁盘 |
4.4.3 文件操作表的注册与多态调用
文件系统在创建inode时,通过 inode->i_fop 指针注册 file_operations。当进程打开文件时,VFS将inode的操作表复制到file对象的 f_op 字段:
// 文件系统在inode初始化时注册操作表
static const struct file_operations ext4_file_operations = {
.llseek = ext4_llseek,
.read_iter = ext4_read_iter,
.write_iter = ext4_write_iter,
.mmap = ext4_file_mmap,
.open = ext4_file_open,
.release = ext4_release_file,
.fsync = ext4_sync_file,
};
// VFS open() 系统调用核心路径
int vfs_open(const struct path *path, struct file *file)
{
struct inode *inode = path->dentry->d_inode;
file->f_path = *path;
file->f_inode = inode;
file->f_op = fops_get(inode->i_fop); // 关键:继承inode的操作表
return do_dentry_open(file, inode, NULL);
}
open 回调中替换 f_op 指针,实现“打开时多态”。例如,设备文件根据打开模式切换读写操作表,或加密文件系统在打开时插入透明加解密层。替换后,后续所有读写调用都走新操作表,无需修改VFS核心路径。
4.5 dentry与file对象的协同工作流
以下流程图展示了从进程发起 open() 到获取file对象的完整路径:
4.6 本章小结
- dentry 是路径分量的内存缓存,通过hash表加速查找,负dentry可避免不必要的磁盘I/O。
- dcache 采用全局hash表 + LRU链表管理,在内存压力下优先回收负dentry和未引用dentry。
- file对象 维护进程与打开文件的运行时状态,每个
open()创建一个独立实例,通过f_count管理生命周期。 - file_operations 是VFS多态的核心,文件系统通过注册回调函数实现差异化行为,VFS通过
f_op指针统一调用。 - 从
open()到返回fd的完整流程涉及dentry查找、inode获取、file对象分配、操作表绑定等多个步骤,每一步都有对应的错误回滚机制。