1. 启动流程概述:从按下电源到操作系统加载

大家好,我是你们这门课的老朋友。今天咱们聊聊启动流程——说白了,就是计算机从你按下电源键,到屏幕上出现桌面或命令行提示符,这中间到底发生了什么。

我刚开始做嵌入式开发时,总觉得启动流程就是个黑盒子。按下电源,等几秒,系统起来了,完事。直到有一次,我调试一块 ARM 开发板,死活起不来,折腾了两天,最后发现是 bootloader 的链接脚本里少定义了一个段。从那以后,我再也不敢小看启动流程了。

什么是启动流程

启动流程,也叫 boot process,是计算机从硬件复位到操作系统完全接管硬件、提供用户交互环境所经历的一系列步骤。它不是一个单一的动作,而是一连串精心设计的接力赛。

你想想看,刚上电时,CPU 的寄存器是随机值,内存里空空如也,外设还没初始化。这时候,谁来告诉 CPU 第一步该做什么?谁来把操作系统从硬盘或 Flash 里搬到内存里?谁来建立中断向量表、页表、进程调度所需的那些数据结构?

这些问题的答案,就是启动流程要解决的核心问题。

核心要点:启动流程的本质是「从无到有」——从硬件复位后的混沌状态,逐步建立起一个可运行的操作系统环境。

为什么需要了解启动流程

我经常被问到:「我写应用层代码,知道启动流程有什么用?」嗯,这个问题我当年也问过我的导师。

后来我发现,了解启动流程至少有三个实实在在的好处:

  • 调试能力质的飞跃——系统起不来时,你能快速定位是硬件初始化失败、bootloader 配置错误、还是内核 panic。而不是盲目地换板子、换线、换电源。
  • 性能优化的钥匙——启动时间优化是嵌入式产品的硬指标。我做过一个工业相机项目,客户要求冷启动时间 < 2 秒。不懂启动流程,你根本不知道瓶颈在哪。
  • 安全加固的基础——Secure Boot、TrustZone、measured boot 这些安全机制,全都嵌入在启动流程的各个阶段。不了解流程,安全就是空谈。

我的建议:把启动流程当作一张地图来学。先看全局,再深入每个阶段。不要一上来就啃 CPU 复位向量表或者 bootloader 源码,容易迷失。

启动流程的宏观阶段划分

整个启动流程,我习惯把它分成四个大阶段。每个阶段都有明确的目标和输出。

下面这张图,是我自己画的一个宏观框架。建议你保存下来,后面每学一章,都回来对照一下,看看当前章节落在哪个位置。

启动流程宏观阶段划分 启动流程宏观阶段划分 阶段 1 硬件初始化 阶段 2 引导加载程序 阶段 3 操作系统内核 阶段 4 用户空间 关键动作 CPU 复位 时钟/内存/外设初始化 跳转到 bootloader 关键动作 加载内核镜像 设备树/ACPI 解析 跳转到内核入口 关键动作 架构初始化 驱动加载 挂载根文件系统 关键动作 init 进程启动 服务管理器启动 用户登录/桌面 上电复位 ~毫秒级 ~百毫秒级 ~秒级 用户态

阶段一:硬件初始化

按下电源键的那一刻,CPU 的复位逻辑开始工作。它会从一个固定的物理地址(比如 ARM 的 0x00000000 或 x86 的 0xFFFFFFF0)取出第一条指令。

这个阶段,CPU 还处于「裸奔」状态——没有中断、没有 MMU、没有缓存。它要做的第一件事,就是初始化最基本的硬件环境:

  • 设置 CPU 的工作模式(比如从实模式切换到保护模式)
  • 配置时钟和 PLL,让 CPU 和外设跑在正确的频率上
  • 初始化内存控制器,让 DDR/SRAM 可用
  • 设置栈指针,为 C 语言运行环境做准备

注意:这个阶段的代码通常用汇编语言编写,因为 C 运行时环境还没建立。我见过有人在这个阶段用 C 语言写初始化代码,结果栈指针没设对,一调用函数就崩溃。嗯,调试这种问题真的很痛苦。

阶段二:引导加载程序

硬件初始化完成后,控制权交给 bootloader。Bootloader 的任务很明确:找到操作系统内核,把它加载到内存里,然后跳转过去。

常见的 bootloader 有 U-Boot、GRUB、UEFI 等。它们通常分两个阶段:

阶段 存储位置 主要任务
Stage 1 ROM 或片上 SRAM 极小代码,初始化基本外设(如串口、NAND Flash 控制器),加载 Stage 2
Stage 2 外部存储(Flash、SD 卡、硬盘) 完整的 bootloader 功能:文件系统驱动、网络启动、内核加载、设备树传递

我个人习惯把 bootloader 比作「摆渡人」——它自己不运行操作系统,但它负责把操作系统安全地送到内存里。

阶段三:操作系统内核

Bootloader 跳转到内核入口后,内核开始接管系统。这个阶段,内核要做的事情非常多:

  • 建立页表,开启 MMU,进入虚拟内存模式
  • 初始化中断控制器和时钟中断
  • 扫描并初始化所有硬件设备(PCIe、USB、存储控制器等)
  • 挂载根文件系统(rootfs)
  • 启动第一个用户态进程(通常是 init)

我记得有一次调试一个 Linux 内核启动卡住的问题,内核日志停在「Waiting for root device」就不动了。查了半天,发现是 bootloader 传给内核的设备树里,根文件系统的分区号写错了。这种问题,不懂启动流程根本无从下手。

阶段四:用户空间

内核启动完 init 进程后,控制权就交给了用户空间。从这一刻起,内核的角色从「初始化者」变成了「服务提供者」。

用户空间的启动通常包括:

  • init 进程(systemd、SysV init、BusyBox init 等)读取配置文件
  • 启动各种系统服务(网络、日志、图形界面等)
  • 启动 getty 或显示管理器,等待用户登录

到了这个阶段,你就能看到熟悉的登录提示符或者桌面了。嗯,启动流程的接力赛,到这里才算跑完。

一个小技巧:在嵌入式 Linux 系统中,你可以通过内核启动参数 initcall_debug 来查看每个初始化函数的执行顺序和耗时。这对优化启动时间非常有帮助。

四个阶段的关系

这四个阶段不是孤立的,它们之间有严格的依赖关系:

  • 硬件初始化是基础,它没做好,后面全白搭
  • Bootloader 依赖硬件初始化提供的运行环境
  • 内核依赖 bootloader 正确加载和传递参数
  • 用户空间依赖内核完成设备初始化和文件系统挂载

任何一个环节出问题,系统都起不来。这也是为什么我建议你把启动流程当作一个整体来学——只看某一个阶段,很容易「只见树木,不见森林」。