嵌入式系统启动流程:从复位到内核
大家好,我是你们的Bootloader讲师。今天咱们来聊聊嵌入式系统上电后,到底发生了什么。说白了,就是从芯片复位到操作系统跑起来,这中间的一整套流程。
很多初学者觉得启动就是“上电→跑系统”,其实没那么简单。我刚开始做嵌入式那会儿,也以为Bootloader就是个搬运工,把内核从Flash搬到内存就完事了。直到有一次,板子死活起不来,查了三天才发现是复位向量表配错了位置……嗯,从那以后,我对启动流程的每个细节都不敢马虎。
2.1 启动流程全景图
先看一张整体流程图,把五个阶段串起来。你想想看,CPU从复位到执行用户程序,其实是一连串的“接力跑”——每一棒都有明确的任务。
核心观点:嵌入式启动不是一步到位,而是分阶段、分权限、分职责的递进过程。每一级Bootloader都在为下一级铺路。
2.2 CPU复位向量——一切从这里开始
芯片上电或复位后,CPU会去一个固定地址取第一条指令。这个地址就是复位向量。对于ARM Cortex-A系列,通常是0x00000000或0xFFFF0000;对于RISC-V,一般是0x80000000。
我记得第一次看ARM手册时,被“异常向量表”搞得晕头转向。其实说白了,复位向量就是一张表的第一个条目。CPU复位后,硬件强制PC指向这个地址,然后开始执行。
实战经验:很多SoC允许通过外部引脚(如BOOT_MODE)选择启动介质。比如从NAND Flash启动,复位向量指向片内ROM;从SD卡启动,则可能映射到不同的地址。设计板子时,一定要先确认启动引脚的硬件连接。
2.3 ROM代码——芯片出厂自带的“小管家”
ROM代码,也叫BootROM,是芯片出厂时固化在片内ROM里的一段小程序。它的任务很简单:初始化最基本的硬件(时钟、存储控制器),然后根据启动介质选择策略,把下一级代码加载到SRAM中。
这里有个关键点:BootROM通常只有几KB到几十KB,功能极其精简。它不会初始化DDR——因为DDR初始化需要大量配置参数,ROM里放不下。所以BootROM的目标是把一级Bootloader(SPL)加载到片内SRAM里。
| 特性 | BootROM | SPL | U-Boot |
|---|---|---|---|
| 存储位置 | 片内ROM(只读) | Flash特定分区 | Flash文件系统或分区 |
| 大小 | 4KB ~ 64KB | 16KB ~ 128KB | 256KB ~ 2MB |
| 运行环境 | SRAM | SRAM | DDR |
| 主要功能 | 加载SPL | 初始化DDR,加载U-Boot | 加载内核/文件系统 |
| 可修改性 | 不可修改 | 可更新 | 可更新 |
2.4 一级Bootloader(SPL)——承上启下的“小个子”
SPL,全称Secondary Program Loader,但习惯上叫它“一级Bootloader”。名字有点绕,你只要记住:它是BootROM加载的第一个用户可修改的程序。
SPL的核心任务就两个:初始化DDR和加载U-Boot。为什么它要做DDR初始化?因为BootROM太小,做不了;U-Boot太大,放不进SRAM。所以SPL必须先把DDR跑起来,然后把U-Boot从Flash搬到DDR里。
我在项目中遇到过一个问题:SPL里DDR时序参数配错了,导致U-Boot加载到DDR后,一运行就崩溃。查了两天才发现是某个寄存器少配了一位。嗯,从那以后我养成了一个习惯——每次改DDR参数,都会用示波器量一下DQS和CLK的相位关系。
避坑指南:我曾经因为SPL体积超限,导致BootROM加载失败。SPL必须严格控制在芯片SRAM大小以内(通常是64KB或128KB)。如果SPL太大,可以考虑裁剪功能,或者使用压缩方式加载。
2.5 二级Bootloader(U-Boot)——功能完整的“大管家”
U-Boot是目前最主流的二级Bootloader。它功能丰富:支持文件系统(FAT、ext4)、网络协议(TFTP、NFS)、命令行交互、环境变量存储等。说白了,U-Boot就是一个微型操作系统。
U-Boot的启动流程大致如下:
- 第一阶段(汇编):设置异常向量表,初始化CPU模式,关闭中断,设置栈指针
- 第二阶段(C语言):初始化串口、定时器、存储控制器,检测启动设备
- 第三阶段(命令行):读取环境变量,执行bootcmd命令,加载内核
我个人习惯在U-Boot里保留一个“救急”功能:如果正常启动失败,自动进入命令行模式,方便手动调试。这个设计救过我很多次——有一次文件系统损坏,就是靠U-Boot命令行从TFTP重新烧录才恢复的。
2.6 内核启动——最后的冲刺
U-Boot加载内核后,通过bootm或bootz命令跳转到内核入口。内核开始执行后,Bootloader的任务就结束了。
内核启动过程包括:
- 解压内核:如果内核是压缩的(zImage),先自解压
- 架构初始化:设置页表、MMU、中断控制器
- 设备驱动初始化:根据设备树(DTB)初始化外设
- 挂载根文件系统:从指定设备(如eMMC、NFS)挂载根分区
- 启动init进程:通常是
/sbin/init,然后启动用户空间服务
关键参数传递:U-Boot通过bootargs环境变量向内核传递启动参数,包括根文件系统位置、控制台设备、内存大小等。我曾经因为bootargs里写错了根设备,导致内核启动后挂载失败——屏幕上一堆VFS: Unable to mount root fs,看着就头疼。
2.7 本章小结
嵌入式启动流程,说白了就是一场接力赛:
- CPU复位向量:起跑线,硬件固定
- ROM代码:第一棒,芯片出厂自带
- 一级Bootloader(SPL):第二棒,初始化DDR
- 二级Bootloader(U-Boot):第三棒,加载内核
- 内核启动:冲刺,进入操作系统