2、嵌入式系统启动流程:CPU复位行为、启动模式选择、启动介质
大家好,我是你们的讲师。今天咱们来聊聊嵌入式系统上电后,CPU到底在干什么。很多人觉得启动就是“上电-跑代码”,其实没那么简单。CPU复位那一刻,硬件世界才刚刚苏醒。
2.1 CPU复位行为:从“混沌”到“有序”
CPU复位,说白了就是给处理器一个“重启”信号。我习惯把复位比作“系统格式化”——所有寄存器回到默认值,流水线清空,状态机归零。
复位信号通常有两种:
- 上电复位(POR):刚通电时,电源稳定后自动触发。
- 外部复位(External Reset):复位引脚拉低,手动或看门狗触发。
复位后,CPU会做三件固定的事:
- 读取复位向量:从固定地址(比如0x00000000或0xFFFF0000)取出第一条指令地址。
- 初始化核心寄存器:比如堆栈指针SP、程序计数器PC、状态寄存器CPSR。
- 跳转到启动代码:开始执行Bootloader或固化在ROM里的引导程序。
重点来了:不同架构的复位向量地址不一样。ARM Cortex-M系列默认从0x00000000取向量表,而Cortex-A系列则从0xFFFF0000或0x00000000取。我在项目中遇到过,有人把Cortex-M的代码直接烧到Cortex-A上,结果复位后直接跑飞——因为向量表地址对不上。
2.2 启动模式选择:CPU怎么知道从哪启动?
你想想看,CPU复位后,它怎么知道该从Flash读代码,还是从SD卡读?答案就在启动模式选择引脚上。
大多数嵌入式SoC会提供2~4个启动模式引脚(比如BOOT0、BOOT1)。这些引脚在复位时被采样,决定CPU的启动介质。我见过一个典型的配置:
| BOOT1 | BOOT0 | 启动介质 |
|---|---|---|
| 0 | 0 | 内部Flash(主闪存) |
| 0 | 1 | 系统存储器(BootROM) |
| 1 | 0 | 内部SRAM |
| 1 | 1 | 保留/外部介质 |
嗯,这里要注意:采样只在复位瞬间发生。复位后你再改变引脚电平,CPU也不会重新选择启动介质。我曾经调试一块板子,发现怎么都进不了下载模式,折腾半天才发现是复位后BOOT引脚被拉高了——其实复位时它还是低电平。
我的经验:设计电路时,BOOT引脚最好加上拉或下拉电阻,并且留出测试点。量产时用默认值,调试时用跳线帽切换。这样既稳定又灵活。
2.3 启动介质:NOR/NAND Flash、SD卡、eMMC
启动介质,就是存放Bootloader和操作系统的地方。不同的介质,读写方式、速度、可靠性都不一样。我按常用程度给大家捋一遍。
2.3.1 NOR Flash
NOR Flash支持XIP(就地执行),也就是说CPU可以直接在Flash上运行代码,不需要先拷贝到RAM。这玩意儿启动速度最快,但容量小、价格贵。我早期做路由器时,Bootloader就放在2MB的NOR Flash里,上电瞬间就能跑起来。
- 优点:XIP支持,启动快,可靠性高。
- 缺点:容量小(通常8MB以下),写入慢。
- 典型应用:BIOS、UEFI、小容量Bootloader。
2.3.2 NAND Flash
NAND Flash容量大、价格低,但不能XIP。CPU必须先把Bootloader的前几KB拷贝到内部SRAM或DDR里,再跳转执行。这个过程叫“NAND启动”。
为什么只能拷贝前几KB?因为NAND Flash有坏块,而且读取需要复杂的控制器。SoC内部通常固化了一个小BootROM,专门负责从NAND读取前4KB或8KB到SRAM。这部分代码再负责加载完整的Bootloader。
避坑指南:我曾经在NAND启动上栽过跟头。当时Bootloader编译出来有200KB,但SoC的BootROM只拷贝前8KB。结果那8KB里没有包含NAND控制器初始化代码,导致后续读取失败。解决方案是:前8KB必须包含最基础的硬件初始化,并且能加载剩余代码。
2.3.3 SD卡/eMMC
SD卡和eMMC本质上是NAND Flash加上一个控制器,对外提供块设备接口。启动时,SoC通过SDIO或eMMC接口读取第一个扇区(通常是MBR或GPT),然后加载Bootloader。
这类介质的好处是:
- 容量大:从几GB到几百GB。
- 标准化:接口统一,换卡不用改驱动。
- 支持分区:可以放多个系统或数据。
但缺点也很明显:启动速度比NOR Flash慢,因为需要初始化控制器、解析分区表。我做过一个项目,用eMMC启动Linux,从上电到看到内核日志,花了将近3秒——其中1.5秒都在初始化eMMC控制器。
2.4 启动流程实战:一个典型ARM Cortex-A的启动链
说了这么多理论,咱们看个实际例子。以我常用的i.MX6ULL为例,它的启动流程是这样的:
- CPU复位:从0x00000000读取复位向量,跳转到BootROM。
- BootROM执行:根据eFuse或GPIO引脚选择启动介质。比如检测到SD卡,就从SD卡读取前4KB到内部SRAM。
- SPL(Secondary Program Loader)执行:这4KB代码初始化DDR控制器,然后从SD卡加载完整的U-Boot到DDR。
- U-Boot执行:初始化更多外设(网络、USB、显示),然后加载Linux内核和设备树。
- 内核启动:挂载根文件系统,启动用户空间。
你看,整个启动链就像接力赛——每一棒只做自己该做的事,然后交给下一棒。我习惯把BootROM叫做“第一级引导”,SPL叫“第二级引导”,U-Boot叫“第三级引导”。
关键点:每一级引导程序的大小和功能都有限制。BootROM通常只有几十KB,SPL最多几十KB,U-Boot可以到几百KB。设计时一定要算好尺寸,否则放不下。
2.5 总结与思考
好了,这一章的内容就这些。咱们回顾一下:
- CPU复位后,从固定地址取向量,执行启动代码。
- 启动模式引脚决定从哪个介质启动,采样只在复位瞬间。
- NOR Flash适合快速启动,NAND Flash容量大但需要拷贝,SD卡/eMMC标准化但启动慢。
- 实际启动链是多级接力,每一级都有尺寸和功能限制。
下一章,咱们会深入BootROM的内部实现,看看那几十KB的代码到底干了什么。到时候我会拿一个真实的BootROM反汇编代码来讲解,保证让你大开眼界。
我是你们的讲师,咱们下节课见。