Linux VFS深度解析:架构、数据结构与文件系统注册
1. VFS概述
Virtual File System(虚拟文件系统,VFS)是Linux内核中的一个软件层,它为用户空间程序提供了统一的文件系统接口。VFS的核心作用是抽象底层文件系统的差异,使得应用程序可以使用相同的系统调用(如open、read、write)来操作不同类型的文件系统(ext4、XFS、Btrfs等)。
1.1 VFS的设计思想
VFS采用了面向对象的设计思想,虽然Linux内核使用C语言编写,但通过结构体和函数指针实现了类似面向对象的机制:
- 抽象基类: VFS定义了通用的数据结构(如inode、dentry、file)
- 多态性: 通过函数指针表(operations)实现不同文件系统的具体操作
- 统一接口: 对上层应用提供标准的POSIX接口
1.2 VFS架构层次
+------------------+
| 用户空间应用 |
+------------------+
|
| (系统调用: open, read, write, close)
v
+------------------+
| 系统调用层 |
+------------------+
|
v
+------------------+
| VFS 层 | <-- 核心抽象层
+------------------+
|
v
+------------------+
| 具体文件系统 |
| (ext4/XFS/Btrfs)|
+------------------+
|
v
+------------------+
| 块设备层/驱动 |
+------------------+
2. VFS核心数据结构
VFS依赖四个主要的数据结构来描述文件系统的层次结构,每个结构都包含一个操作函数表(operations table)。
2.1 超级块 (super_block)
超级块是文件系统的元信息容器,代表一个已挂载的文件系统实例。
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; // 文件系统类型
const struct super_operations *s_op; // 超级块操作函数表
unsigned long s_flags; // 挂载标志
unsigned long s_magic; // 文件系统魔数
struct dentry *s_root; // 根目录项
int s_count; // 引用计数
atomic_t s_active; // 活跃计数
void *s_fs_info; // 文件系统私有信息
// ... 更多字段
};
关键操作函数表:
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*dirty_inode)(struct inode *, int flags);
int (*write_inode)(struct inode *, struct writeback_control *wbc);
void (*put_super)(struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*statfs)(struct dentry *, struct kstatfs *);
int (*remount_fs)(struct super_block *, int *, char *);
// ... 更多操作
};
2.2 索引节点 (inode)
inode存储文件的元数据信息,每个文件或目录都对应一个inode。
struct inode {
umode_t i_mode; // 访问权限
kuid_t i_uid; // 所有者ID
kgid_t i_gid; // 组ID
unsigned long i_ino; // inode编号
loff_t i_size; // 文件大小
struct timespec64 i_atime; // 访问时间
struct timespec64 i_mtime; // 修改时间
struct timespec64 i_ctime; // 状态改变时间
blkcnt_t i_blocks; // 块数
const struct inode_operations *i_op; // inode操作函数表
const struct file_operations *i_fop; // 文件操作函数表
struct super_block *i_sb; // 所属超级块
struct address_space *i_mapping; // 页缓存映射
union {
struct pipe_inode_info *i_pipe; // 管道信息
struct block_device *i_bdev; // 块设备
struct cdev *i_cdev; // 字符设备
};
// ... 更多字段
};
关键操作函数表:
struct inode_operations {
int (*create)(struct inode *, struct dentry *, umode_t, bool);
struct dentry *(*lookup)(struct inode *, struct dentry *, unsigned int);
int (*link)(struct dentry *, struct inode *, struct dentry *);
int (*unlink)(struct inode *, struct dentry *);
int (*mkdir)(struct inode *, struct dentry *, umode_t);
int (*rmdir)(struct inode *, struct dentry *);
int (*rename)(struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr)(struct dentry *, struct iattr *);
int (*getattr)(const struct path *, struct kstat *, u32, unsigned int);
// ... 更多操作
};
2.3 目录项 (dentry)
dentry将文件名与inode关联起来,形成目录树结构。dentry主要存在于内存中,用于加速路径查找。
struct dentry {
unsigned int d_flags; // 目录项标志
struct dentry *d_parent; // 父目录项
struct qstr d_name; // 目录项名称
struct inode *d_inode; // 关联的inode
const struct dentry_operations *d_op; // 目录项操作
struct super_block *d_sb; // 所属超级块
void *d_fsdata; // 文件系统私有数据
struct list_head d_child; // 父目录的子目录项链表
struct list_head d_subdirs; // 当前目录的子目录项链表
struct hlist_node d_alias; // inode的别名链表
// ... 更多字段
};
dentry状态:
- 正数状态: d_inode指向有效inode,表示文件存在
- 负数状态: d_inode为NULL,表示文件不存在(缓存的查找失败结果)
2.4 文件对象 (file)
file结构代表进程打开的文件,包含文件位置、访问模式等运行时信息。
struct file {
struct path f_path; // 文件路径(包含dentry和vfsmount)
struct inode *f_inode; // 关联的inode
const struct file_operations *f_op; // 文件操作函数表
atomic_long_t f_count; // 引用计数
unsigned int f_flags; // 打开标志(O_RDONLY, O_WRONLY等)
fmode_t f_mode; // 访问模式
loff_t f_pos; // 文件位置指针
struct fown_struct f_owner; // 文件所有者
void *private_data; // 私有数据(驱动使用)
struct address_space *f_mapping; // 页缓存映射
// ... 更多字段
};
关键操作函数表:
struct file_operations {
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 (*open)(struct inode *, struct file *);
int (*release)(struct inode *, struct file *);
int (*fsync)(struct file *, loff_t, loff_t, int datasync);
int (*mmap)(struct file *, struct vm_area_struct *);
// ... 更多操作
};
2.5 数据结构关系图
进程 (task_struct)
|
|-- fs_struct (文件系统上下文)
| |-- root (根目录 dentry)
| |-- pwd (当前工作目录 dentry)
|
|-- files_struct (打开文件表)
|-- fd_array[] (文件描述符数组)
|
v
file (文件对象)
|
|-- f_path.dentry --> dentry (目录项)
| |
| |-- d_inode --> inode (索引节点)
| |
|-- f_op (file_operations) |-- i_sb --> super_block
|
|-- i_op (inode_operations)
|
|-- i_fop (file_operations)
3. 注册新文件系统
注册文件系统需要定义file_system_type结构并调用register_filesystem()。
3.1 file_system_type结构
struct file_system_type {
const char *name; // 文件系统名称
int fs_flags; // 文件系统标志
// 挂载文件系统的回调函数
struct dentry *(*mount)(struct file_system_type *, int,
const char *, void *);
void (*kill_sb)(struct super_block *); // 卸载文件系统
struct module *owner; // 所属模块
struct file_system_type *next; // 链表指针
struct hlist_head fs_supers; // 该类型的所有超级块
};
3.2 注册流程示例
以下是一个简化的文件系统注册示例:
#include <linux/fs.h>
#include <linux/module.h>
// 1. 定义超级块操作
static const struct super_operations myfs_super_ops = {
.alloc_inode = myfs_alloc_inode,
.destroy_inode = myfs_destroy_inode,
.write_inode = myfs_write_inode,
.put_super = myfs_put_super,
.statfs = myfs_statfs,
.sync_fs = myfs_sync_fs,
};
// 2. 定义inode操作
static const struct inode_operations myfs_inode_ops = {
.lookup = myfs_lookup,
.create = myfs_create,
.mkdir = myfs_mkdir,
.rmdir = myfs_rmdir,
.unlink = myfs_unlink,
};
// 3. 定义文件操作
static const struct file_operations myfs_file_ops = {
.read_iter = myfs_read_iter,
.write_iter = myfs_write_iter,
.open = myfs_open,
.release = myfs_release,
.fsync = myfs_fsync,
.llseek = generic_file_llseek,
};
// 4. 填充超级块
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *root_inode;
struct dentry *root_dentry;
// 设置超级块基本信息
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = MYFS_MAGIC;
sb->s_op = &myfs_super_ops;
sb->s_maxbytes = MAX_LFS_FILESIZE;
// 创建根inode
root_inode = new_inode(sb);
if (!root_inode)
return -ENOMEM;
root_inode->i_ino = 1;
root_inode->i_mode = S_IFDIR | 0755;
root_inode->i_op = &myfs_inode_ops;
root_inode->i_fop = &simple_dir_operations;
set_nlink(root_inode, 2);
// 创建根dentry
root_dentry = d_make_root(root_inode);
if (!root_dentry) {
iput(root_inode);
return -ENOMEM;
}
sb->s_root = root_dentry;
return 0;
}
// 5. 挂载函数
static struct dentry *myfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name,
void *data)
{
// 对于基于块设备的文件系统,使用mount_bdev
return mount_bdev(fs_type, flags, dev_name, data, myfs_fill_super);
// 对于伪文件系统(如procfs),使用mount_nodev
// return mount_nodev(fs_type, flags, data, myfs_fill_super);
}
// 6. 定义file_system_type
static struct file_system_type myfs_type = {
.owner = THIS_MODULE,
.name = "myfs",
.mount = myfs_mount,
.kill_sb = kill_block_super, // 或kill_litter_super
.fs_flags = FS_REQUIRES_DEV,
};
// 7. 模块初始化和清理
static int __init myfs_init(void)
{
int ret;
// 注册文件系统
ret = register_filesystem(&myfs_type);
if (ret) {
pr_err("Failed to register myfs\n");
return ret;
}
pr_info("myfs registered successfully\n");
return 0;
}
static void __exit myfs_exit(void)
{
// 注销文件系统
unregister_filesystem(&myfs_type);
pr_info("myfs unregistered\n");
}
module_init(myfs_init);
module_exit(myfs_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("My Custom File System");
3.3 注册过程详解
- register_filesystem(): 将
file_system_type添加到全局文件系统类型链表 - mount操作: 用户执行
mount命令时,内核调用对应的mount回调 - fill_super: 创建超级块对象,初始化根inode和根dentry
- 挂载点连接: VFS将新文件系统的根dentry连接到挂载点
4. 读写操作的传递路径
4.1 read系统调用的完整路径
用户空间: read(fd, buf, count)
|
| (系统调用陷入内核)
v
SYSCALL_DEFINE3(read, ...)
|
v
ksys_read()
|
v
vfs_read() <-- VFS层
|
| 1. 检查文件权限和模式
| 2. 更新访问时间(atime)
|
v
file->f_op->read_iter() 或 file->f_op->read()
|
| (通过函数指针调用具体文件系统的实现)
v
ext4_file_read_iter() <-- ext4文件系统层
|
v
generic_file_read_iter() <-- 通用页缓存层
|
| 1. 检查页缓存
| 2. 如果缓存命中,直接从缓存读取
| 3. 如果缓存未命中,触发页面读取
|
v
ext4_readpage() / ext4_readpages()
|
| 1. 通过inode->i_mapping找到页缓存
| 2. 分配页面
| 3. 通过ext4的extent tree找到物理块地址
|
v
submit_bio() <-- 块层
|
| 构造bio请求
|
v
块设备驱动 (如SCSI/NVMe)
|
v
硬件设备
4.2 write系统调用的完整路径
用户空间: write(fd, buf, count)
|
| (系统调用陷入内核)
v
SYSCALL_DEFINE3(write, ...)
|
v
ksys_write()
|
v
vfs_write() <-- VFS层
|
| 1. 检查写权限
| 2. 检查文件大小限制
|
v
file->f_op->write_iter()
|
| (调用具体文件系统的写函数)
v
ext4_file_write_iter() <-- ext4文件系统层
|
| 1. 处理O_DIRECT(直接I/O)
| 2. 处理O_APPEND(追加模式)
|
v
__generic_file_write_iter()
|
v
generic_perform_write() <-- 通用写路径
|
| 1. 循环处理每一页
| 2. 调用address_space_operations
|
v
ext4_write_begin()
|
| 1. 分配或查找页面
| 2. 如果需要,分配新的数据块
| 3. 读取部分写的页面内容
|
v
ext4_write_end()
|
| 1. 复制用户数据到页缓存
| 2. 标记页面为脏(PG_dirty)
| 3. 标记inode为脏
| 4. 更新文件大小和修改时间
|
v
(异步回写或同步刷新)
|
v
ext4_writepages()
|
| 1. 收集脏页
| 2. 分配物理块(如果需要)
| 3. 构造写请求
|
v
submit_bio() <-- 块层
|
v
块设备驱动
|
v
硬件设备
4.3 关键函数分析
4.3.1 vfs_read()
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
// 检查文件是否可读
if (!(file->f_mode & FMODE_READ))
return -EBADF;
// 检查file_operations是否存在
if (!file->f_op->read && !file->f_op->read_iter)
return -EINVAL;
// 检查缓冲区是否可写
if (!access_ok(buf, count))
return -EFAULT;
// 调用具体文件系统的read操作
if (file->f_op->read_iter)
ret = new_sync_read(file, buf, count, pos);
else
ret = file->f_op->read(file, buf, count, pos);
// 更新访问时间
if (ret > 0) {
fsnotify_access(file);
add_rchar(current, ret);
}
inc_syscr(current);
return ret;
}
4.3.2 ext4_file_read_iter()
static ssize_t ext4_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct inode *inode = file_inode(iocb->ki_filp);
// 处理加密文件
if (!ext4_inode_readable(inode))
return -EACCES;
// 对于直接I/O
if (iocb->ki_flags & IOCB_DIRECT) {
// 处理Direct I/O
return ext4_dio_read_iter(iocb, to);
}
// 标准的buffered I/O,使用通用页缓存
return generic_file_read_iter(iocb, to);
}
4.3.3 generic_file_read_iter()
ssize_t generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
// 如果是Direct I/O
if (iocb->ki_flags & IOCB_DIRECT) {
struct address_space *mapping = iocb->ki_filp->f_mapping;
// 刷新该范围的脏页
filemap_write_and_wait_range(mapping, iocb->ki_pos, end);
// 执行Direct I/O读取
return mapping->a_ops->direct_IO(iocb, iter);
}
// 标准的页缓存读取
return filemap_read(iocb, iter, 0);
}
4.4 ext4特定的实现
ext4文件系统使用extent tree来管理文件的物理块映射。
4.4.1 ext4的file_operations
const struct file_operations ext4_file_operations = {
.llseek = ext4_llseek,
.read_iter = ext4_file_read_iter,
.write_iter = ext4_file_write_iter,
.unlocked_ioctl = ext4_ioctl,
.open = ext4_file_open,
.release = ext4_release_file,
.mmap = ext4_file_mmap,
.fsync = ext4_sync_file,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = ext4_fallocate,
};
4.4.2 ext4的address_space_operations
static const struct address_space_operations ext4_aops = {
.readpage = ext4_readpage,
.readpages = ext4_readpages,
.writepage = ext4_writepage,
.writepages = ext4_writepages,
.write_begin = ext4_write_begin,
.write_end = ext4_write_end,
.bmap = ext4_bmap,
.invalidatepage = ext4_invalidatepage,
.releasepage = ext4_releasepage,
.direct_IO = ext4_direct_IO,
.migratepage = buffer_migrate_page,
.is_partially_uptodate = block_is_partially_uptodate,
.error_remove_page = generic_error_remove_page,
};
4.4.3 块地址转换
ext4使用extent tree将逻辑块号转换为物理块号:
// 逻辑块号 -> 物理块号
int ext4_map_blocks(handle_t *handle, struct inode *inode,
struct ext4_map_blocks *map, int flags)
{
struct ext4_extent_header *eh;
struct ext4_extent *ex;
// 1. 在extent tree中查找逻辑块对应的extent
ex = ext4_find_extent(inode, map->m_lblk);
// 2. 如果找到extent,直接返回物理块号
if (ex) {
map->m_pblk = ext4_ext_pblock(ex) + (map->m_lblk - le32_to_cpu(ex->ee_block));
return 0;
}
// 3. 如果是写操作且没找到,需要分配新块
if (flags & EXT4_GET_BLOCKS_CREATE) {
return ext4_ext_map_blocks(handle, inode, map, flags);
}
return -ENOENT;
}
5. 完整流程示例:读取ext4文件
假设用户程序执行 read(fd, buf, 4096):
1. 用户空间
- 应用程序调用read()
- C库(glibc)包装系统调用
2. 系统调用入口
- 陷入内核态
- SYSCALL_DEFINE3(read, ...)
3. VFS层 (vfs_read)
- 通过fd找到struct file
- 检查文件权限(FMODE_READ)
- 检查参数合法性
- 调用file->f_op->read_iter
4. ext4文件系统层
- ext4_file_read_iter()
- 判断是否Direct I/O
- 调用generic_file_read_iter()
5. 页缓存层
- generic_file_read_iter()
- 调用filemap_read()
- 查找页缓存(find_get_page)
6a. 缓存命中路径
- 从页缓存直接copy_to_user()
- 更新统计信息
- 返回用户空间
6b. 缓存未命中路径
- 调用readpage()/readpages()
- ext4_readpage()被调用
- 通过extent tree找到物理块号
- ext4_map_blocks()转换地址
- 构造bio请求
- submit_bio()提交到块层
7. 块I/O层
- 块层调度器处理请求
- 合并相邻请求
- 提交到设备驱动
8. 设备驱动层
- SCSI/NVMe驱动处理
- DMA传输数据
9. I/O完成中断
- 硬件中断
- 标记页面为最新(PG_uptodate)
- 唤醒等待进程
- copy_to_user()复制到用户缓冲区
10. 返回用户空间
- 系统调用返回
- 恢复用户态执行
6. 总结
VFS通过以下机制实现了文件系统的统一抽象:
- 分层设计: 清晰的层次分离了通用逻辑和文件系统特定实现
- 函数指针表: 实现了类似面向对象的多态性
- 页缓存: 提高了I/O性能,减少了磁盘访问
- dentry缓存: 加速了路径查找操作
理解VFS的工作原理对于文件系统开发、性能优化和内核调试都至关重要。无论是开发新的文件系统,还是分析I/O性能问题,VFS都是必须深入理解的核心组件。
参考资源
- Linux内核源码:
fs/目录 - Documentation/filesystems/vfs.txt
- ext4源码:
fs/ext4/ - 《Linux内核设计与实现》第13章
- 《深入理解Linux内核》第12章


