4. 内核挂载根文件系统的关键函数:prepare_namespace、mount_root 与 init/do_mounts.c

根文件系统的挂载是 Linux 内核启动过程中从“内核初始化”过渡到“用户空间”的临界点。一旦内核完成硬件探测、驱动加载和设备初始化,便进入 prepare_namespace() 函数,该函数负责解析启动参数、识别根设备、调用具体的文件系统驱动,最终将根文件系统挂载到 / 目录。整个流程的核心代码集中在 init/do_mounts.c 文件中。

关键认知: 根文件系统挂载失败是内核启动失败最常见的原因之一。理解 prepare_namespace → mount_root 的执行路径,是定位“Kernel panic - not syncing: VFS: Unable to mount root fs”等致命错误的必备技能。

4.1 函数调用链与执行流程

从 start_kernel() 到根文件系统就绪,关键函数调用链如下:

  1. start_kernel() → 完成架构初始化、内存管理、调度器、中断等子系统初始化。
  2. rest_init() → 创建 kernel_init 内核线程。
  3. kernel_init() → 调用 prepare_namespace()。
  4. prepare_namespace() → 解析 root= 参数,处理 initrd/initramfs,最终调用 mount_root()。
  5. mount_root() → 打开根设备,调用 get_sb_bdev() 或 mount_bdev() 挂载文件系统。
  6. kernel_init() → 挂载成功后,切换到根目录并执行 /sbin/init 或指定的 init= 程序。
调试入口: 在 kernel_init() 函数中插入 printk 或使用 early_printk,可以实时观察根设备号、文件系统类型和挂载返回值,是排查挂载失败的第一道防线。
根文件系统挂载函数调用链 start_kernel() rest_init() kernel_init() prepare_namespace() mount_root() get_sb_bdev() / mount_bdev() 执行 /sbin/init prepare_namespace() 核心职责 • 解析 root= 启动参数 • 处理 initrd / initramfs • 调用 mount_root() • 设置根设备号 (ROOT_DEV)

4.2 prepare_namespace() 函数深度解析

prepare_namespace() 定义在 init/do_mounts.c 中,是根文件系统挂载的“总调度器”。其执行逻辑可分为以下阶段:

执行阶段 关键操作 涉及变量/函数 失败后果
1. 参数解析 读取 root= 启动参数,确定根设备 root_dev_setup(), ROOT_DEV 设备号无效,挂载失败
2. initrd 处理 如果指定了 initrd=,加载并挂载临时根文件系统 initrd_load() initrd 损坏或格式错误导致内核 panic
3. 设备等待 等待根设备在 /dev 中出现(异步探测场景) wait_for_device_probe() 设备未就绪,超时后挂载失败
4. 文件系统识别 根据 rootfstype= 或自动探测确定文件系统类型 get_fs_type() 不支持的文件系统类型导致挂载失败
5. 调用 mount_root 最终调用 mount_root() 执行实际挂载 mount_root() 挂载失败,进入 kernel panic
常见陷阱: 在嵌入式系统中,根设备可能是 /dev/mmcblk0p2 或 /dev/sda1,但内核在 prepare_namespace() 阶段可能尚未完成对应驱动注册。此时需要检查驱动加载顺序,或使用 rootwait 参数让内核等待设备就绪。

4.3 mount_root() 函数与挂载机制

mount_root() 是实际执行文件系统挂载的函数。其核心流程如下:

  1. 打开根设备: 通过 blkdev_get_by_dev() 获取块设备句柄。
  2. 获取文件系统类型: 调用 get_fs_type() 根据 rootfstype= 参数或自动探测结果,找到对应的文件系统驱动(如 ext4、squashfs、nfs)。
  3. 调用挂载接口: 执行 mount_bdev() 或 get_sb_bdev(),将文件系统挂载到 / 目录。
  4. 错误处理: 如果挂载失败,打印错误信息并进入 panic()。
// init/do_mounts.c 中的 mount_root() 简化逻辑
void __init mount_root(void)
{
    struct file_system_type *fs_type;
    struct block_device *bdev;
    int err;

    // 1. 打开根设备
    bdev = blkdev_get_by_dev(ROOT_DEV, FMODE_READ | FMODE_WRITE, NULL);
    if (IS_ERR(bdev)) {
        printk("VFS: Cannot open root device \"%s\"\n", saved_root_name);
        panic("VFS: Unable to mount root fs on %s", saved_root_name);
    }

    // 2. 获取文件系统类型
    fs_type = get_fs_type(root_fs_names);
    if (!fs_type) {
        printk("VFS: Cannot find filesystem type \"%s\"\n", root_fs_names);
        panic("VFS: Unable to mount root fs");
    }

    // 3. 执行挂载
    err = mount_bdev(fs_type, 0, bdev, NULL);
    if (err) {
        printk("VFS: Cannot mount root fs on %s\n", saved_root_name);
        panic("VFS: Unable to mount root fs");
    }
}
调试技巧: 在 mount_root() 中,saved_root_name 保存了 root= 参数的原始字符串。如果挂载失败,内核会打印该字符串,帮助确认根设备参数是否正确传递。

4.4 init/do_mounts.c 文件结构概览

init/do_mounts.c 是根文件系统挂载的核心实现文件,包含以下主要函数和全局变量:

函数/变量 作用 关键依赖
prepare_namespace() 根文件系统挂载的总入口 root_dev_setup(), initrd_load()
mount_root() 执行实际挂载操作 blkdev_get_by_dev(), mount_bdev()
mount_block_root() 处理块设备根文件系统的挂载 get_fs_type()
initrd_load() 加载并挂载 initrd rd_load_image(), rd_load_prompt()
ROOT_DEV 全局变量,存储根设备号 由 root_dev_setup() 设置
saved_root_name 保存 root= 参数的字符串 用于调试输出
root_fs_names 保存 rootfstype= 参数的字符串 用于指定文件系统类型
init/do_mounts.c 文件结构图 init/do_mounts.c 核心结构 prepare_namespace() 总调度入口 mount_root() 实际挂载执行 mount_block_root() 块设备挂载处理 initrd_load() initrd 加载处理 ROOT_DEV 根设备号 saved_root_name root= 参数字符串 root_fs_names rootfstype= 字符串 blkdev_get 块设备操作 mount_bdev 挂载接口 get_fs_type 文件系统类型

4.5 常见挂载失败场景与根因定位

基于 prepare_namespace() 和 mount_root() 的执行逻辑,以下表格总结了最常见的根文件系统挂载失败场景及其根因:

失败现象 内核日志关键信息 根因分析 修复方向
VFS: Cannot open root device VFS: Cannot open root device "mmcblk0p2" 根设备驱动未加载或设备不存在 检查驱动编译选项、设备树配置、rootwait 参数
VFS: Cannot find filesystem type VFS: Cannot find filesystem type "ext4" 文件系统驱动未编译进内核 将文件系统驱动编译为 built-in(非模块)
VFS: Cannot mount root fs VFS: Cannot mount root fs on "sda1" 文件系统损坏或格式不匹配 检查文件系统完整性、rootfstype= 参数
Kernel panic - not syncing No filesystem could mount root 所有尝试的文件系统类型均挂载失败 检查内核配置、initramfs 内容、根设备分区表
initramfs 解压失败 Unpacking initramfs failed initramfs 镜像损坏或格式错误 重新生成 initramfs,检查压缩格式
实战建议: 在嵌入式开发中,建议在 prepare_namespace() 之前添加 printk 打印 ROOT_DEV 和 saved_root_name,确认内核接收到的根设备参数与预期一致。这是最快速定位“参数传递错误”类问题的方法。

4.6 本章小结

  • prepare_namespace() 是根文件系统挂载的总入口,负责参数解析、initrd 处理和最终挂载调度。
  • mount_root() 执行实际的块设备打开、文件系统类型获取和挂载操作。
  • init/do_mounts.c 集中了根文件系统挂载的所有核心逻辑,是内核启动调试的关键文件。
  • 挂载失败时,内核日志中的 VFS: 错误信息直接指向问题根因,是定位故障的第一手资料。
  • 理解这三个关键函数,等于掌握了根文件系统挂载的“命脉”,是分析和修复启动失败问题的必备知识。