Linux 启动流程学习笔记:start_kernel、根文件系统与 init
本文记录一次围绕 Linux 6.1 内核启动流程的学习过程,重点关注:
start_kernel()的整体职责- 根文件系统如何挂载
- PID 1 / init 进程如何创建
- initramfs 中的
/init与真实根文件系统中的/sbin/init的区别 - 根据实际串口启动日志判断本机启动路径
1. start_kernel 的整体位置
start_kernel() 位于:
1 | init/main.c |
它可以理解为 Linux 内核进入 C 世界后的总启动入口。架构相关的汇编和早期初始化完成后,会进入 start_kernel()。
简化后的主线如下:
1 | arch 入口汇编/早期 C |
start_kernel() 本身不会直接启动用户态程序。它的核心职责是把内核运行环境搭好,然后通过 rest_init() 创建后续负责完成启动的线程。
2. PID 1 是如何创建的
rest_init() 中有关键代码:
1 | pid = user_mode_thread(kernel_init, NULL, CLONE_FS); |
这会创建一个运行 kernel_init() 的线程。因为它是第一个将来进入用户态的线程,所以会成为 PID 1。
随后又创建:
1 | pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); |
这个线程通常是 PID 2,即 kthreadd,负责后续内核线程的创建和管理。
因此,PID 1 一开始并不是 /sbin/init,而是运行在内核态的 kernel_init()。后面它会通过 kernel_execve() 替换成真正的用户态 init 程序。
3. kernel_init 的主要工作
kernel_init() 的简化逻辑如下:
1 | kernel_init() |
其中 kernel_init_freeable() 会完成很多后期初始化:
1 | kernel_init_freeable() |
4. /init 是什么
/init 指的是 initramfs 解包后的内存根文件系统里的第一个用户态程序。
内核里默认配置:
1 | static char *ramdisk_execute_command = "/init"; |
也就是说,内核默认会优先尝试执行当前 rootfs 中的 /init。
这里的 rootfs 通常是内核早期挂载的内存文件系统,可以是 ramfs 或 tmpfs。initramfs 会被解包到这个 rootfs 中。
/init 可以是:
1 | shell 脚本 |
典型 initramfs /init 脚本可能会做:
1 | 1. 挂载 /proc、/sys、/dev |
5. 内核如何判断有没有 initramfs /init
关键代码在 kernel_init_freeable():
1 | if (init_eaccess(ramdisk_execute_command) != 0) { |
默认情况下,ramdisk_execute_command 是:
1 | /init |
因此判断逻辑是:
1 | 如果 /init 存在且可执行: |
如果启动参数指定了:
1 | rdinit=/xxx |
则 ramdisk_execute_command 会变成 /xxx,内核会优先检查并执行这个路径,而不是默认的 /init。
6. prepare_namespace 如何挂载真实根文件系统
当 initramfs 里没有可执行的 /init 时,内核会进入:
1 | prepare_namespace(); |
它位于:
1 | init/do_mounts.c |
简化流程:
1 | prepare_namespace() |
其中 mount_root() 会根据不同根文件系统类型分支处理:
1 | root=/dev/nfs -> NFS root |
对普通块设备或 UBI/UBIFS 根文件系统而言,最终会把真实根挂载到 /root,再通过 MS_MOVE 移动成真正的 /。
7. 用户态 init 的选择顺序
根文件系统准备好后,kernel_init() 会尝试执行 init 程序。
顺序大致是:
1 | ramdisk_execute_command 默认 /init,可由 rdinit= 指定 |
如果都失败,内核会 panic:
1 | No working init found |
执行用户态程序的函数是:
1 | kernel_execve(init_filename, argv_init, envp_init); |
一旦成功,PID 1 就从内核态的 kernel_init() 变成真正的用户态 init 程序。
8. initramfs /init 与 /sbin/init 的区别
两者容易混淆,但位置和角色不同。
1 | /init |
通常位于 initramfs 解包出来的内存 rootfs 中。它是内核优先尝试执行的早期用户态程序。
1 | /sbin/init |
通常位于真实根文件系统中,例如 eMMC、SD、UBIFS、NFS root 等。它是在真实根挂载完成后执行的系统 init。
判断依据:
1 | 日志出现 Run /init as init process |
9. 本机启动日志分析
本次串口日志文件:
1 | MobaXterm_COM9USB-Enhanced-SERIALCH343COM91_20260417_210416.txt |
关键启动参数:
1 | rw rootwait earlycon=uart8250,mmio32,0xff0a0000 console=ttyFIQ0 |
这说明根文件系统来自:
1 | MTD 分区 5 |
关键日志:
1 | ubi0: attaching mtd5 |
因此可以确定本机实际启动路径是:
1 | kernel_init() |
结论:
1 | 本次启动没有使用 initramfs /init。 |
10. /sbin/init 实际是 BusyBox
在 rootfs 构建目录中查看:
1 | realpath output/rootfs/target/sbin/init |
结果:
1 | /home/dky/workspaces_rk/buildroot/output/atk_dlrk3506/target/usr/bin/busybox |
这说明真实根文件系统中:
1 | /sbin/init -> /usr/bin/busybox |
内核实际执行的是:
1 | /sbin/init |
但由于它是指向 BusyBox 的符号链接,所以最终运行的是 BusyBox 的 init applet。
路径可以理解为:
1 | 内核执行 /sbin/init |
后续系统服务启动,例如:
1 | Starting syslogd |
通常来自 BusyBox init 根据 /etc/inittab 和相关启动脚本执行的结果。
11. 后续继续追踪方向
如果继续分析用户态启动流程,可以从真实 rootfs 中这些文件入手:
1 | /etc/inittab |
对当前系统而言,建议重点关注:
1 | output/rootfs/target/etc/inittab |
它们会解释 BusyBox init 如何启动 syslog、klog、udev、网络、weston、sshd 以及 userdata 等挂载逻辑。