§1.1 rootfs 怎么挂上
平台:QEMU virt + ARM64。当前命令:
bash
qemu-system-aarch64 \
-M virt \
-cpu cortex-a57 \
-m 1024 \
-kernel linux-6.10/arch/arm64/boot/Image \
-initrd initramfs.cpio.gz \
-nographic \
-append "console=ttyAMA0 nokaslr" \
-s -S
0. 这条命令和根有关的部分
| QEMU | 和根的关系 |
|---|---|
-initrd initramfs.cpio.gz |
cpio initramfs ,解进已经存在的内存 /。不是 root=/dev/ram,不是 Root_RAM0 |
-append "console=ttyAMA0 nokaslr" |
没有 root= ,saved_root_name 为空 |
包里的 /init |
busybox 脚本,最后 exec /bin/sh。有它就不会进 prepare_namespace |
sh
#!/bin/busybox sh
/bin/busybox --install -s /bin
mount -t proc proc /proc
mount -t sysfs sys /sys
exec /bin/sh
1. 在 main.c 里和挂根相关的挂钩
内核一开始 没有磁盘 /。先有一个空的内存根,再决定要不要切成真根。
start_kernel
vfs_caches_init → mnt_init
init_rootfs() // do_mounts.c:tmpfs 还是 ramfs
init_mount_tree() // 空的 "/" 挂上 【会走】
kernel_init → kernel_init_freeable
populate_rootfs(initramfs.c) // 异步把 cpio 解进 "/"
wait_for_initramfs() // 等解压完;不是 do_mounts 入口
console_on_rootfs()
if (init_eaccess("/init") != 0)
prepare_namespace() // do_mounts.c 主戏 【本套不走】
kernel_execve("/init" 或 /sbin/init)
wait_for_initramfs() 在 init/initramfs.c,只等 cpio 铺完。
进 do_mounts.c 读挂真根,入口是 prepare_namespace() ,不是 wait_for_initramfs。
2. 核心抽象(读 prepare_namespace 盯这三句)
/可以换 :启动用空 rootfs 当容器;真根先挂到/root,再MS_MOVE+chroot换成/。这是挂载命名空间,不是驱动总线。- 根由 cmdline 策略选定 :
root=/rootfstype=/ro/rw/rootdelay/rootwait。硬件只是被查表。 - 挂根是消费者:块设备由 probe 异步造出;挂之前必须等「这一波 probe」结束。
3. 路 A ------ 你现在:initramfs 铺进空根(不切根)
空的内存 /(init_rootfs + init_mount_tree)
→ rootfs_initcall(populate_rootfs)
unpack_to_rootfs(cpio) // init/initramfs.c
→ wait_for_initramfs()
→ init_eaccess("/init") 成功
→ 跳过 prepare_namespace / mount_root
→ kernel_execve("/init") → exec /bin/sh
/ 从头到尾就是那块内存盘:先空,再被 initramfs.cpio.gz 填上 busybox。没有第二块盘。
不是 Root_RAM0。 那个是 root=/dev/ram 时 parse_root_device() 返回的老式 ramdisk 块设备。你没有 root=,也进不了 parse_root_device。
4. 路 B ------ BSP 常卡:prepare_namespace 切真根
仅当 没有可 exec 的 /init 时:
c
ramdisk_execute_command = NULL;
prepare_namespace();
流水线:
rootdelay 给慢速 USB/MMC 多等几秒
→ wait_for_device_probe() 等已经触发的 probe / deferred / async
→ md_run_setup()
→ parse_root_device(root=) 填 ROOT_DEV;找不到则为 0
→ initrd_load() 老式块设备 initrd;cpio 路径通常 false
→ rootwait ROOT_DEV==0 时轮询设备出现
→ mount_root(saved_root_name) 真根挂到 /root(还不是 /)
→ devtmpfs_mount
→ MS_MOVE + chroot 「/」换身份
mount_root() 按 ROOT_DEV 分发:NFS / CIFS / mtd·ubi / 普通块设备。
块设备:mknod /dev/root → do_mount_root(..., "/root")。
源 = root= 那个设备;目标 = 空 rootfs 里的 /root;fstype = rootfstype= 或逐个试。
之后 MS_MOVE 才把这棵子树变成 /。
你这条命令 没有 root=、有 /init,整段 B 不执行。
以后真板要复现:加盘 + root=/dev/vda,并且让 /init 不存在(或 rdinit= 指到无效路径)。
和 probe 的关系(栅栏,不是本条主模型)
wait_for_device_probe()(drivers/base/dd.c)只保证「已经排队/正在跑的 probe」结束,不管以后还会不会有新设备。
flush_work(deferred_probe_work)----EPROBE_DEFER那一轮wait_event(probe_waitqueue, probe_count==0)--- PID 1 睡在这里,schedule()在wait.hasync_synchronize_full()--- 异步 probe
叫醒:driver_probe_device() 里每次 probe 结束 probe_count-- + wake_up_all。
设备和驱动谁后上总线,谁触发这次 probe(device_add 或 driver_register)。匹配算法属 §1.2。
5. 按你这条命令:会走 / 不走
| 步骤 | 走? |
|---|---|
init_rootfs / 空 / |
走 |
populate_rootfs 解 cpio |
走 |
wait_for_initramfs |
走 |
prepare_namespace / mount_root / parse_root_device |
不走 |
execve("/init") → exec /bin/sh |
走 |
gdb:断 prepare_namespace / mount_root 不该命中。看根相关断 populate_rootfs、unpack_to_rootfs、kernel_execve。