我们首先需要区分这两个概念:BootROM和Bootloader。它们都是系统启动过程中的重要组成部分,但位于不同的层次,扮演不同的角色。
BootROM(启动只读存储器)
定义:BootROM是固化在芯片内部的一段不可修改的代码(通常是在芯片制造时就被写入,或者是一次性可编程的)。它是芯片上电后执行的第一段代码,因此也称为“芯片内置引导程序”。
位置:位于CPU的复位向量处(例如,ARM芯片复位后从地址0x00000000或0xFFFF0000开始执行,这个地址通常映射到BootROM)。
作用:
初始化芯片最基本的硬件,例如时钟、堆栈等。
根据芯片的启动模式(通过芯片的引脚电平或熔丝位设置)决定从哪个外部设备(如NAND Flash、SD卡、USB等)加载下一阶段的启动代码(通常是Bootloader)到内存中。
验证下一阶段代码的完整性(例如,通过校验和或数字签名),确保其可信。
跳转到加载到内存中的Bootloader继续执行。
特点:
代码大小有限,因为ROM容量通常很小(几KB到几十KB)。
不可修改,因此如果存在漏洞,无法通过软件升级修复(但可以通过后续阶段进行弥补)。
Bootloader(引导加载程序)
定义:Bootloader是一段存储在外部存储设备(如Flash、SD卡)上的软件,由BootROM加载到内存中并执行。它负责进一步的硬件初始化,并加载操作系统内核。
位置:存储在外部非易失性存储器中,由BootROM根据启动模式选择并加载到内存(通常是RAM)中。
作用:
完成更复杂的硬件初始化,例如内存控制器、更复杂的时钟树、外设等。
提供用户交互界面(例如,通过串口或显示屏),允许用户选择启动不同的操作系统或内核。
加载操作系统内核镜像(可能从硬盘、网络、SD卡等)到内存中,并传递必要的参数(如设备树、ATAGS等)。
验证操作系统内核的完整性,并在支持安全启动的系统上进行数字签名验证。
跳转到操作系统内核的入口点,将控制权交给操作系统。
特点:
代码大小相对较大(几十KB到几百KB,甚至几MB),因为功能更复杂。
通常可以升级(因为存储在外部可擦写存储器中)。
常见的Bootloader有:U-Boot、RedBoot、ARM的Bootmon等。
启动流程
典型的ARM系统启动流程如下:
a. 芯片上电,CPU从复位向量(BootROM的起始地址)开始执行BootROM代码。
b. BootROM根据启动引脚配置,从选定的启动设备(如eMMC)中加载Bootloader到内存(通常是内部SRAM或外部DRAM)。
c. BootROM验证Bootloader的签名(如果启用了安全启动),然后跳转到Bootloader。
d. Bootloader初始化更多的硬件,然后从存储设备(或网络)加载操作系统内核到内存,并跳转到内核。
两者区别总结
特性 BootROM Bootloader
存储位置 芯片内部ROM 外部存储(Flash, SD卡等)
代码大小 小(几KB-几十KB) 较大(几十KB-几MB)
可升级性 不可升级(一次性固化) 可升级(存储在可擦写存储器)
功能 最基本的初始化,加载Bootloader 复杂的初始化,加载操作系统内核
执行顺序 最先执行 第二段执行
示例(以常见的嵌入式Linux系统为例)
芯片上电后,执行BootROM。
BootROM从SD卡中读取U-Boot的前一部分(可能是一个SPL,即Secondary Program Loader)到内部SRAM,因为此时外部DRAM尚未初始化。
U-Boot的SPL初始化外部DRAM,然后将完整的U-Boot加载到DRAM并跳转执行。
U-Boot初始化更多硬件,然后从SD卡中加载Linux内核镜像和设备树到DRAM,并跳转到内核。
因此,BootROM和Bootloader是系统启动链中的两个环节,BootROM是芯片内置的、不可更改的,而Bootloader是存储在外部的、可升级的,两者协作完成从硬件上电到操作系统启动的过程。
在ARM架构的嵌入式系统中,BootROM和Bootloader是系统启动过程中两个关键但不同的组件。它们就像计算机启动时按下电源键到Windows/Linux完全加载之间的多个阶段。
BootROM(芯片内置启动代码)
BootROM是固化在ARM芯片内部的不可修改的只读存储器,包含芯片上电后执行的第一段代码。
特性与作用:
位置固定,无法修改:
在芯片制造时就烧录到硅片中
用户无法修改或擦除
位于芯片内部,不依赖外部存储
最小化的硬件初始化:
初始化最基本的时钟(通常是内部RC振荡器)
配置最小的内存(内部SRAM)
设置初始堆栈指针和程序计数器
启动模式检测:
读取芯片引脚(Boot mode pins)或熔丝位,决定从哪里加载下一级代码:
例如:
BOOT[2:0] = 000 → 从SPI Flash启动
BOOT[2:0] = 001 → 从SD卡启动
BOOT[2:0] = 010 → 从eMMC启动
BOOT[2:0] = 011 → 从USB下载
加载下一级引导程序:
从指定存储设备读取Bootloader的前几个扇区到内部SRAM
通常只加载很小的一段代码(几KB到几十KB)
安全启动的根信任:
验证Bootloader的数字签名(如果支持安全启动)
提供初始的加解密服务
简单类比:BootROM就像是计算机主板上的BIOS/UEFI固件,是硬件的一部分。
Bootloader(引导加载程序)
Bootloader是存储在外部非易失性存储器(如Flash、SD卡)中的可升级的软件,负责完成更复杂的初始化并加载操作系统。
常见Bootloader示例:
U-Boot:最流行的开源Bootloader
ARM Trusted Firmware:ARM官方参考实现
Little Kernel (LK):Android设备常用
RedBoot:传统嵌入式系统使用
主要功能:
// 简化版Bootloader执行流程
void bootloader_main(void) {
// 1. 早期硬件初始化
init_clocks(); // 设置PLL,系统主频
init_dram(); // 初始化外部DDR内存
init_serial(); // 初始化调试串口
// 2. 环境与命令行
setup_env(); // 读取环境变量
if (serial_key_pressed()) {
enter_cli(); // 进入命令行交互模式
}
// 3. 加载操作系统
kernel = load_image("zImage"); // 从存储设备读取内核
dtb = load_image("dtb"); // 加载设备树
// 4. 设置启动参数
setup_boot_args(kernel, dtb, cmdline);
// 5. 安全验证(可选)
if (secure_boot_enabled) {
verify_signature(kernel); // 验证内核签名
}
// 6. 跳转到操作系统
jump_to_kernel(kernel_entry);
}
详细功能分解:
1. 完整硬件初始化
配置系统时钟树(PLL、分频器等)
初始化外部DDR内存控制器
设置外设控制器(如NAND、SDIO、USB、Ethernet等)
2. 存储设备驱动
实现Flash驱动(NOR/NAND)
实现文件系统支持(FAT、EXT4等)
支持网络引导(TFTP、HTTP)
3. 交互式命令行
# U-Boot命令行示例
U-Boot > printenv # 打印环境变量
U-Boot > setenv ipaddr 192.168.1.100
U-Boot > tftp 0x80008000 zImage # 从网络加载内核
U-Boot > bootm 0x80008000 # 启动内核
4. 操作系统加载
解析内核镜像格式(zImage、uImage、FIT等)
加载设备树二进制(DTB)
设置启动参数(ATAGS或FDT)
5. 高级功能
恢复模式:允许刷写新固件
快速启动:跳过不必要的初始化
多重引导:选择不同的操作系统
典型启动流程对比
让我们用一个实际例子说明两者的分工(以STM32F7系列启动Linux为例):
flowchart TD
A[上电/复位] --> B[BootROM执行]
B --> C{检测启动模式}
C -->|BOOT0=0| D[从主Flash启动
用户应用]
C -->|BOOT0=1| E[从系统存储器启动
内置Bootloader]
subgraph F [BootROM阶段]
B
C
E
end
E --> G[加载SPL
(U-Boot第一阶段)]
subgraph H [Bootloader阶段]
G
I[SPL初始化
时钟, SRAM, 串口]
J[加载完整U-Boot
到外部RAM]
K[U-Boot初始化
DRAM, 网络, 文件系统]
L[加载Linux内核
和设备树]
M[跳转到内核]
end
I --> J
J --> K
K --> L
L --> M
时间线视角:
时间 | 组件 | 位置 | 主要任务
-----------|------------|--------------|------------------
t=0-10ms | BootROM | 芯片内部ROM | 引脚检测,时钟初始化
t=10-50ms | Bootloader | 外部Flash | DRAM初始化,外设设置
t=50-100ms | Bootloader | 外部Flash | 加载内核,准备启动参数
t=100ms+ | Linux内核 | 外部Flash | 启动完成,运行用户空间
Bootloader的演进:多阶段启动
现代复杂系统通常使用多阶段Bootloader:
SPL(Secondary Program Loader):
由BootROM加载到内部SRAM
初始化外部DRAM
加载完整的Bootloader到DRAM
TPL(Tertiary Program Loader):
在某些SoC中存在
进一步初始化复杂外设
主Bootloader(如U-Boot):
功能完整的引导程序
支持命令行、网络、文件系统
ARM特定流程:
BootROM → SPL(EL3/安全监控模式)
→ ATF(ARM Trusted Firmware,可选)
→ U-Boot(EL2/非安全模式)
→ Linux内核(EL1/内核模式)
实际开发中的注意事项
1. Bootloader调试
// 早期调试技巧
void early_debug(char *msg) {
// 在DRAM初始化前,使用串口输出
// 可能需要轮询方式,因为中断还没初始化
while (*msg) {
while (!(UART->SR & UART_SR_TXE));
UART->DR = *msg++;
}
}
2. Bootloader大小优化
# U-Boot配置示例,减小尺寸
CONFIG_SYS_THUMB_BUILD=y # 使用Thumb指令集
CONFIG_SPL=y # 启用SPL
CONFIG_SPL_SIZE_LIMIT=0x10000 # 限制SPL为64KB
3. 安全启动实现
// 验证引导链
bool verify_boot_chain(void) {
// 1. BootROM验证SPL签名
// 2. SPL验证U-Boot签名
// 3. U-Boot验证内核签名
// 4. 内核验证根文件系统
return check_signature(image, public_key);
}
总结:关键区别
特性
BootROM
Bootloader
位置
芯片内部ROM
外部存储(Flash/SD)
可修改性
不可修改
可升级、可替换
大小
很小(几KB)
较大(几十KB到几MB)
功能
最基本初始化
完整硬件初始化
信任根
是(安全起点)
可被验证的组件
开发者
芯片厂商
系统开发者/开源社区
简单记忆:
BootROM = "硬"引导,芯片自带,固定不变
Bootloader = "软"引导,用户可定制,功能丰富
在嵌入式开发中,Bootloader是开发者需要重点关注和定制的部分,而BootROM通常只需要了解其支持的启动模式即可。