4. 文件系统注册与挂载:register_filesystem、mount、fill_super 回调函数实现
文件系统驱动在 Linux/Android 内核中的核心生命周期包含两个关键阶段:注册与挂载。注册阶段将文件系统类型告知 VFS(虚拟文件系统)层;挂载阶段则通过 fill_super 回调将具体存储介质上的超级块信息读入内存,建立起文件系统树的连接。本章深入剖析这三个核心接口的实现机制与调试要点。
核心要点: register_filesystem 建立类型索引,mount 系统调用触发查找与实例化,fill_super 完成磁盘超级块到内存超级块的转换。三者构成文件系统驱动的“入场”三部曲。
4.1 文件系统注册:register_filesystem
每个文件系统驱动在初始化时(通常通过 module_init)调用 register_filesystem,将一个 struct file_system_type 实例注册到内核全局链表 file_systems 中。
4.1.1 数据结构:struct file_system_type
| 成员 | 类型 | 说明 |
|---|---|---|
name |
const char * |
文件系统名称,如 "ext4"、"f2fs"、"sdcardfs" |
fs_flags |
int |
特性标志,如 FS_REQUIRES_DEV、FS_USERNS_MOUNT |
mount |
struct dentry *(*mount)(...) |
挂载入口回调,VFS 调用此函数获取根 dentry |
kill_sb |
void (*kill_sb)(struct super_block *) |
卸载时释放超级块的回调 |
next |
struct file_system_type * |
链表指针,指向下一个已注册的文件系统类型 |
4.1.2 注册流程
- 驱动模块初始化函数中定义并填充
struct file_system_type实例。 - 调用
register_filesystem(&my_fs_type)。 - 内核将
my_fs_type插入file_systems链表头部。 - 若名称冲突(同名文件系统已注册),返回
-EBUSY。
// 示例:一个简单内存文件系统的注册
static struct file_system_type myfs_fs_type = {
.owner = THIS_MODULE,
.name = "myfs",
.mount = myfs_mount,
.kill_sb = myfs_kill_superblock,
.fs_flags = FS_USERNS_MOUNT,
};
static int __init myfs_init(void)
{
int ret = register_filesystem(&myfs_fs_type);
if (ret)
pr_err("myfs: failed to register filesystem (err=%d)\n", ret);
else
pr_info("myfs: filesystem registered\n");
return ret;
}
module_init(myfs_init);
调试提示: 使用
cat /proc/filesystems 可查看当前内核中所有已注册的文件系统类型。若你的驱动未出现在列表中,检查 register_filesystem 返回值及模块加载日志。
4.2 挂载入口:mount 回调
当用户执行 mount -t myfs /dev/block/xxx /mnt 时,VFS 根据 myfs 名称在 file_systems 链表中找到对应的 file_system_type,然后调用其 mount 回调。该回调负责创建超级块并返回根目录的 dentry。
4.2.1 mount 回调的两种典型实现
| 场景 | 推荐辅助函数 | 说明 |
|---|---|---|
| 基于块设备的文件系统 | mount_bdev |
打开块设备,调用 fill_super,返回根 dentry |
| 基于内存/虚拟的文件系统 | mount_nodev |
不依赖块设备,直接调用 fill_super,返回根 dentry |
| 基于单文件的文件系统 | mount_single |
所有挂载点共享同一个超级块实例 |
// 使用 mount_nodev 的典型实现
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);
}
4.2.2 mount 回调的返回值
- 成功: 返回根目录的
struct dentry *,该 dentry 的d_sb指向已初始化的超级块。 - 失败: 返回
ERR_PTR(-错误码),常见错误码包括-ENOMEM、-EINVAL、-ENODEV。
常见陷阱: mount 回调中不要直接返回
NULL,必须使用 ERR_PTR 包裹错误码。VFS 层通过 IS_ERR 宏判断返回值是否有效。
4.3 超级块填充:fill_super 回调
fill_super 是文件系统驱动中最关键的初始化回调。它在 mount 过程中被调用,负责从存储介质读取超级块信息,填充内存中的 struct super_block,并创建根索引节点(inode)和根目录 dentry。
4.3.1 fill_super 的标准实现步骤
- 分配并读取磁盘超级块: 使用
sb_bread或自定义读取函数从块设备加载原始超级块数据。 - 校验超级块魔数: 验证文件系统标识,防止挂载错误类型。
- 填充 struct super_block: 设置
s_blocksize、s_blocksize_bits、s_maxbytes、s_op(超级块操作函数集)、s_export_op(NFS 导出操作)等。 - 创建根 inode: 调用
iget_locked或new_inode分配 inode,填充i_mode、i_uid、i_gid、i_size、i_ino及i_op(inode 操作函数集)、i_fop(文件操作函数集)。 - 创建根 dentry: 调用
d_make_root(inode)将根 inode 与 dentry 关联。 - 设置 s_root: 将
sb->s_root指向刚创建的根 dentry。
// 简化的 fill_super 实现示例
static int myfs_fill_super(struct super_block *sb, void *data, int silent)
{
struct inode *root_inode;
struct myfs_sb_info *sbi;
// 1. 分配私有超级块信息
sbi = kzalloc(sizeof(struct myfs_sb_info), GFP_KERNEL);
if (!sbi)
return -ENOMEM;
sb->s_fs_info = sbi;
// 2. 设置超级块基本参数
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = MYFS_MAGIC;
sb->s_op = &myfs_super_ops;
// 3. 创建根 inode(inode number = 1)
root_inode = new_inode(sb);
if (!root_inode) {
kfree(sbi);
return -ENOMEM;
}
root_inode->i_ino = MYFS_ROOT_INO;
root_inode->i_mode = S_IFDIR | 0755;
root_inode->i_uid = current_fsuid();
root_inode->i_gid = current_fsgid();
root_inode->i_size = 0;
root_inode->i_op = &myfs_dir_inode_ops;
root_inode->i_fop = &myfs_dir_operations;
inode_init_owner(&nop_mnt_idmap, root_inode, NULL, root_inode->i_mode);
// 4. 创建根 dentry
sb->s_root = d_make_root(root_inode);
if (!sb->s_root) {
iput(root_inode);
kfree(sbi);
return -ENOMEM;
}
return 0;
}
4.3.2 fill_super 关键字段速查
| 字段 | 设置时机 | 典型值/说明 |
|---|---|---|
s_blocksize |
fill_super | 4096(与 PAGE_SIZE 一致时性能最佳) |
s_op |
fill_super | 指向 struct super_operations,必须实现 put_super、sync_fs、statfs |
s_magic |
fill_super | 文件系统魔数,用于 statfs 和 fstatfs 识别 |
s_fs_info |
fill_super | 指向驱动私有数据结构的指针,通常包含挂载选项、设备信息 |
s_root |
fill_super 末尾 | 根 dentry,必须由 d_make_root 创建 |
4.4 注册与挂载全流程 SVG 架构图
下图展示了从模块初始化到文件系统挂载完成的完整调用链与数据结构关系:
4.5 调试与验证方法
4.5.1 注册阶段调试
- 检查 /proc/filesystems: 确认文件系统名称出现在列表中,区分 "nodev"(虚拟文件系统)和块设备文件系统。
- 查看 dmesg: 确认
register_filesystem返回 0,无-EBUSY错误。 - 模块引用计数: 使用
lsmod | grep myfs确认模块已加载且引用计数正确。
4.5.2 挂载阶段调试
| 问题现象 | 可能原因 | 调试手段 |
|---|---|---|
| mount: unknown filesystem type | 文件系统未注册或名称拼写错误 | 检查 /proc/filesystems 与 mount -t 参数 |
| mount: invalid argument | fill_super 返回 -EINVAL,魔数校验失败 | 在 fill_super 中添加 pr_debug 打印魔数 |
| mount: cannot allocate memory | fill_super 中 kmalloc 或 new_inode 失败 | 检查 GFP_KERNEL 标志,确认内存压力 |
| mount 成功但 ls 报错 | 根 inode 的 i_op 或 i_fop 未正确设置 | 使用 tracepoint 跟踪 inode 操作调用 |
4.5.3 使用 ftrace 跟踪挂载流程
# 开启文件系统相关 tracepoint
echo 1 > /sys/kernel/tracing/events/fs/fs_open/enable
echo 1 > /sys/kernel/tracing/events/fs/do_sys_open/enable
# 挂载文件系统
mount -t myfs /dev/null /mnt
# 查看跟踪输出
cat /sys/kernel/tracing/trace | grep myfs
进阶调试: 在
fill_super 中插入 WARN_ON(1) 或 dump_stack() 可快速定位调用路径。生产环境建议使用 pr_debug 配合动态调试(dynamic debug)控制日志输出。
4.6 本章小结
- register_filesystem 是文件系统驱动的“入场券”,必须在模块初始化时成功调用。
- mount 回调 是 VFS 与驱动之间的桥梁,根据文件系统类型选择
mount_bdev、mount_nodev或mount_single。 - fill_super 回调 是驱动初始化的核心,必须正确填充超级块、根 inode 和根 dentry,任何一步失败都会导致挂载失败。
- 调试时善用
/proc/filesystems、dmesg、ftrace和动态调试,可大幅缩短问题定位时间。