2. VFS四大核心对象super_block/inode/dentry/file全解析四大核心对象是VFS的基石它们分别描述了文件系统的不同维度从静态的文件系统结构到动态的进程文件交互形成了完整的文件管理体系。本章节基于Linux 6.6内核源码定义在include/linux/fs.h完整拆解每个对象的核心字段、生命周期、相互关联关系。2.1 超级块struct super_blockstruct super_block是一个已挂载的文件系统实例的全局描述符对应磁盘上的文件系统超级块存储了文件系统的类型、块大小、魔数、根目录、操作函数集、挂载参数等全局信息是整个文件系统的「根」。每个已挂载的文件系统在内核中都有且仅有一个super_block实例比如系统挂载了/ext4、/bootvfat、/procprocfs就会有三个对应的super_block实例。2.1.1 核心字段拆解struct super_block { // 超级块的链表节点所有已挂载的super_block都链接到全局super_blocks链表 struct list_head s_list; // 设备标识符块设备的设备号伪文件系统为0 dev_t s_dev; // 文件系统的块大小单位字节 unsigned long s_blocksize; // 块大小的位掩码用于快速计算 unsigned char s_blocksize_bits; // 文件系统的魔数用于识别文件系统类型比如ext4的魔数是0xEF53 unsigned long s_magic; // 超级块的标志位比如MS_RDONLY只读挂载、MS_NOSUID禁止suid unsigned long s_flags; // 挂载的次数用于共享挂载 int s_count; // 超级块的读写信号量保护超级块的修改 struct rw_semaphore s_umount; // 超级块的自旋锁 spinlock_t s_lock; // 超级块操作函数集文件系统级别的操作接口 const struct super_operations *s_op; // 该文件系统的类型描述符对应file_system_type结构体 struct file_system_type *s_type; // 该超级块对应的挂载实例 struct vfsmount *s_vfsmnt; // 文件系统的根目录dentry struct dentry *s_root; // inode相关字段 // 该文件系统的所有inode的哈希表 struct hlist_head *s_inodes; // 该文件系统的inode数量统计 atomic_long_t s_inodes_count; // 正在使用的inode数量 atomic_long_t s_active_inodes; // 脏inode链表需要同步到磁盘的inode struct list_head s_inodes_dirty; // inode的LRU链表用于inode缓存回收 struct list_head s_inodes_lru; // 目录项缓存相关字段 // 目录项缓存的收缩函数 int (*s_shrink)(struct super_block *sb, struct shrink_control *sc); // 文件系统的最大链接数 unsigned int s_max_links; // 时间戳的粒度比如ext4是1纳秒 u32 s_time_gran; // 具体文件系统的私有数据比如ext4的ext4_sb_info void *s_fs_info; // 挂载命名空间相关 struct user_namespace *s_user_ns; // 安全相关的钩子函数 struct security_hook_heads *s_security; } __randomize_layout;2.1.2 核心字段深度解析1.s_op超级块操作函数集定义了文件系统级别的操作接口具体文件系统必须实现这套接口核心函数包括struct super_operations { // 分配一个新的inode struct inode *(*alloc_inode)(struct super_block *sb); // 释放inode void (*destroy_inode)(struct inode *inode); // 把inode的元数据同步到磁盘 void (*write_inode)(struct inode *inode, struct writeback_control *wbc); // 丢弃inode void (*evict_inode)(struct inode *inode); // 把文件系统的所有脏数据同步到磁盘 int (*sync_fs)(struct super_block *sb, int wait); // 冻结文件系统用于快照 int (*freeze_super)(struct super_block *sb); // 解冻文件系统 int (*thaw_super)(struct super_block *sb); // umount时调用释放超级块 void (*put_super)(struct super_block *sb); // 统计文件系统的磁盘使用情况 int (*statfs)(struct dentry *dentry, struct kstatfs *buf); };比如ext4文件系统会实现自己的ext4_sops超级块操作函数集处理ext4格式的inode分配、同步、磁盘统计等逻辑。2.s_root文件系统的根目录dentry指向该文件系统根目录的dentry实例是路径解析的起点挂载文件系统时内核会读取文件系统的根目录inode创建对应的dentry赋值给s_root。3.s_fs_info文件系统私有数据这是VFS为具体文件系统提供的私有数据指针具体文件系统可以把自己的超级块私有信息存储在这里比如ext4的struct ext4_sb_info、xfs的struct xfs_mountVFS层不会修改这个指针完全由具体文件系统管理。4.s_inodes/s_inodes_lru/s_inodes_dirty管理该文件系统的所有inode包括inode的哈希表用于快速查找inode、LRU链表用于inode缓存回收、脏inode链表用于同步到磁盘是inode生命周期管理的核心结构。2.1.3 生命周期创建mount系统调用挂载文件系统时内核调用具体文件系统的fill_super函数读取磁盘上的超级块创建并初始化super_block实例加入全局super_blocks链表使用文件系统挂载期间super_block实例一直存在用于管理该文件系统的所有inode、dentry、挂载参数销毁umount系统调用卸载文件系统时内核会等待所有的inode释放调用super_operations的put_super函数释放super_block实例从全局链表中移除。2.2 索引节点struct inodestruct inodeIndex Node索引节点是VFS中最核心的对象它描述了一个文件的元数据和物理存储信息包括文件的权限、所有者、大小、创建/修改/访问时间、块指针、扩展属性等。核心认知纠正inode是文件的唯一标识和文件名无关。文件名只是dentry中的一个字符串一个inode可以对应多个文件名硬链接每个硬链接对应一个dentry指向同一个inode。每个文件包括目录、设备文件、管道、socket在内核中都有且仅有一个inode实例无论是磁盘文件系统的inode持久化到磁盘还是内存文件系统的inode仅存在于内存都由VFS统一管理。2.2.1 核心字段拆解struct inode { // inode的哈希表节点用于快速查找 struct hlist_node i_hash; // inode的链表节点用于链接到super_block的inode链表 struct list_head i_list; // inode的LRU链表节点用于缓存回收 struct list_head i_lru; // 脏inode链表节点用于同步到磁盘 struct list_head i_io_list; // 该inode所属的超级块 struct super_block *i_sb; // inode号文件系统内唯一是文件的唯一标识 ino_t i_ino; // 硬链接计数有多少个硬链接指向这个inode为0时释放inode unsigned int i_nlink; // 文件的UID/GID kuid_t i_uid; kgid_t i_gid; // 文件的类型和权限S_IFREG普通文件/S_IFDIR目录/S_IFCHR字符设备等 umode_t i_mode; // 文件的标志位比如S_IMMUTABLE不可修改 unsigned int i_flags; // 文件的锁 struct rw_semaphore i_rwsem; // 自旋锁 spinlock_t i_lock; // 文件的大小单位字节 loff_t i_size; // 文件的块数量 blkcnt_t i_blocks; // 文件的块大小 unsigned int i_blkbits; // 时间戳 struct timespec64 i_atime; // 最后访问时间 struct timespec64 i_mtime; // 最后修改时间 struct timespec64 i_ctime; // 最后元数据修改时间 struct timespec64 i_btime; // 文件创建时间 // 操作函数集 // inode操作函数集元数据相关操作 const struct inode_operations *i_op; // 文件操作函数集IO相关操作打开文件时会赋值给file-f_op const struct file_operations *i_fop; // 页缓存相关 // 该文件的页缓存address_space管理文件的所有缓存页 struct address_space *i_mapping; struct address_space i_data; // 内嵌的address_space普通文件直接使用 // 设备相关设备文件的设备号 dev_t i_rdev; // 引用计数有多少个地方引用了这个inode为0时可以回收 refcount_t i_count; // 脏页标记inode的元数据是否被修改 unsigned long i_state; // 安全相关 struct security_hook_heads *i_security; // 私有数据具体文件系统使用比如ext4的ext4_inode_info void *i_private; } __randomize_layout;2.2.2 核心字段深度解析1.i_ino与i_nlinki_inoinode号在同一个文件系统内是唯一的是文件的唯一标识ls -i命令可以查看文件的inode号i_nlink硬链接计数每创建一个硬链接这个值加1每删除一个硬链接这个值减1当值为0时inode会被释放对应的磁盘空间会被回收。2.i_opinode操作函数集定义了文件元数据相关的操作接口是目录、文件创建/删除、硬链接/软链接、权限修改的核心入口核心函数包括struct inode_operations { // 创建一个新文件 int (*create)(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl); // 创建一个硬链接 int (*link)(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry); // 删除一个文件/硬链接 int (*unlink)(struct inode *dir, struct dentry *dentry); // 创建一个软链接 int (*symlink)(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname); // 创建一个目录 int (*mkdir)(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); // 删除一个目录 int (*rmdir)(struct inode *dir, struct dentry *dentry); // 重命名文件/目录 int (*rename)(struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); // 读取软链接的目标路径 const char *(*get_link)(struct dentry *dentry, struct inode *inode, struct delayed_call *done); // 修改文件的权限 int (*setattr)(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); // 获取文件的属性 int (*getattr)(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); };比如我们调用mkdir()创建目录最终会调用父目录inode的i_op-mkdir函数调用unlink()删除文件最终会调用父目录inode的i_op-unlink函数。1.i_fop文件操作函数集定义了文件打开后的IO操作接口进程调用open()打开文件时内核会把inode的i_fop赋值给file实例的f_op后续的read()/write()等操作都会调用file-f_op对应的函数。比如ext4的普通文件inodei_fop指向ext4_file_operations实现了ext4的read/write逻辑字符设备的inodei_fop指向设备驱动的file_operations实现设备的IO逻辑。2.i_mapping页缓存address_space指向该文件的页缓存地址空间管理文件的所有缓存页是VFS和页缓存层的桥梁。我们在内存管理章节已经详细拆解过address_space文件的read/write操作都会通过这个字段访问页缓存。3.i_mode文件类型与权限这个字段分为两部分高4位是文件类型低12位是文件权限。常见文件类型S_IFREG普通文件、S_IFDIR目录、S_IFLNK软链接、S_IFCHR字符设备、S_IFBLK块设备、S_IFIFO管道、S_IFSOCKsocket权限位低9位是UGO的读/写/执行权限高3位是SUID/SGID/Sticky位2.2.3 生命周期创建创建文件/目录/设备时内核调用super_block的s_op-alloc_inode函数分配inode实例初始化inode号、权限、所有者、时间戳等信息加入到super_block的inode哈希表和链表中使用文件被访问、打开、修改期间inode的引用计数i_count会增加保证inode不会被回收脏数据同步inode的元数据被修改后会被标记为脏加入到super_block的脏inode链表中内核的回写线程会定期把脏inode同步到磁盘释放当硬链接计数i_nlink为0且引用计数i_count为0时内核会调用s_op-evict_inode函数释放inode对应的磁盘空间回收inode实例。2.3 目录项struct dentrystruct dentryDirectory Entry目录项是路径解析的核心它描述了文件的名称、目录层级关系、与inode的关联。简单来说inode描述文件的「是什么」dentry描述文件的「在哪里」。dentry是内存中的对象不会持久化到磁盘磁盘上的目录项存储在目录文件的数据块中内核会把经常访问的dentry缓存起来形成目录项缓存dcache避免每次路径解析都要从磁盘读取目录项极大提升路径解析的性能。2.3.1 核心字段拆解struct dentry { // 目录项的引用计数 refcount_t d_count; // 目录项的标志位比如DCACHE_UNHASHED未哈希、DCACHE_MOUNTED挂载点 unsigned int d_flags; // 自旋锁 spinlock_t d_lock; // 目录项的名称 struct qstr d_name; // 父目录的dentry根目录的d_parent指向自己 struct dentry *d_parent; // 该dentry对应的inode为NULL表示负dentry struct inode *d_inode; // 该dentry所属的超级块 struct super_block *d_sb; // 子目录项的哈希表用于快速查找子目录/文件 struct hlist_head d_child; // 子目录项的链表用于遍历该目录下的所有子项 struct list_head d_subdirs; // 兄弟目录项的链表节点链接到父目录的d_subdirs链表 struct list_head d_child_list; // 目录项的LRU链表节点用于dcache回收 struct list_head d_lru; // 目录项操作函数集 const struct dentry_operations *d_op; // 挂载相关该dentry作为挂载点时指向对应的vfsmount实例 struct vfsmount *d_mount; // 目录项的完整路径名缓存 char *d_iname; } __randomize_layout;2.3.2 核心字段深度解析1.d_name与d_parentd_name目录项的名称是一个struct qstr结构体包含名称字符串、长度、哈希值哈希值用于快速查找子目录项d_parent指向父目录的dentry形成了完整的目录层级树根目录的d_parent指向自己。2.d_inode关联的inode指向该目录项对应的inode实例一个inode可以对应多个dentry硬链接每个硬链接对应一个dentry都指向同一个inode。负dentryNegative Dentry如果d_inode为NULL这个dentry就是负dentry对应一个不存在的文件。内核会缓存负dentry当用户频繁访问一个不存在的文件时不需要每次都遍历磁盘目录直接通过负dentry返回ENOENT错误极大提升性能。3.d_subdirs与d_childd_subdirs该目录下所有子目录项的链表头遍历目录时就是遍历这个链表d_child子目录项的哈希表头根据文件名的哈希值可以在O(1)时间复杂度内找到对应的子目录项是路径解析的核心优化。4.d_op目录项操作函数集定义了目录项的操作接口用于路径解析、哈希比较、dentry释放等核心函数包括struct dentry_operations { // 比较两个文件名是否相同用于路径解析 int (*d_compare)(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name); // 计算文件名的哈希值 int (*d_hash)(const struct dentry *dentry, struct qstr *name); // dentry被释放时调用 void (*d_release)(struct dentry *dentry); // 路径解析时判断dentry是否有效 int (*d_revalidate)(struct dentry *dentry, unsigned int flags); };比如网络文件系统NFS会实现自己的d_revalidate函数每次访问dentry时验证远端服务器上的文件是否存在保证缓存的一致性。2.3.3 生命周期与dcache创建路径解析时如果对应的dentry不在dcache中内核会创建新的dentry实例初始化名称、父目录、关联的inode加入到父目录的子项哈希表和链表中同时加入dcache的全局哈希表缓存dentry被释放后不会立即销毁而是加入到dcache的LRU链表中缓存起来后续再次访问时可以直接从LRU链表中取出复用避免重新创建回收当系统内存不足时内核会调用dcache的收缩函数回收LRU链表中最久未使用的dentry释放内存销毁当dentry的引用计数为0且被从LRU链表中移除时内核会销毁dentry实例释放内存。dcache的核心价值路径解析是文件操作中最频繁的操作之一比如打开/usr/bin/ls需要解析/、usr、bin、ls四个目录项如果每次都要从磁盘读取性能会极差。dcache把经常访问的dentry缓存到内存中路径解析的性能提升了几个数量级。2.4 打开的文件实例struct filestruct file是进程打开一个文件后内核创建的动态实例描述了进程与文件的交互上下文包括文件的打开模式、当前读写偏移、操作函数集、私有数据等。核心认知纠正inode是文件的静态描述全局唯一file是进程打开文件的动态实例每个进程每次打开同一个文件都会创建一个新的file实例。比如两个进程同时打开同一个文件会有两个独立的file实例各自有自己的读写偏移但是共享同一个inode。file实例是用户态文件描述符fd的底层载体用户态拿到的fd本质是进程文件描述符表中的索引对应内核中的一个file实例。2.4.1 核心字段拆解struct file { // 文件的引用计数 refcount_t f_count; // 文件的打开模式O_RDONLY/O_WRONLY/O_RDWR/O_APPEND/O_NONBLOCK等 unsigned int f_mode; // 文件的打开标志open()系统调用的flags参数 unsigned int f_flags; // 文件的当前读写偏移lseek()修改的就是这个值 loff_t f_pos; // 保护f_pos的自旋锁 spinlock_t f_pos_lock; // 该文件对应的inode struct inode *f_inode; // 该文件对应的路径信息包括dentry和vfsmount struct path f_path; #define f_dentry f_path.dentry #define f_vfsmnt f_path.mnt // 文件操作函数集open()时从inode-i_fop复制过来 const struct file_operations *f_op; // 所属的进程文件描述符表 struct files_struct *f_files; // 对应的文件描述符fd unsigned int f_fd; // 异步IO相关 struct fown_struct f_owner; // 事件通知相关poll/select/epoll使用 struct epoll_event f_epoll; // 私有数据文件系统/驱动使用比如ext4的私有数据、设备驱动的私有数据 void *private_data; // 安全相关 struct security_hook_heads *f_security; // 预读相关文件的预读状态 struct file_ra_state f_ra; } __randomize_layout;2.4.2 核心字段深度解析1.f_path文件的路径信息是一个struct path结构体包含两个字段dentry指向该文件的dentry实例mnt指向该文件所在的挂载实例struct vfsmount。这个字段是文件操作的核心通过它可以找到对应的dentry、inode、super_block完成所有的文件操作。2.f_op文件操作函数集这是IO操作的核心入口open()系统调用时内核会把inode的i_fop复制到这里后续的read()/write()/mmap()/fsync()等系统调用都会调用这个函数集对应的函数。核心函数包括struct file_operations { // 打开文件时调用 int (*open)(struct inode *inode, struct file *filp); // 读文件 ssize_t (*read)(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); // 写文件 ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); // 新的异步IO读写接口替代传统的read/write ssize_t (*read_iter)(struct kiocb *iocb, struct iov_iter *to); ssize_t (*write_iter)(struct kiocb *iocb, struct iov_iter *from); // 调整文件读写偏移 loff_t (*llseek)(struct file *filp, loff_t offset, int whence); // 内存映射 int (*mmap)(struct file *filp, struct vm_area_struct *vma); // 同步文件数据到磁盘 int (*fsync)(struct file *filp, loff_t start, loff_t end, int datasync); // 轮询文件事件poll/select/epoll使用 __poll_t (*poll)(struct file *filp, struct poll_table_struct *wait); // 控制文件ioctl系统调用 long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg); // 关闭文件时调用 int (*release)(struct inode *inode, struct file *filp); // 刷新文件的脏页 int (*flush)(struct file *filp, fl_owner_t id); };这是VFS最核心的操作函数集所有的文件IO操作最终都会落到这里不同的文件系统、设备驱动、socket都会实现自己的file_operations函数集接入VFS的统一接口。3f_pos文件当前读写偏移记录了文件的当前读写位置每次read()/write()操作后这个值会自动增加对应的字节数lseek()系统调用可以直接修改这个值实现随机读写。核心细节每个file实例有独立的f_pos多个进程同时打开同一个文件各自的读写偏移互不影响如果多个进程共享同一个file实例比如fork之后父子进程共享打开的文件则共享同一个f_pos读写会相互影响。4.f_flags与f_modef_flags保存了open()系统调用的flags参数比如O_NONBLOCK非阻塞IO、O_APPEND追加写、O_DIRECT直接IO、O_SYNC同步写内核会根据这些标志位调整IO操作的行为f_mode文件的访问模式比如FMODE_READ/FMODE_WRITE/FMODE_EXEC是权限检查的核心依据。5.private_data私有数据指针这是VFS为文件系统、设备驱动提供的私有数据指针用于存储打开文件的上下文信息比如设备驱动的硬件状态、文件系统的会话信息、socket的网络连接状态等VFS层不会修改这个指针完全由具体的实现管理。2.4.3 生命周期创建open()系统调用时内核完成路径解析、权限检查后分配file实例初始化f_path、f_op、f_flags、f_mode、f_pos等字段调用f_op-open函数完成文件的打开操作然后把file实例加入到进程的文件描述符表中返回对应的fd给用户态使用进程通过fd调用read()/write()/lseek()/mmap()等系统调用时内核通过fd找到对应的file实例调用f_op对应的函数完成IO操作引用计数管理每次dup()、fork()都会增加file实例的引用计数f_count每次close()都会减少引用计数销毁当引用计数f_count为0时内核调用f_op-release函数完成文件的关闭操作释放file实例从进程的文件描述符表中移除。2.5 四大核心对象的关联关系我们用一个简单的例子梳理四大核心对象的关联关系进程打开/data/test.txt文件super_block对应/data分区挂载的ext4文件系统实例是整个文件系统的根inode对应test.txt文件的元数据inode号唯一存储了文件的权限、大小、块指针等信息dentry对应test.txt的目录项名称是test.txt父目录是data的dentry指向test.txt的inodefile进程打开文件时创建的实例指向test.txt的dentry和inode有独立的读写偏移f_op指向ext4的文件操作函数集对应进程的fd。关联关系的核心链路进程fd → struct file → struct dentry → struct inode → struct super_block → 具体文件系统实现
Linux内核学习轨迹第六部:VFS四大核心对象:super_block/inode/dentry/file(第二节)
发布时间:2026/6/9 16:22:36
2. VFS四大核心对象super_block/inode/dentry/file全解析四大核心对象是VFS的基石它们分别描述了文件系统的不同维度从静态的文件系统结构到动态的进程文件交互形成了完整的文件管理体系。本章节基于Linux 6.6内核源码定义在include/linux/fs.h完整拆解每个对象的核心字段、生命周期、相互关联关系。2.1 超级块struct super_blockstruct super_block是一个已挂载的文件系统实例的全局描述符对应磁盘上的文件系统超级块存储了文件系统的类型、块大小、魔数、根目录、操作函数集、挂载参数等全局信息是整个文件系统的「根」。每个已挂载的文件系统在内核中都有且仅有一个super_block实例比如系统挂载了/ext4、/bootvfat、/procprocfs就会有三个对应的super_block实例。2.1.1 核心字段拆解struct super_block { // 超级块的链表节点所有已挂载的super_block都链接到全局super_blocks链表 struct list_head s_list; // 设备标识符块设备的设备号伪文件系统为0 dev_t s_dev; // 文件系统的块大小单位字节 unsigned long s_blocksize; // 块大小的位掩码用于快速计算 unsigned char s_blocksize_bits; // 文件系统的魔数用于识别文件系统类型比如ext4的魔数是0xEF53 unsigned long s_magic; // 超级块的标志位比如MS_RDONLY只读挂载、MS_NOSUID禁止suid unsigned long s_flags; // 挂载的次数用于共享挂载 int s_count; // 超级块的读写信号量保护超级块的修改 struct rw_semaphore s_umount; // 超级块的自旋锁 spinlock_t s_lock; // 超级块操作函数集文件系统级别的操作接口 const struct super_operations *s_op; // 该文件系统的类型描述符对应file_system_type结构体 struct file_system_type *s_type; // 该超级块对应的挂载实例 struct vfsmount *s_vfsmnt; // 文件系统的根目录dentry struct dentry *s_root; // inode相关字段 // 该文件系统的所有inode的哈希表 struct hlist_head *s_inodes; // 该文件系统的inode数量统计 atomic_long_t s_inodes_count; // 正在使用的inode数量 atomic_long_t s_active_inodes; // 脏inode链表需要同步到磁盘的inode struct list_head s_inodes_dirty; // inode的LRU链表用于inode缓存回收 struct list_head s_inodes_lru; // 目录项缓存相关字段 // 目录项缓存的收缩函数 int (*s_shrink)(struct super_block *sb, struct shrink_control *sc); // 文件系统的最大链接数 unsigned int s_max_links; // 时间戳的粒度比如ext4是1纳秒 u32 s_time_gran; // 具体文件系统的私有数据比如ext4的ext4_sb_info void *s_fs_info; // 挂载命名空间相关 struct user_namespace *s_user_ns; // 安全相关的钩子函数 struct security_hook_heads *s_security; } __randomize_layout;2.1.2 核心字段深度解析1.s_op超级块操作函数集定义了文件系统级别的操作接口具体文件系统必须实现这套接口核心函数包括struct super_operations { // 分配一个新的inode struct inode *(*alloc_inode)(struct super_block *sb); // 释放inode void (*destroy_inode)(struct inode *inode); // 把inode的元数据同步到磁盘 void (*write_inode)(struct inode *inode, struct writeback_control *wbc); // 丢弃inode void (*evict_inode)(struct inode *inode); // 把文件系统的所有脏数据同步到磁盘 int (*sync_fs)(struct super_block *sb, int wait); // 冻结文件系统用于快照 int (*freeze_super)(struct super_block *sb); // 解冻文件系统 int (*thaw_super)(struct super_block *sb); // umount时调用释放超级块 void (*put_super)(struct super_block *sb); // 统计文件系统的磁盘使用情况 int (*statfs)(struct dentry *dentry, struct kstatfs *buf); };比如ext4文件系统会实现自己的ext4_sops超级块操作函数集处理ext4格式的inode分配、同步、磁盘统计等逻辑。2.s_root文件系统的根目录dentry指向该文件系统根目录的dentry实例是路径解析的起点挂载文件系统时内核会读取文件系统的根目录inode创建对应的dentry赋值给s_root。3.s_fs_info文件系统私有数据这是VFS为具体文件系统提供的私有数据指针具体文件系统可以把自己的超级块私有信息存储在这里比如ext4的struct ext4_sb_info、xfs的struct xfs_mountVFS层不会修改这个指针完全由具体文件系统管理。4.s_inodes/s_inodes_lru/s_inodes_dirty管理该文件系统的所有inode包括inode的哈希表用于快速查找inode、LRU链表用于inode缓存回收、脏inode链表用于同步到磁盘是inode生命周期管理的核心结构。2.1.3 生命周期创建mount系统调用挂载文件系统时内核调用具体文件系统的fill_super函数读取磁盘上的超级块创建并初始化super_block实例加入全局super_blocks链表使用文件系统挂载期间super_block实例一直存在用于管理该文件系统的所有inode、dentry、挂载参数销毁umount系统调用卸载文件系统时内核会等待所有的inode释放调用super_operations的put_super函数释放super_block实例从全局链表中移除。2.2 索引节点struct inodestruct inodeIndex Node索引节点是VFS中最核心的对象它描述了一个文件的元数据和物理存储信息包括文件的权限、所有者、大小、创建/修改/访问时间、块指针、扩展属性等。核心认知纠正inode是文件的唯一标识和文件名无关。文件名只是dentry中的一个字符串一个inode可以对应多个文件名硬链接每个硬链接对应一个dentry指向同一个inode。每个文件包括目录、设备文件、管道、socket在内核中都有且仅有一个inode实例无论是磁盘文件系统的inode持久化到磁盘还是内存文件系统的inode仅存在于内存都由VFS统一管理。2.2.1 核心字段拆解struct inode { // inode的哈希表节点用于快速查找 struct hlist_node i_hash; // inode的链表节点用于链接到super_block的inode链表 struct list_head i_list; // inode的LRU链表节点用于缓存回收 struct list_head i_lru; // 脏inode链表节点用于同步到磁盘 struct list_head i_io_list; // 该inode所属的超级块 struct super_block *i_sb; // inode号文件系统内唯一是文件的唯一标识 ino_t i_ino; // 硬链接计数有多少个硬链接指向这个inode为0时释放inode unsigned int i_nlink; // 文件的UID/GID kuid_t i_uid; kgid_t i_gid; // 文件的类型和权限S_IFREG普通文件/S_IFDIR目录/S_IFCHR字符设备等 umode_t i_mode; // 文件的标志位比如S_IMMUTABLE不可修改 unsigned int i_flags; // 文件的锁 struct rw_semaphore i_rwsem; // 自旋锁 spinlock_t i_lock; // 文件的大小单位字节 loff_t i_size; // 文件的块数量 blkcnt_t i_blocks; // 文件的块大小 unsigned int i_blkbits; // 时间戳 struct timespec64 i_atime; // 最后访问时间 struct timespec64 i_mtime; // 最后修改时间 struct timespec64 i_ctime; // 最后元数据修改时间 struct timespec64 i_btime; // 文件创建时间 // 操作函数集 // inode操作函数集元数据相关操作 const struct inode_operations *i_op; // 文件操作函数集IO相关操作打开文件时会赋值给file-f_op const struct file_operations *i_fop; // 页缓存相关 // 该文件的页缓存address_space管理文件的所有缓存页 struct address_space *i_mapping; struct address_space i_data; // 内嵌的address_space普通文件直接使用 // 设备相关设备文件的设备号 dev_t i_rdev; // 引用计数有多少个地方引用了这个inode为0时可以回收 refcount_t i_count; // 脏页标记inode的元数据是否被修改 unsigned long i_state; // 安全相关 struct security_hook_heads *i_security; // 私有数据具体文件系统使用比如ext4的ext4_inode_info void *i_private; } __randomize_layout;2.2.2 核心字段深度解析1.i_ino与i_nlinki_inoinode号在同一个文件系统内是唯一的是文件的唯一标识ls -i命令可以查看文件的inode号i_nlink硬链接计数每创建一个硬链接这个值加1每删除一个硬链接这个值减1当值为0时inode会被释放对应的磁盘空间会被回收。2.i_opinode操作函数集定义了文件元数据相关的操作接口是目录、文件创建/删除、硬链接/软链接、权限修改的核心入口核心函数包括struct inode_operations { // 创建一个新文件 int (*create)(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl); // 创建一个硬链接 int (*link)(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry); // 删除一个文件/硬链接 int (*unlink)(struct inode *dir, struct dentry *dentry); // 创建一个软链接 int (*symlink)(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname); // 创建一个目录 int (*mkdir)(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); // 删除一个目录 int (*rmdir)(struct inode *dir, struct dentry *dentry); // 重命名文件/目录 int (*rename)(struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); // 读取软链接的目标路径 const char *(*get_link)(struct dentry *dentry, struct inode *inode, struct delayed_call *done); // 修改文件的权限 int (*setattr)(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); // 获取文件的属性 int (*getattr)(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); };比如我们调用mkdir()创建目录最终会调用父目录inode的i_op-mkdir函数调用unlink()删除文件最终会调用父目录inode的i_op-unlink函数。1.i_fop文件操作函数集定义了文件打开后的IO操作接口进程调用open()打开文件时内核会把inode的i_fop赋值给file实例的f_op后续的read()/write()等操作都会调用file-f_op对应的函数。比如ext4的普通文件inodei_fop指向ext4_file_operations实现了ext4的read/write逻辑字符设备的inodei_fop指向设备驱动的file_operations实现设备的IO逻辑。2.i_mapping页缓存address_space指向该文件的页缓存地址空间管理文件的所有缓存页是VFS和页缓存层的桥梁。我们在内存管理章节已经详细拆解过address_space文件的read/write操作都会通过这个字段访问页缓存。3.i_mode文件类型与权限这个字段分为两部分高4位是文件类型低12位是文件权限。常见文件类型S_IFREG普通文件、S_IFDIR目录、S_IFLNK软链接、S_IFCHR字符设备、S_IFBLK块设备、S_IFIFO管道、S_IFSOCKsocket权限位低9位是UGO的读/写/执行权限高3位是SUID/SGID/Sticky位2.2.3 生命周期创建创建文件/目录/设备时内核调用super_block的s_op-alloc_inode函数分配inode实例初始化inode号、权限、所有者、时间戳等信息加入到super_block的inode哈希表和链表中使用文件被访问、打开、修改期间inode的引用计数i_count会增加保证inode不会被回收脏数据同步inode的元数据被修改后会被标记为脏加入到super_block的脏inode链表中内核的回写线程会定期把脏inode同步到磁盘释放当硬链接计数i_nlink为0且引用计数i_count为0时内核会调用s_op-evict_inode函数释放inode对应的磁盘空间回收inode实例。2.3 目录项struct dentrystruct dentryDirectory Entry目录项是路径解析的核心它描述了文件的名称、目录层级关系、与inode的关联。简单来说inode描述文件的「是什么」dentry描述文件的「在哪里」。dentry是内存中的对象不会持久化到磁盘磁盘上的目录项存储在目录文件的数据块中内核会把经常访问的dentry缓存起来形成目录项缓存dcache避免每次路径解析都要从磁盘读取目录项极大提升路径解析的性能。2.3.1 核心字段拆解struct dentry { // 目录项的引用计数 refcount_t d_count; // 目录项的标志位比如DCACHE_UNHASHED未哈希、DCACHE_MOUNTED挂载点 unsigned int d_flags; // 自旋锁 spinlock_t d_lock; // 目录项的名称 struct qstr d_name; // 父目录的dentry根目录的d_parent指向自己 struct dentry *d_parent; // 该dentry对应的inode为NULL表示负dentry struct inode *d_inode; // 该dentry所属的超级块 struct super_block *d_sb; // 子目录项的哈希表用于快速查找子目录/文件 struct hlist_head d_child; // 子目录项的链表用于遍历该目录下的所有子项 struct list_head d_subdirs; // 兄弟目录项的链表节点链接到父目录的d_subdirs链表 struct list_head d_child_list; // 目录项的LRU链表节点用于dcache回收 struct list_head d_lru; // 目录项操作函数集 const struct dentry_operations *d_op; // 挂载相关该dentry作为挂载点时指向对应的vfsmount实例 struct vfsmount *d_mount; // 目录项的完整路径名缓存 char *d_iname; } __randomize_layout;2.3.2 核心字段深度解析1.d_name与d_parentd_name目录项的名称是一个struct qstr结构体包含名称字符串、长度、哈希值哈希值用于快速查找子目录项d_parent指向父目录的dentry形成了完整的目录层级树根目录的d_parent指向自己。2.d_inode关联的inode指向该目录项对应的inode实例一个inode可以对应多个dentry硬链接每个硬链接对应一个dentry都指向同一个inode。负dentryNegative Dentry如果d_inode为NULL这个dentry就是负dentry对应一个不存在的文件。内核会缓存负dentry当用户频繁访问一个不存在的文件时不需要每次都遍历磁盘目录直接通过负dentry返回ENOENT错误极大提升性能。3.d_subdirs与d_childd_subdirs该目录下所有子目录项的链表头遍历目录时就是遍历这个链表d_child子目录项的哈希表头根据文件名的哈希值可以在O(1)时间复杂度内找到对应的子目录项是路径解析的核心优化。4.d_op目录项操作函数集定义了目录项的操作接口用于路径解析、哈希比较、dentry释放等核心函数包括struct dentry_operations { // 比较两个文件名是否相同用于路径解析 int (*d_compare)(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name); // 计算文件名的哈希值 int (*d_hash)(const struct dentry *dentry, struct qstr *name); // dentry被释放时调用 void (*d_release)(struct dentry *dentry); // 路径解析时判断dentry是否有效 int (*d_revalidate)(struct dentry *dentry, unsigned int flags); };比如网络文件系统NFS会实现自己的d_revalidate函数每次访问dentry时验证远端服务器上的文件是否存在保证缓存的一致性。2.3.3 生命周期与dcache创建路径解析时如果对应的dentry不在dcache中内核会创建新的dentry实例初始化名称、父目录、关联的inode加入到父目录的子项哈希表和链表中同时加入dcache的全局哈希表缓存dentry被释放后不会立即销毁而是加入到dcache的LRU链表中缓存起来后续再次访问时可以直接从LRU链表中取出复用避免重新创建回收当系统内存不足时内核会调用dcache的收缩函数回收LRU链表中最久未使用的dentry释放内存销毁当dentry的引用计数为0且被从LRU链表中移除时内核会销毁dentry实例释放内存。dcache的核心价值路径解析是文件操作中最频繁的操作之一比如打开/usr/bin/ls需要解析/、usr、bin、ls四个目录项如果每次都要从磁盘读取性能会极差。dcache把经常访问的dentry缓存到内存中路径解析的性能提升了几个数量级。2.4 打开的文件实例struct filestruct file是进程打开一个文件后内核创建的动态实例描述了进程与文件的交互上下文包括文件的打开模式、当前读写偏移、操作函数集、私有数据等。核心认知纠正inode是文件的静态描述全局唯一file是进程打开文件的动态实例每个进程每次打开同一个文件都会创建一个新的file实例。比如两个进程同时打开同一个文件会有两个独立的file实例各自有自己的读写偏移但是共享同一个inode。file实例是用户态文件描述符fd的底层载体用户态拿到的fd本质是进程文件描述符表中的索引对应内核中的一个file实例。2.4.1 核心字段拆解struct file { // 文件的引用计数 refcount_t f_count; // 文件的打开模式O_RDONLY/O_WRONLY/O_RDWR/O_APPEND/O_NONBLOCK等 unsigned int f_mode; // 文件的打开标志open()系统调用的flags参数 unsigned int f_flags; // 文件的当前读写偏移lseek()修改的就是这个值 loff_t f_pos; // 保护f_pos的自旋锁 spinlock_t f_pos_lock; // 该文件对应的inode struct inode *f_inode; // 该文件对应的路径信息包括dentry和vfsmount struct path f_path; #define f_dentry f_path.dentry #define f_vfsmnt f_path.mnt // 文件操作函数集open()时从inode-i_fop复制过来 const struct file_operations *f_op; // 所属的进程文件描述符表 struct files_struct *f_files; // 对应的文件描述符fd unsigned int f_fd; // 异步IO相关 struct fown_struct f_owner; // 事件通知相关poll/select/epoll使用 struct epoll_event f_epoll; // 私有数据文件系统/驱动使用比如ext4的私有数据、设备驱动的私有数据 void *private_data; // 安全相关 struct security_hook_heads *f_security; // 预读相关文件的预读状态 struct file_ra_state f_ra; } __randomize_layout;2.4.2 核心字段深度解析1.f_path文件的路径信息是一个struct path结构体包含两个字段dentry指向该文件的dentry实例mnt指向该文件所在的挂载实例struct vfsmount。这个字段是文件操作的核心通过它可以找到对应的dentry、inode、super_block完成所有的文件操作。2.f_op文件操作函数集这是IO操作的核心入口open()系统调用时内核会把inode的i_fop复制到这里后续的read()/write()/mmap()/fsync()等系统调用都会调用这个函数集对应的函数。核心函数包括struct file_operations { // 打开文件时调用 int (*open)(struct inode *inode, struct file *filp); // 读文件 ssize_t (*read)(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); // 写文件 ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); // 新的异步IO读写接口替代传统的read/write ssize_t (*read_iter)(struct kiocb *iocb, struct iov_iter *to); ssize_t (*write_iter)(struct kiocb *iocb, struct iov_iter *from); // 调整文件读写偏移 loff_t (*llseek)(struct file *filp, loff_t offset, int whence); // 内存映射 int (*mmap)(struct file *filp, struct vm_area_struct *vma); // 同步文件数据到磁盘 int (*fsync)(struct file *filp, loff_t start, loff_t end, int datasync); // 轮询文件事件poll/select/epoll使用 __poll_t (*poll)(struct file *filp, struct poll_table_struct *wait); // 控制文件ioctl系统调用 long (*unlocked_ioctl)(struct file *filp, unsigned int cmd, unsigned long arg); // 关闭文件时调用 int (*release)(struct inode *inode, struct file *filp); // 刷新文件的脏页 int (*flush)(struct file *filp, fl_owner_t id); };这是VFS最核心的操作函数集所有的文件IO操作最终都会落到这里不同的文件系统、设备驱动、socket都会实现自己的file_operations函数集接入VFS的统一接口。3f_pos文件当前读写偏移记录了文件的当前读写位置每次read()/write()操作后这个值会自动增加对应的字节数lseek()系统调用可以直接修改这个值实现随机读写。核心细节每个file实例有独立的f_pos多个进程同时打开同一个文件各自的读写偏移互不影响如果多个进程共享同一个file实例比如fork之后父子进程共享打开的文件则共享同一个f_pos读写会相互影响。4.f_flags与f_modef_flags保存了open()系统调用的flags参数比如O_NONBLOCK非阻塞IO、O_APPEND追加写、O_DIRECT直接IO、O_SYNC同步写内核会根据这些标志位调整IO操作的行为f_mode文件的访问模式比如FMODE_READ/FMODE_WRITE/FMODE_EXEC是权限检查的核心依据。5.private_data私有数据指针这是VFS为文件系统、设备驱动提供的私有数据指针用于存储打开文件的上下文信息比如设备驱动的硬件状态、文件系统的会话信息、socket的网络连接状态等VFS层不会修改这个指针完全由具体的实现管理。2.4.3 生命周期创建open()系统调用时内核完成路径解析、权限检查后分配file实例初始化f_path、f_op、f_flags、f_mode、f_pos等字段调用f_op-open函数完成文件的打开操作然后把file实例加入到进程的文件描述符表中返回对应的fd给用户态使用进程通过fd调用read()/write()/lseek()/mmap()等系统调用时内核通过fd找到对应的file实例调用f_op对应的函数完成IO操作引用计数管理每次dup()、fork()都会增加file实例的引用计数f_count每次close()都会减少引用计数销毁当引用计数f_count为0时内核调用f_op-release函数完成文件的关闭操作释放file实例从进程的文件描述符表中移除。2.5 四大核心对象的关联关系我们用一个简单的例子梳理四大核心对象的关联关系进程打开/data/test.txt文件super_block对应/data分区挂载的ext4文件系统实例是整个文件系统的根inode对应test.txt文件的元数据inode号唯一存储了文件的权限、大小、块指针等信息dentry对应test.txt的目录项名称是test.txt父目录是data的dentry指向test.txt的inodefile进程打开文件时创建的实例指向test.txt的dentry和inode有独立的读写偏移f_op指向ext4的文件操作函数集对应进程的fd。关联关系的核心链路进程fd → struct file → struct dentry → struct inode → struct super_block → 具体文件系统实现