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 注册流程

  1. 驱动模块初始化函数中定义并填充 struct file_system_type 实例。
  2. 调用 register_filesystem(&my_fs_type)。
  3. 内核将 my_fs_type 插入 file_systems 链表头部。
  4. 若名称冲突(同名文件系统已注册),返回 -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 的标准实现步骤

  1. 分配并读取磁盘超级块: 使用 sb_bread 或自定义读取函数从块设备加载原始超级块数据。
  2. 校验超级块魔数: 验证文件系统标识,防止挂载错误类型。
  3. 填充 struct super_block: 设置 s_blocksize、s_blocksize_bits、s_maxbytes、s_op(超级块操作函数集)、s_export_op(NFS 导出操作)等。
  4. 创建根 inode: 调用 iget_locked 或 new_inode 分配 inode,填充 i_mode、i_uid、i_gid、i_size、i_ino 及 i_op(inode 操作函数集)、i_fop(文件操作函数集)。
  5. 创建根 dentry: 调用 d_make_root(inode) 将根 inode 与 dentry 关联。
  6. 设置 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 架构图

下图展示了从模块初始化到文件系统挂载完成的完整调用链与数据结构关系:

文件系统注册与挂载全流程架构图 文件系统注册与挂载全流程 阶段一:模块初始化 → register_filesystem module_init(myfs_init) 填充 file_system_type register_filesystem() 插入 file_systems 链表 /proc/filesystems 用户态可查看注册列表 阶段二:mount 系统调用 → VFS 查找 file_system_type mount -t myfs 用户态系统调用 VFS: path_mount() 查找 fs_type->mount 调用 mount 回调 mount_nodev/mount_bdev 阶段三:fill_super 回调 → 超级块与根 inode 初始化 读取磁盘超级块 sb_bread / 自定义读取 填充 super_block s_blocksize, s_op, s_magic 创建根 inode new_inode / iget_locked d_make_root(inode) 设置 sb->s_root 挂载成功:返回根 dentry → VFS 建立挂载点连接 用户可通过 /mnt 访问文件系统 关键数据结构关系 file_system_type → mount → super_block → s_root(dentry) → d_inode(inode)

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 和动态调试,可大幅缩短问题定位时间。