深入 ARM Linux 系统启动全流程¶
对于每一个嵌入式Linux开发者来说,从按下开发板电源,到终端打印出熟悉的
[root@board ~]#提示符,这期间发生的一切,既熟悉又神秘。它就像一个“黑盒”,我们每天都在使用,却很少有人能完整地描述出其中的每一个细节。
今天,就让我们化身一名数字世界的侦探,带上放大镜,一步步追踪电流的足迹,完整地走一遍ARM Linux系统的启动全流程。理解这个过程,你将对整个系统有“上帝视角”般的认知。
核心流程概览¶
整个启动过程可以被清晰地划分为几个核心阶段,像一场环环相扣的接力赛:
BootROM ➡️ Bootloader (U-Boot) ➡️ Linux Kernel ➡️ Root File System & init Process ➡️ Shell
Stage 1: BootROM - 芯片的“第一声啼哭” 👶¶
当你按下电源开关,电流涌入SoC(System on Chip)的那一刻,CPU并不是茫然的。CPU上电后会从一个预设的、固化在芯片内部的内存地址(如0x00000000)开始执行指令。
这个地址指向的就是 BootROM,也叫 SPL (Secondary Program Loader) 的前身,它是芯片厂商写死在芯片内部的一小段程序,我们无法修改。
- 它的使命只有一个:找到并加载下一阶段的引导程序(Bootloader)。
- 它从哪里找? BootROM会按照芯片预设的顺序,去检查不同的启动介质,如 eMMC、SD卡、NAND Flash、SPI Flash,甚至USB或UART。这个检查顺序通常可以通过几个特殊的
BOOT引脚电平来配置。 - 它做什么? 一旦在某个介质的特定位置(如SD卡的第一个扇区)找到了合法的Bootloader镜像,BootROM就会把它加载到芯片内部的SRAM(一块容量很小但速度飞快的内存)中,然后跳转到SRAM的入口地址,将控制权完全交给Bootloader。
至此,BootROM的使命完成,它像一个尽职的助产士,悄然退场。
Stage 2: U-Boot - 万能的“瑞士军刀” 🛠️¶
U-Boot (Universal Boot Loader) 是嵌入式世界中应用最广泛的Bootloader。它功能强大,像一把瑞士军刀,负责在内核启动前,为系统做好一切准备工作。
当控制权交给U-Boot后,它主要做三件大事:
1. 硬件初始化¶
此时的硬件环境还非常“原始”。U-Boot的首要任务就是初始化必要的硬件:
- DDR内存初始化:这是最关键的一步。Linux内核体积庞大,必须在DDR内存中运行。U-Boot会根据配置参数,初始化DDR控制器,让大容量的DDR内存变得可用。
- 时钟配置:将CPU、DDR等部件的时钟频率提升到正常工作速度。
- 外设初始化:初始化串口(用于打印调试信息)、Flash控制器(用于读内核)等。
2. 加载内核与设备树¶
准备好硬件环境后,U-Boot就要去加载真正的主角——Linux内核了。
- 它会从预设的存储介质(如eMMC的分区)中,读取 Linux内核镜像(如
zImage或uImage)到DDR内存的某个地址。 - 同时,它还会读取 设备树文件(DTB, Device Tree Blob) 到DDR的另一个地址。设备树就像一张“硬件说明书”,它用一种特定的格式向内核描述了这块开发板上所有的硬件信息(如CPU有几个核、内存多大、有哪些I2C设备、GPIO如何连接等)。这使得Linux内核可以“一份代码,适配万千硬件”。
3. 传递启动参数并“交棒”¶
万事俱备,只欠东风。在启动内核前,U-Boot会准备好启动参数(boot arguments)。这些参数会告诉内核一些重要的初始信息,例如:
console=ttyS0,115200:告诉内核使用哪个串口作为控制台,以及波特率。root=/dev/mmcblk0p2:告诉内核根文件系统在哪个分区。init=/sbin/init:告诉内核第一个要运行的用户程序是什么。
最后,U-Boot执行 bootm 或 booti 命令,将CPU的控制权和设备树的内存地址,正式“交棒”给Linux内核。从此,U-Boot的使命也完成了。
Stage 3: Linux Kernel - “沉睡巨人”的苏醒 🐘¶
当CPU的PC指针跳转到内核在DDR中的入口地址时,Linux这个“沉睡的巨人”开始苏醒。这个过程极为复杂,但我们可以抓住主线:
- 内核自解压:我们加载的
zImage是一个压缩过的内核。所以第一步,内核会先进行自解压,将真正的内核代码释放到DDR的高地址空间。 - 解析设备树:内核会读取U-Boot传递过来的DTB文件地址,解析它,从而在内存中建立起描述所有硬件设备的数据结构。
- 初始化核心子系统:依次初始化内核的各大核心模块,如:
- MMU(内存管理单元):开启分页机制,建立虚拟地址与物理地址的映射,这是现代操作系统的基石。
- 中断控制器:初始化GIC,为响应外部中断做好准备。
- 调度器(Scheduler):初始化进程调度算法。
- 时钟和定时器:为系统提供心跳。
- 初始化驱动程序:内核会根据设备树中的描述,依次探测(probe)并初始化各种硬件设备的驱动程序。此时,你会在串口终端上看到密密麻麻的、熟悉的驱动加载信息。
- 挂载根文件系统(RootFS):当核心驱动都加载完毕后,内核会根据U-Boot传递的
root=参数,找到并挂载第一个文件系统——根文件系统。根文件系统包含了Linux世界中所有的应用程序、库文件和配置文件(/bin,/lib,/etc等)。
Stage 4 & 5: init进程与bash终端 - 用户世界的开端 🚀¶
内核的初始化工作到挂载根文件系统就基本结束了。但此时系统还不能交互,因为它还没有运行任何用户程序。
- 启动
init进程:内核会启动在根文件系统中找到的第一个用户进程——/sbin/init。这个进程的PID(进程ID)为1,它是系统中所有其他用户进程的“始祖”。 init执行启动脚本:init进程会读取它的配置文件(对于传统的SysVinit是/etc/inittab,对于现代的systemd则是各种.service文件),然后按照配置,启动各种系统服务,如网络服务、日志服务、SSH服务等。- 启动
getty并显示登录提示:在系统服务的最后,init进程会执行getty程序,它会在指定的终端(如ttyS0)上打开一个端口,并显示我们熟悉的login:提示。 - 用户登录与Shell启动:当你输入用户名和密码,
login程序会验证它们。验证通过后,它会根据/etc/passwd文件中的配置,为该用户启动一个Shell(通常是/bin/bash)。
bash启动后,会首先显示命令提示符,如 [root@myboard ~]#。
至此,从按下电源到bash终端的漫长旅程终于走完。你可以在这个终端里敲下ls -l,感受掌控整个系统的乐趣了。
总结¶
| 阶段 | 核心角色 | 主要任务 | 成果 |
|---|---|---|---|
| 1 | BootROM | 从指定介质加载U-Boot到SRAM | U-Boot开始在SRAM中运行 |
| 2 | U-Boot | 初始化DDR,加载内核和设备树到DDR | 内核和设备树准备就绪 |
| 3 | Linux Kernel | 初始化自身,加载驱动,挂载根文件系统 | 运行第一个用户进程init |
| 4 | init Process | 执行启动脚本,启动系统服务 | 系统服务运行,准备好登录环境 |
| 5 | Login & Shell | 验证用户,启动bash | 用户成功登录,看到命令提示符 |
希望这次“深度游”能帮助你彻底理清ARM Linux的启动脉络。下一次当你的开发板无法启动时,你就能够根据终端停留在哪个阶段的打印信息,快速定位问题所在了。