arm中常说的bootrom和bootloader

我们首先需要区分这两个概念: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通常只需要了解其支持的启动模式即可。