这不是一套“Linux 命令入门”
这套专辑讨论的是另一个问题:一台机器从上电开始,究竟如何一步步变成一个可以登录、运行程序的 Linux 系统?
本专辑以 RISC-V(主要使用 QEMU virt 机器) 作为主线架构。Linux 的通用子系统会尽量抽象地讨论,但凡涉及启动入口、寄存器、异常、中断、页表、设备树和平台初始化,默认都以 RISC-V 的实现为准;其他架构只在必要时用于对比,不作为本专辑的主要实验目标。
本专辑与 RISC-V:从指令集到系统实战 以及 RISC-V CPU:从五级流水线到乱序多发 互相衔接:CPU 专辑解释指令如何在硬件中执行,RISC-V 专辑解释架构语义,Linux 专辑解释操作系统如何使用这些机制。
我们不会只停留在启动流程图,也不会把源码当成只能浏览的百科全书。后续每个模块都会同时沿着两条线推进:
- 源码线:定位入口、调用关系、关键数据结构和控制权交接;
- 实验线:用 QEMU、交叉编译工具链、Buildroot 和自制 rootfs 构造可重复的启动环境,再用日志、GDB 和
/proc验证源码中的行为。
这里的“启动”也不只是按下电源键之后的几秒钟,而是一条跨越固件、引导加载器、内核和用户空间的控制权链路。
六个需要真正理解的模块
固件(BIOS / UEFI) ↓引导加载器(GRUB 等) ↓Linux 内核入口与架构初始化 ↓内核通用初始化(start_kernel) ↓initramfs 与根文件系统切换 ↓init / PID 1 与用户空间这六个模块是专辑的主线。它们不是互相独立的知识点:前一个模块准备的寄存器、内存布局、启动参数或文件系统,会直接决定后一个模块从哪里开始工作。
1. 固件:CPU 最初从哪里执行
CPU 复位后不会读取 Linux 源码,而是从体系结构规定的复位地址开始执行固件。BIOS 或 UEFI 负责完成最早期的硬件初始化、设备发现和启动介质选择。
源码学习上,这一阶段需要关注的是启动协议和内存交接,而不是把某个具体主板的固件全部读完。实验中我们会通过 QEMU 的机器模型观察:固件、设备树或 ACPI、内存布局分别为后续阶段提供了什么。
2. 引导加载器:内核是怎样被放进内存的
引导加载器读取配置,加载内核镜像和 initramfs,并把内核命令行传递给内核。启动后可以先观察实际参数:
cat /proc/cmdline在实战中,我们会从直接使用发行版的 GRUB 开始,逐步切换到 QEMU 的 -kernel、-append 和 -initrd 参数,减少隐藏行为,明确看到“谁加载了什么”。
3. 内核入口:架构代码如何进入 C 代码
内核最早的入口由架构决定。以 RISC-V 的 QEMU virt 机器为例,我们会从启动汇编、链接脚本和 QEMU 的加载地址开始,追踪控制权如何进入架构相关的初始化函数。
重点不是背下某个版本的函数名,而是建立一套读源码的方法:
- 从链接脚本和符号表确认入口地址;
- 从汇编确认栈、寄存器和启动参数在哪里准备;
- 从
setup_arch等架构函数确认平台初始化; - 沿调用关系进入通用启动路径。
可调试的 vmlinux、System.map、objdump 和 GDB 会是这一部分的主要工具。
4. start_kernel:内核如何初始化基础模块
不同架构最终会进入 Linux 的通用初始化路径,核心观察点是 init/main.c 中的 start_kernel。它会组织早期内核子系统的初始化,例如内存管理、调度器、中断、定时器、进程管理、驱动模型和文件系统支持。
我们会把启动日志和源码中的初始化顺序对应起来,而不是把 start_kernel 当成一份孤立的函数列表:
dmesg | lessjournalctl -b -k实验中还会通过修改内核配置、增加最小的调试输出或设置 GDB 断点,验证某个初始化模块何时发生、依赖什么条件,以及失败时控制权会落在哪里。
5. initramfs:在真正根文件系统出现之前
内核可能还不能直接挂载真正的根文件系统,因为磁盘驱动、文件系统驱动、RAID、LVM 或加密卷支持尚未准备好。initramfs 提供一个位于内存中的临时用户空间,负责加载模块、探测设备并完成根文件系统切换。
这部分会用两种方式对照:
- 用 Buildroot 生成一个最小 Linux 系统和 rootfs;
- 手动制作一个包含
/init、BusyBox 和必要目录的 initramfs。
我们会在 /init 中打印环境、挂载 /proc 和 /sys,观察内核传入的参数,再执行 switch_root 或直接启动用户空间程序。这样可以明确区分:内核初始化和早期用户空间脚本并不是同一件事。
6. init / PID 1:控制权交给用户空间
根文件系统准备好后,内核会尝试执行用户空间的第一个进程。它的进程号是 1,传统名称是 init,现代发行版中常见的实现是 systemd。
可以在真实系统中观察这一交接:
ps -p 1 -o pid,comm,argsreadlink /proc/1/exe在 QEMU 实验中,我们会先让自己的 /init 成为 PID 1,再替换成 BusyBox init 或 Buildroot 生成的 init,比较它们如何启动 shell 和基础服务。这样读者能直接看到 PID 1 的特殊职责,而不只是记住“systemd 是第一个进程”。
后续实验路线
专辑会按启动链路逐步展开,预计顺序如下:
- 建立 QEMU 实验环境:准备架构、交叉编译器、Linux 源码和 GDB;
- 追踪内核入口:从链接脚本、启动汇编到
setup_arch; - 分析
start_kernel:把通用初始化顺序和启动日志对应起来; - 制作 initramfs:从手写
/init到 BusyBox; - 使用 Buildroot 构建系统:理解 toolchain、rootfs、内核和启动参数之间的关系;
- 追踪 PID 1:比较自制 init、BusyBox init 和 systemd 的启动行为;
- 加入调试与故障实验:故意移除驱动、修改根设备参数,定位系统为什么卡在某个启动阶段。
每一篇文章都应当留下一个可以运行的实验和一个可以回到源码验证的问题。最终目标不是得到一张漂亮的启动流程图,而是当系统停在某条日志、某个断点或某个 exec 上时,知道应该去源码中的哪一层寻找答案。