存储系统-VFS 篇:为自制 OS 设计统一文件抽象层
"在自制操作系统中,如何让 open/read/write 既能操作 ext2,又能操作 procfs?
本文将从零设计 VFS 抽象层,构建四大核心对象,实现路径解析与缓存机制,
为后续文件系统提供统一接口。"
引言:为什么自制 OS 需要 VFS?
在自制操作系统中,初期可能只实现一个文件系统(如 ext2)。
但随着系统演进,你将需要:
- procfs:暴露内核信息(/proc/cpuinfo)
- sysfs:管理设备(/sys/devices)
- devfs:设备文件(/dev/tty0)
- tmpfs:内存文件系统
如果每个文件系统都直接暴露 ext2_open、proc_open 接口,
用户程序将不得不硬编码文件系统类型,系统扩展性极差。
VFS(Virtual File System)正是为解决此问题而生!
它通过统一抽象层,让上层应用只需调用 open(path),
VFS 自动路由到对应文件系统的实现。
本文将为自制 OS 设计一个简洁高效、易于扩展的 VFS 框架。
第一章:VFS 核心设计原则
1.1 设计目标
核心目标:
- 统一接口:用户程序无需关心文件系统类型
- 易于扩展:新增文件系统只需实现 VFS 接口
- 性能优先:路径解析 O(log n),缓存加速
- 内存高效:对象按需分配,支持回收
约束条件:
- 32 位系统:指针对齐 4 字节
- 无动态加载:文件系统编译时注册
- 单核设计:暂不考虑锁(后续可扩展)
1.2 架构概览
+------------------+
| 用户程序 | // 调用 open("/proc/cpuinfo", O_RDONLY)
+------------------+
| 系统调用层 | // sys_open → vfs_open
+------------------+
| VFS | // 路径解析 → 找到 procfs → 调用 proc_open
+------------------+
| 文件系统层 | // ext2, procfs, sysfs, devfs
+------------------+
| 块设备层 | // IDE, RAM disk
+------------------+
1.3 关键设计决策
四大核心对象(简化版):
| 对象 | 职责 | 生命周期 | |——|——|———-| | vfs_super | 描述文件系统实例 | 挂载时创建,卸载时销毁 | | vfs_inode | 描述单个文件 | 首次访问时创建,LRU 回收 | | vfs_dentry | 缓存路径名到 inode 映射 | 路径解析时创建,LRU 回收 | | vfs_file | 描述打开的文件 | open 时创建,close 时销毁 |
路径解析策略:
- dentry 缓存:哈希表加速查找
- 按需加载:inode 仅在需要时创建
- 路径组件解析:从根目录逐级查找
缓存管理:
- LRU 链表:管理未使用的 dentry/inode
- 内存压力回收:空闲内存低于阈值时触发
第二章:VFS 核心数据结构
2.1 vfs_super:文件系统实例
// vfs.h
#define VFS_MAGIC_EXT2 0xEF53
#define VFS_MAGIC_PROC 0x9FA0
#define VFS_MAGIC_SYS 0x6265
struct vfs_super_operations {
struct vfs_inode *(*alloc_inode)(struct vfs_super *sb);
void (*put_super)(struct vfs_super *sb);
int (*statfs)(struct vfs_super *sb, struct vfs_statfs *buf);
};
struct vfs_super {
uint32_t s_magic; // 文件系统魔数
char s_id[32]; // 挂载点标识(如 "/dev/hda1")
void *s_fs_info; // 文件系统私有数据
struct vfs_super_operations *s_op; // 超级块操作
struct vfs_dentry *s_root; // 根目录 dentry
struct list_head s_instances; // 同类型文件系统链表
};
2.2 vfs_inode:文件元数据
struct vfs_inode_operations {
struct vfs_dentry *(*lookup)(struct vfs_inode *dir, const char *name);
int (*mkdir)(struct vfs_inode *dir, const char *name, uint16_t mode);
int (*rmdir)(struct vfs_inode *dir, const char *name);
int (*create)(struct vfs_inode *dir, const char *name, uint16_t mode);
int (*unlink)(struct vfs_inode *dir, const char *name);
};
struct vfs_file_operations {
ssize_t (*read)(struct vfs_file *file, char *buf, size_t count);
ssize_t (*write)(struct vfs_file *file, const char *buf, size_t count);
int (*ioctl)(struct vfs_file *file, uint32_t cmd, void *arg);
int (*open)(struct vfs_inode *inode, struct vfs_file *file);
int (*release)(struct vfs_inode *inode, struct vfs_file *file);
};
struct vfs_inode {
uint32_t i_ino; // inode 编号
uint16_t i_mode; // 文件类型与权限
uint32_t i_size; // 文件大小
uint32_t i_blocks; // 块数
uint32_t i_atime; // 访问时间
uint32_t i_mtime; // 修改时间
uint32_t i_ctime; // 创建时间
struct vfs_inode_operations *i_op; // inode 操作
struct vfs_file_operations *i_fop; // 文件操作
void *i_private; // 文件系统私有数据
struct vfs_super *i_sb; // 所属超级块
// VFS 缓存管理
struct list_head i_lru; // LRU 链表
atomic_t i_count; // 引用计数
};
2.3 vfs_dentry:路径名缓存
struct vfs_dentry {
char *d_name; // 文件名(动态分配)
uint32_t d_hash; // 文件名哈希值
struct vfs_inode *d_inode; // 对应 inode
struct vfs_dentry *d_parent; // 父目录
struct list_head d_subdirs; // 子目录链表头
struct list_head d_child; // 兄弟节点
// VFS 缓存管理
struct hlist_node d_hash; // 哈希表节点
struct list_head d_lru; // LRU 链表
atomic_t d_count; // 引用计数
};
2.4 vfs_file:打开的文件
struct vfs_file {
uint32_t f_flags; // 打开标志(O_RDONLY/O_WRONLY)
uint32_t f_pos; // 当前读写位置
struct vfs_inode *f_inode; // 对应 inode
struct vfs_file_operations *f_op; // 文件操作
void *f_private; // 私有数据(如文件偏移)
};
第三章:VFS 核心流程实现
3.1 文件系统注册
文件系统类型
// vfs.h
struct vfs_filesystem_type {
const char *name; // 文件系统名("ext2")
struct vfs_super *(*mount)(const char *dev, void *data);
void (*kill_sb)(struct vfs_super *sb);
struct vfs_filesystem_type *next;
};
// 全局文件系统链表
extern struct vfs_filesystem_type *vfs_fs_types;
注册宏
// vfs.h
#define VFS_DECLARE_FILESYSTEM(name) \
static struct vfs_filesystem_type name##_fs_type = { \
.name = #name, \
.mount = name##_mount, \
.kill_sb = name##_kill_sb, \
}; \
__attribute__((constructor)) static void name##_init(void) { \
name##_fs_type.next = vfs_fs_types; \
vfs_fs_types = &name##_fs_type; \
}
使用示例(ext2)
// ext2.c
VFS_DECLARE_FILESYSTEM(ext2)
static struct vfs_super *ext2_mount(const char *dev, void *data) {
// 1. 打开块设备
struct block_device *bdev = block_open(dev);
if (!bdev) return NULL;
// 2. 读取超级块
struct ext2_super_block *es = kmalloc(sizeof(struct ext2_super_block));
block_read(bdev, 1024, es, sizeof(struct ext2_super_block));
// 3. 验证魔数
if (es->s_magic != 0xEF53) {
kfree(es);
return NULL;
}
// 4. 创建 vfs_super
struct vfs_super *sb = kmalloc(sizeof(struct vfs_super));
sb->s_magic = VFS_MAGIC_EXT2;
sb->s_fs_info = es;
sb->s_op = &ext2_super_ops;
// 5. 读取根 inode
struct vfs_inode *root = ext2_iget(sb, 2); // EXT2_ROOT_INO=2
sb->s_root = d_obtain_root(root);
return sb;
}
3.2 路径解析:vfs_path_lookup
主流程
// vfs.c
int vfs_path_lookup(const char *path, struct vfs_dentry **dentry) {
// 1. 处理绝对/相对路径
struct vfs_dentry *current = (path[0] == '/') ?
vfs_root_dentry : current_task->cwd;
// 2. 跳过开头的 '/'
if (path[0] == '/') path++;
// 3. 逐级解析
while (*path) {
// 提取路径组件
char component[256];
const char *next = strchr(path, '/');
if (next) {
memcpy(component, path, next - path);
component[next - path] = '\0';
path = next + 1;
} else {
strcpy(component, path);
path += strlen(path);
}
// 跳过空组件("//")
if (component[0] == '\0') continue;
// 特殊组件处理
if (strcmp(component, ".") == 0) {
continue;
} else if (strcmp(component, "..") == 0) {
if (current->d_parent) {
current = current->d_parent;
}
continue;
}
// 查找子 dentry
struct vfs_dentry *child = d_lookup(current, component);
if (!child) {
return -ENOENT;
}
current = child;
}
*dentry = current;
return 0;
}
dentry 查找(d_lookup)
// vfs.c
struct vfs_dentry *d_lookup(struct vfs_dentry *parent, const char *name) {
// 1. 计算哈希值
uint32_t hash = d_hash(name);
// 2. 查询 dentry 哈希表
struct hlist_head *head = &dentry_hashtable[hash % DENTRY_HASH_SIZE];
struct vfs_dentry *dentry;
hlist_for_each_entry(dentry, head, d_hash) {
if (dentry->d_parent == parent &&
strcmp(dentry->d_name, name) == 0) {
// 增加引用计数
atomic_inc(&dentry->d_count);
return dentry;
}
}
// 3. 缓存未命中,调用 inode lookup
if (!parent->d_inode || !parent->d_inode->i_op->lookup) {
return NULL;
}
struct vfs_dentry *new_dentry = parent->d_inode->i_op->lookup(parent->d_inode, name);
if (new_dentry) {
// 加入缓存
d_add(parent, new_dentry);
atomic_inc(&new_dentry->d_count);
}
return new_dentry;
}
3.3 dentry 缓存管理
d_add:添加 dentry 到缓存
// vfs.c
void d_add(struct vfs_dentry *parent, struct vfs_dentry *dentry) {
// 1. 设置父指针
dentry->d_parent = parent;
// 2. 加入父目录子链表
list_add(&dentry->d_child, &parent->d_subdirs);
// 3. 加入全局哈希表
uint32_t hash = dentry->d_hash;
hlist_add_head(&dentry->d_hash, &dentry_hashtable[hash % DENTRY_HASH_SIZE]);
// 4. 加入 LRU 链表
list_add_tail(&dentry->d_lru, &dentry_lru_list);
atomic_set(&dentry->d_count, 1);
}
dentry 回收
// vfs.c
void dentry_reclaim(int nr_to_reclaim) {
struct vfs_dentry *dentry, *tmp;
int reclaimed = 0;
list_for_each_entry_safe(dentry, tmp, &dentry_lru_list, d_lru) {
if (atomic_read(&dentry->d_count) == 0) {
// 从哈希表移除
hlist_del(&dentry->d_hash);
// 从父链表移除
list_del(&dentry->d_child);
// 释放内存
kfree(dentry->d_name);
kfree(dentry);
reclaimed++;
if (reclaimed >= nr_to_reclaim) break;
}
}
}
第四章:系统调用对接
4.1 open 系统调用
// sys_vfs.c
int sys_open(const char *pathname, int flags) {
// 1. 复制用户路径
char path[256];
if (copy_from_user(path, pathname, sizeof(path))) {
return -1;
}
// 2. 路径解析
struct vfs_dentry *dentry;
if (vfs_path_lookup(path, &dentry) < 0) {
return -1;
}
// 3. 创建 vfs_file
struct vfs_file *file = kmalloc(sizeof(struct vfs_file));
file->f_flags = flags;
file->f_pos = 0;
file->f_inode = dentry->d_inode;
file->f_op = dentry->d_inode->i_fop;
file->f_private = NULL;
// 4. 调用文件系统 open
if (file->f_op && file->f_op->open) {
if (file->f_op->open(dentry->d_inode, file) < 0) {
kfree(file);
return -1;
}
}
// 5. 分配 fd
int fd = alloc_fd(file);
return fd;
}
4.2 read/write 系统调用
// sys_vfs.c
ssize_t sys_read(int fd, void *buf, size_t count) {
struct vfs_file *file = get_file(fd);
if (!file || !file->f_op || !file->f_op->read) {
return -1;
}
// 用户缓冲区验证
if (!validate_user_ptr(buf, count)) {
return -1;
}
// 调用文件系统 read
ssize_t ret = file->f_op->read(file, buf, count);
if (ret > 0) {
file->f_pos += ret;
}
return ret;
}
第五章:内存与性能优化
5.1 LRU 缓存回收策略
触发条件:
- 内存分配失败:buddy_alloc 返回 NULL
- 定期回收:时钟中断定期检查
回收顺序:
- dentry 缓存:无引用的 dentry
- inode 缓存:无引用的 inode
- 页缓存:后续实现
5.2 哈希表优化
动态扩容(简化版):
// vfs.c
#define DENTRY_HASH_SIZE 1024
static struct hlist_head dentry_hashtable[DENTRY_HASH_SIZE];
// 哈希函数(djb2)
static uint32_t d_hash(const char *str) {
uint32_t hash = 5381;
int c;
while (c = *str++) {
hash = ((hash << 5) + hash) + c;
}
return hash;
}
5.3 路径解析优化
路径组件缓存:
- 栈分配:小路径使用栈缓冲区
- 避免重复解析:缓存完整路径结果
// vfs.c
int vfs_path_lookup(const char *path, struct vfs_dentry **dentry) {
// 栈缓冲区(避免 kmalloc)
char component[64];
// ... 解析逻辑
}
结论:为自制 OS 构建可扩展存储栈
VFS 是自制操作系统存储子系统的基石。
通过精心设计的四大对象和缓存机制,
我们实现了:
- 统一接口:用户程序无需关心底层文件系统
- 易于扩展:新增文件系统只需实现 VFS 接口
- 性能保障:dentry 缓存加速路径解析
- 内存高效:LRU 回收机制避免内存泄漏
此 VFS 框架为后续实现 ext2、procfs、sysfs、devfs 奠定了坚实基础。
每新增一个文件系统,只需:
- 实现
vfs_super_operations - 实现
vfs_inode_operations和vfs_file_operations - 使用
VFS_DECLARE_FILESYSTEM注册
真正的操作系统,始于对抽象的深刻理解。
VFS 正是这种抽象能力的完美体现。
附录:关键数据结构与接口速查
核心数据结构
| 结构 | 作用 | |——|——| | struct vfs_super | 文件系统实例 | | struct vfs_inode | 文件元数据 | | struct vfs_dentry | 路径名缓存 | | struct vfs_file | 打开的文件 |
文件系统注册
VFS_DECLARE_FILESYSTEM(fsname)
// 自动生成 fsname_mount, fsname_kill_sb
路径解析
int vfs_path_lookup(const char *path, struct vfs_dentry **dentry);
系统调用
int sys_open(const char *pathname, int flags);
ssize_t sys_read(int fd, void *buf, size_t count);
ssize_t sys_write(int fd, const void *buf, size_t count);
注:本文所有代码均为简化实现,实际使用需添加错误处理、边界检查等。