4. VFS核心数据结构(下):目录项对象与文件对象

在VFS中,目录项对象(dentry)和文件对象(file)是进程与文件系统交互的直接接口。dentry负责路径解析与目录结构缓存,file则管理打开文件的状态与读写操作。理解这两个对象的结构与协作关系,是掌握VFS运行机制的关键。

4.1 目录项对象(dentry)的结构与作用

目录项对象(dentry)是VFS在内存中表示路径名与inode之间映射关系的核心数据结构。它不直接对应磁盘上的目录项,而是内核在路径查找过程中动态创建和缓存的中间层。

4.1.1 dentry的主要作用

  • 加速路径解析:缓存路径名到inode的映射,避免重复的磁盘目录项读取。
  • 维护目录层次:通过父子指针构建内存中的目录树结构。
  • 支持硬链接:多个dentry可以指向同一个inode。
  • 负缓存:缓存不存在的路径名,快速返回“文件不存在”。

4.1.2 dentry结构体核心字段

字段类型说明
d_namestruct qstr目录项名称(快速字符串)
d_parentstruct dentry *父目录项指针
d_inodestruct inode *关联的inode(NULL表示负缓存)
d_opconst struct dentry_operations *dentry操作函数表
d_sbstruct super_block *所属超级块
d_flagsunsigned int状态标志(DCACHE_*)
d_lrustruct list_headLRU链表节点,用于回收
d_subdirsstruct list_head子目录项链表头
d_childstruct list_head兄弟目录项链表节点

4.1.3 dentry状态与生命周期

dentry在内存中经历三种状态:

  • 被使用(DCACHE_USED):当前有进程引用,d_count > 0,不会被回收。
  • 未使用(DCACHE_UNUSED):d_count == 0,但仍在LRU链表中,可被回收。
  • 负缓存(DCACHE_NEGATIVE):d_inode == NULL,表示路径不存在,用于加速失败查找。
性能关键点: dentry缓存是VFS路径解析性能的核心。内核通过hash表(dentry_hashtable)快速查找dentry,LRU算法回收未使用的dentry。在大型目录场景下,dentry缓存命中率直接影响系统调用(如stat、open)的延迟。

4.1.4 dentry操作函数表(dentry_operations)

struct dentry_operations {
    int (*d_revalidate)(struct dentry *, unsigned int);
    int (*d_weak_revalidate)(struct dentry *, unsigned int);
    int (*d_hash)(const struct dentry *, struct qstr *);
    int (*d_compare)(const struct dentry *, unsigned int,
                     const char *, const struct qstr *);
    int (*d_delete)(const struct dentry *);
    void (*d_release)(struct dentry *);
    void (*d_prune)(struct dentry *);
    void (*d_iput)(struct dentry *, struct inode *);
    char *(*d_dname)(struct dentry *, char *, int);
    struct vfsmount *(*d_automount)(struct path *);
    int (*d_manage)(const struct dentry *, bool);
};

关键回调说明:

  • d_revalidate:在路径查找时验证dentry是否仍然有效(网络文件系统常用)。
  • d_hash:自定义hash计算,用于支持大小写不敏感等特殊需求。
  • d_compare:自定义名称比较函数。
  • d_delete:最后一个引用释放时调用,决定是否允许dentry进入未使用状态。
  • d_automount:处理自动挂载点(如NFS回退)。

4.1.5 dentry与inode关系SVG图

dentry与inode关系图 struct dentry d_name: "document.txt" d_parent: 指向父目录 d_inode: ——————————→ d_op: dentry_operations d_count: 2 (被引用) struct inode i_ino: 123456 i_mode: 0100644 (普通文件) i_nlink: 2 (硬链接计数) i_size: 4096 bytes i_fop: file_operations i_sb: super_block d_inode指针 父目录 dentry d_name: "home" d_inode: 指向目录inode d_parent 关键说明 • 一个inode可被多个dentry 指向(硬链接) • dentry是内存对象,无磁盘对应

4.2 文件对象(file)的结构与作用

文件对象(file)代表进程打开的一个文件实例。它保存了读写位置、访问模式、文件指针等动态状态。每个进程的struct files_struct中维护了一个文件描述符表,每个描述符指向一个struct file。

4.2.1 file结构体核心字段

字段类型说明
f_pathstruct path文件路径(包含dentry和vfsmount)
f_inodestruct inode *缓存指针,指向关联inode
f_opconst struct file_operations *文件操作函数表
f_posloff_t当前读写偏移量
f_flagsunsigned int打开标志(O_RDONLY/O_WRONLY等)
f_modefmode_t访问模式(读/写/执行)
f_countatomic_long_t引用计数
f_ownerstruct fown_struct异步I/O所有者
f_credconst struct cred *打开时的安全凭证
private_datavoid *文件系统私有数据

4.2.2 文件对象与进程的关系

进程通过文件描述符(fd)访问文件对象。多个fd可以指向同一个file对象(如dup/fork共享),多个file对象也可以指向同一个inode(如多次打开同一文件)。

面试高频题: 为什么lseek只影响当前file对象的f_pos,而不会影响其他打开同一文件的进程?因为每个struct file是独立的,f_pos是文件实例的私有状态。这正是多线程并发读写时需要加锁保护f_pos的原因。

4.2.3 文件对象生命周期SVG图

文件对象生命周期与共享关系 进程A fd[3] ——————————→ fd[4] ——————————→ 进程B fd[3] ——————————→ struct file #1 f_pos: 1024 f_count: 2 f_flags: O_RDWR struct file #2 f_pos: 0 f_count: 1 f_flags: O_RDONLY struct inode i_ino: 789012 i_nlink: 1 i_size: 65536 i_fop: ext4_file_ops 共享关系 进程A的fd[3]和fd[4]共享 同一个file对象(dup后)

4.3 文件操作函数表(file_operations)

file_operations是VFS中最重要的多态接口之一。每个文件系统为它的inode注册一组操作函数,VFS通过f_op指针调用具体实现,实现“同一套系统调用,不同文件系统行为”的设计目标。

4.3.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 *);
    __poll_t (*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.3.2 核心回调函数分类

类别回调函数说明
定位llseek修改文件读写位置
读写read / write同步读写(用户态缓冲区)
迭代读写read_iter / write_iter支持iov_iter的读写(现代接口)
目录遍历iterate / iterate_shared读取目录项列表
内存映射mmap将文件映射到进程地址空间
生命周期open / release文件打开和关闭时的钩子
同步fsync / fasync数据同步到磁盘
锁lock / flock文件锁操作
空间管理fallocate预分配/释放磁盘空间

4.3.3 系统调用到file_operations的调用路径

以read()系统调用为例,VFS的调用链如下:

  1. 用户态调用read(fd, buf, count),触发系统调用sys_read。
  2. 内核通过fd找到struct file *f,获取f->f_op。
  3. 调用f->f_op->read(f, buf, count, &f->f_pos)。
  4. 具体文件系统(如ext4)实现自己的read函数,执行磁盘I/O。
  5. 更新f->f_pos,返回读取字节数。
设计模式: file_operations是典型的策略模式。VFS定义接口契约,每个文件系统提供自己的实现。例如ext4的ext4_file_operations、procfs的proc_reg_file_ops、socket的socket_file_ops,行为完全不同但对外接口一致。

4.3.4 不同文件系统的file_operations对比

文件系统file_operations实例特点
ext4ext4_file_operations支持直接I/O、DAX、加密
procfsproc_reg_file_ops动态生成内容,无磁盘操作
sysfssysfs_file_operations读写内核属性,单值操作
sockfssocket_file_ops网络socket读写,无seek
tmpfsshmem_file_operations基于内存,支持共享映射

4.3.5 file_operations调用流程SVG图

系统调用到file_operations调用流程 用户态 read(fd, buf, 1024) VFS系统调用层 sys_read() → fd查找file VFS通用层 f->f_op->read() 具体文件系统 ext4_file_read_iter() 块设备层 submit_bio() 调用链关键点 1. VFS通过file->f_op找到具体实现 2. 不同文件系统提供不同的f_op实例 3. 用户态无需关心底层文件系统类型

4.4 dentry、file、inode三者协作关系

在VFS中,dentry、file、inode三者通过指针相互关联,共同完成文件操作:

  • dentry:负责路径名到inode的映射,是路径查找的缓存层。
  • inode:负责存储文件的元数据和磁盘块映射,是持久化信息的载体。
  • file:负责管理打开文件的状态(位置、模式、私有数据),是进程与inode之间的会话层。
常见误区: dentry不是磁盘上的目录项(dirent)。磁盘目录项是存储在目录文件中的固定长度记录,而dentry是内核在内存中构建的缓存结构。两者通过dentry->d_inode和inode的目录项读取回调间接关联。

4.4.1 核心数据结构关系总结表

数据结构生命周期磁盘对应主要作用
dentry动态创建,LRU回收无直接对应路径缓存、目录层次维护
inode文件存在则存在磁盘inode元数据、数据块映射
fileopen到close无打开状态、读写位置、操作函数

4.4.2 三者协作SVG图

dentry、file、inode协作关系 进程 fd → file struct file f_path.dentry → f_inode → ← 上一章 📖 返回目录 下一章 →