存储系统-VFS 篇:为自制 OS 设计统一文件抽象层

"在自制操作系统中,如何让 open/read/write 既能操作 ext2,又能操作 procfs?
本文将从零设计 VFS 抽象层,构建四大核心对象,实现路径解析与缓存机制,
为后续文件系统提供统一接口。"

引言:为什么自制 OS 需要 VFS?

在自制操作系统中,初期可能只实现一个文件系统(如 ext2)。
但随着系统演进,你将需要:

  • procfs:暴露内核信息(/proc/cpuinfo)
  • sysfs:管理设备(/sys/devices)
  • devfs:设备文件(/dev/tty0)
  • tmpfs:内存文件系统

如果每个文件系统都直接暴露 ext2_openproc_open 接口,
用户程序将不得不硬编码文件系统类型,系统扩展性极差。

VFS(Virtual File System)正是为解决此问题而生!
它通过统一抽象层,让上层应用只需调用 open(path)
VFS 自动路由到对应文件系统的实现。

本文将为自制 OS 设计一个简洁高效、易于扩展的 VFS 框架。


第一章:VFS 核心设计原则

1.1 设计目标

核心目标:

  1. 统一接口:用户程序无需关心文件系统类型
  2. 易于扩展:新增文件系统只需实现 VFS 接口
  3. 性能优先:路径解析 O(log n),缓存加速
  4. 内存高效:对象按需分配,支持回收

约束条件:

  • 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
  • 定期回收:时钟中断定期检查

回收顺序:

  1. dentry 缓存:无引用的 dentry
  2. inode 缓存:无引用的 inode
  3. 页缓存:后续实现

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 奠定了坚实基础。
每新增一个文件系统,只需:

  1. 实现 vfs_super_operations
  2. 实现 vfs_inode_operationsvfs_file_operations
  3. 使用 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);

:本文所有代码均为简化实现,实际使用需添加错误处理、边界检查等。