给Linux内核开发新手的礼物:手把手教你写一个能跑起来的简易内存文件系统
从零构建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 推荐学习路径
掌握基础实现后,可以逐步深入研究:
- 添加文件权限控制:完善inode的权限管理
- 实现扩展属性:支持xattr功能
- 加入文件锁机制:实现
flock操作 - 优化内存管理:自定义内存分配策略
// 示例:添加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中处理内存压力的方式就非常值得借鉴。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)