2. VFS四大核心对象:super_block/inode/dentry/file全解析
四大核心对象是VFS的基石,它们分别描述了文件系统的不同维度,从静态的文件系统结构,到动态的进程文件交互,形成了完整的文件管理体系。本章节基于Linux 6.6内核源码(定义在include/linux/fs.h),完整拆解每个对象的核心字段、生命周期、相互关联关系。
2.1 超级块:struct super_block
struct super_block是一个已挂载的文件系统实例的全局描述符,对应磁盘上的文件系统超级块,存储了文件系统的类型、块大小、魔数、根目录、操作函数集、挂载参数等全局信息,是整个文件系统的「根」。
每个已挂载的文件系统,在内核中都有且仅有一个super_block实例,比如系统挂载了/(ext4)、/boot(vfat)、/proc(procfs),就会有三个对应的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);
};
2.s_root:文件系统的根目录dentry
3.s_fs_info:文件系统私有数据
4.s_inodes/s_inodes_lru/s_inodes_dirty
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 inode
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_nlink
- i_ino:inode号,在同一个文件系统内是唯一的,是文件的唯一标识,ls -i命令可以查看文件的inode号;
- i_nlink:硬链接计数,每创建一个硬链接,这个值加1,每删除一个硬链接,这个值减1,当值为0时,inode会被释放,对应的磁盘空间会被回收。
2.i_op:inode操作函数集
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);
};
1.i_fop:文件操作函数集
2.i_mapping:页缓存address_space
3.i_mode:文件类型与权限
- 常见文件类型:S_IFREG(普通文件)、S_IFDIR(目录)、S_IFLNK(软链接)、S_IFCHR(字符设备)、S_IFBLK(块设备)、S_IFIFO(管道)、S_IFSOCK(socket);
- 权限位:低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 dentry
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_parent
- d_name:目录项的名称,是一个struct qstr结构体,包含名称字符串、长度、哈希值,哈希值用于快速查找子目录项;
- d_parent:指向父目录的dentry,形成了完整的目录层级树,根目录的d_parent指向自己。
2.d_inode:关联的inode
- 负dentry(Negative Dentry):如果d_inode为NULL,这个dentry就是负dentry,对应一个不存在的文件。内核会缓存负dentry,当用户频繁访问一个不存在的文件时,不需要每次都遍历磁盘目录,直接通过负dentry返回ENOENT错误,极大提升性能。
3.d_subdirs与d_child
- d_subdirs:该目录下所有子目录项的链表头,遍历目录时,就是遍历这个链表;
- d_child:子目录项的哈希表头,根据文件名的哈希值,可以在O(1)时间复杂度内找到对应的子目录项,是路径解析的核心优化。
4.d_op:目录项操作函数集
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);
};
2.3.3 生命周期与dcache
- 创建:路径解析时,如果对应的dentry不在dcache中,内核会创建新的dentry实例,初始化名称、父目录、关联的inode,加入到父目录的子项哈希表和链表中,同时加入dcache的全局哈希表;
- 缓存:dentry被释放后,不会立即销毁,而是加入到dcache的LRU链表中,缓存起来,后续再次访问时,可以直接从LRU链表中取出复用,避免重新创建;
- 回收:当系统内存不足时,内核会调用dcache的收缩函数,回收LRU链表中最久未使用的dentry,释放内存;
- 销毁:当dentry的引用计数为0,且被从LRU链表中移除时,内核会销毁dentry实例,释放内存。
2.4 打开的文件实例:struct 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:文件的路径信息
- dentry:指向该文件的dentry实例;
- mnt:指向该文件所在的挂载实例struct vfsmount。这个字段是文件操作的核心,通过它可以找到对应的dentry、inode、super_block,完成所有的文件操作。
2.f_op:文件操作函数集
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);
};
3f_pos:文件当前读写偏移
- 核心细节:每个file实例有独立的f_pos,多个进程同时打开同一个文件,各自的读写偏移互不影响;如果多个进程共享同一个file实例(比如fork之后父子进程共享打开的文件),则共享同一个f_pos,读写会相互影响。
4.f_flags与f_mode
- f_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:私有数据指针
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 四大核心对象的关联关系
- super_block:对应/data分区挂载的ext4文件系统实例,是整个文件系统的根;
- inode:对应test.txt文件的元数据,inode号唯一,存储了文件的权限、大小、块指针等信息;
- dentry:对应test.txt的目录项,名称是test.txt,父目录是data的dentry,指向test.txt的inode;
- file:进程打开文件时创建的实例,指向test.txt的dentry和inode,有独立的读写偏移,f_op指向ext4的文件操作函数集,对应进程的fd。
进程fd → struct file → struct dentry → struct inode → struct super_block → 具体文件系统实现
4070



被折叠的 条评论
为什么被折叠?



