从零构建Linux内存文件系统:新手友好的实战指南

为什么选择内存文件系统作为内核开发起点?

刚接触Linux内核开发时,文件系统往往是最令人望而生畏的领域之一。传统文件系统涉及块设备、缓存管理、磁盘调度等复杂机制,而内存文件系统则提供了一个干净的切入点——它省去了持久化存储的复杂性,让我们可以专注于理解VFS(虚拟文件系统)的核心机制。

记得我第一次尝试阅读ext4源码时的困惑:数千行代码中,哪些是真正关键的?后来发现,Linux内核其实提供了一系列"simple_"开头的API(如simple_lookup、simple_dir_operations),专门为简化开发而设计。这些API就像乐高积木的基础件,让我们能快速搭建起可运行的原型。

1. 环境准备与基础概念

1.1 开发环境配置

在开始编码前,我们需要一个合适的内核开发环境:

# 安装基础编译工具
sudo apt install build-essential flex bison libssl-dev

# 获取当前内核头文件
sudo apt install linux-headers-$(uname -r)

# 创建模块Makefile
obj-m += myfs.o
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

提示:建议使用虚拟机进行内核模块开发,避免主机系统崩溃风险

1.2 内存文件系统核心组件

一个最小化的内存文件系统需要实现以下数据结构:

组件作用对应内核结构体
超级块文件系统元信息容器struct super_block
inode文件/目录的元数据struct inode
dentry目录项缓存struct dentry
file打开文件的上下文struct file

2. 文件系统注册与挂载

2.1 定义文件系统类型

首先向内核注册我们的文件系统类型:

static struct file_system_type myfs_type = {
    .owner = THIS_MODULE,
    .name = "myfs",
    .mount = myfs_mount,
    .kill_sb = kill_litter_super, // 使用内核提供的简单卸载方法
};

static int __init myfs_init(void)
{
    return register_filesystem(&myfs_type);
}

这里的关键点:

  • .mount 指定挂载时的回调函数
  • kill_litter_super 是内核提供的简单清理函数,适合基础场景

2.2 实现挂载操作

挂载函数需要完成超级块的初始化:

static struct dentry *myfs_mount(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data)
{
    return mount_nodev(fs_type, flags, data, myfs_fill_super);
}

static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
    struct inode *root_inode;
    
    sb->s_blocksize = PAGE_SIZE;
    sb->s_blocksize_bits = PAGE_SHIFT;
    sb->s_magic = 0x13131313; // 自定义魔数
    
    root_inode = myfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);
    sb->s_root = d_make_root(root_inode);
    
    return sb->s_root ? 0 : -ENOMEM;
}

注意:mount_nodev适用于无需块设备的文件系统,这正是内存文件系统的特点

3. inode与文件操作实现

3.1 简易inode生成

myfs_get_inode是核心函数,负责创建各种类型的inode:

struct inode *myfs_get_inode(struct super_block *sb,
                const struct inode *dir, umode_t mode, dev_t dev)
{
    struct inode *inode = new_inode(sb);
    
    if (!inode)
        return NULL;
        
    inode->i_ino = get_next_ino();
    inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
    inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
    
    if (S_ISDIR(mode)) {
        inode->i_op = &myfs_dir_inode_operations;
        inode->i_fop = &simple_dir_operations;
        set_nlink(inode, 2); // 目录默认两个硬链接:自身和"."
    } else if (S_ISREG(mode)) {
        inode->i_op = &myfs_file_inode_operations;
        inode->i_fop = &myfs_file_operations;
        inode->i_mapping->a_ops = &ram_aops; // 使用内存页缓存
    }
    
    return inode;
}

3.2 文件操作实现

利用内核提供的通用文件操作可以大幅简化开发:

static const struct file_operations myfs_file_operations = {
    .read_iter = generic_file_read_iter,
    .write_iter = generic_file_write_iter,
    .mmap = generic_file_mmap,
    .fsync = noop_fsync,
    .llseek = generic_file_llseek,
};

static const struct inode_operations myfs_file_inode_operations = {
    .setattr = simple_setattr,
    .getattr = simple_getattr,
};

4. 目录操作与文件创建

4.1 实现目录操作

借助simple_系列函数实现基本目录操作:

static const struct inode_operations myfs_dir_inode_operations = {
    .create = myfs_create,
    .lookup = simple_lookup,
    .link = simple_link,
    .unlink = simple_unlink,
    .mkdir = myfs_mkdir,
    .rmdir = simple_rmdir,
    .mknod = myfs_mknod,
};

static int myfs_create(struct mnt_idmap *idmap,
                      struct inode *dir, struct dentry *dentry,
                      umode_t mode, bool excl)
{
    struct inode *inode = myfs_get_inode(dir->i_sb, dir, mode | S_IFREG, 0);
    if (!inode)
        return -ENOSPC;
        
    d_instantiate(dentry, inode);
    return 0;
}

4.2 测试文件系统功能

编译加载模块后,可以通过以下命令测试:

# 创建挂载点
mkdir /mnt/myfs

# 挂载文件系统
sudo mount -t myfs none /mnt/myfs

# 测试基本操作
echo "Hello World" > /mnt/myfs/testfile
ls -l /mnt/myfs
cat /mnt/myfs/testfile

5. 调试技巧与进阶学习

5.1 常用调试手段

开发过程中这些工具特别有用:

  • printk:内核态打印日志(通过dmesg查看)
  • strace:跟踪系统调用
  • gdb + kgdb:内核调试
  • procfs和sysfs:查看内核状态

5.2 推荐学习路径

掌握基础实现后,可以逐步深入研究:

  1. 添加文件权限控制:完善inode的权限管理
  2. 实现扩展属性:支持xattr功能
  3. 加入文件锁机制:实现flock操作
  4. 优化内存管理:自定义内存分配策略
// 示例:添加xattr支持
static const struct xattr_handler *myfs_xattr_handlers[] = {
    &generic_xattr_user,
    NULL
};

static const struct inode_operations myfs_file_inode_operations = {
    .listxattr = generic_listxattr,
    .getattr = simple_getattr,
    .setattr = simple_setattr,
    .xattr_handlers = myfs_xattr_handlers,
};

6. 从ramfs/tmpfs汲取灵感

Linux内核自带的ramfs和tmpfs是优秀的学习资源:

  • ramfs:最简单的内存文件系统,约500行代码
  • tmpfs:支持交换的高级内存文件系统

关键学习点:

  • 如何组织文件数据的内存存储
  • 页缓存与地址空间操作(address_space_operations)
  • 文件系统限额实现

在开发过程中遇到问题时,我习惯去参考这些成熟实现的对应代码,往往能找到优雅的解决方案。比如tmpfs中处理内存压力的方式就非常值得借鉴。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐