背景
一个能在实验室跑通的 Bootloader,和一个能出货一百万台的"企业级" Bootloader,差的不是几百行代码,而是鲁棒性和安全性这两条底线。
在企业级语境里,我们不说"内存",只说 Flash(闪存)——因为代码断电后必须不丢失。 Bootloader 的核心使命只有两条:永远能启动,永远不能变砖。
最常见的入门理解是"三段式"架构(Bootloader + 出厂区 + 运行区 + 参数区),这其实就是业内说的 Golden Image / Recovery 架构。但在真实产品里,根据 Flash 容量和业务需求,还会演化出 A/B 双区、外部 Flash 缓存等多种形态。这篇笔记把它们一次梳理清楚。
分析
一、三段式架构详解(Golden Image / Recovery)
这种架构把 MCU(如 STM32)的内部 Flash 切成四块:
-
Bootloader 区(固化区)
- 系统上电执行的第一行代码:硬件初步初始化、检查升级标志、校验固件完整性、执行跳转。
- 企业级要求:绝对不能在普通 OTA 中被改写。通常用 STM32 的硬件 Write Protection 把扇区锁死——它坏了,设备就成了砖。
-
Factory App 区(出厂 / 黄金镜像区)
- 出厂烧录的、经过极严格测试的、功能最基础但绝对稳定的版本。
- 当升级版本出现致命 Bug(如无限重启),或用户长按"恢复出厂"键时,Bootloader 放弃当前版本、跳转回这里,确保设备永远活着、永远能联网重新拉新固件。
-
Active App 区(运行 / 升级区)
- 当前正在运行的最新业务代码。日常跑的都是它。
-
Data / Flag 区(参数区)
- 存 Bootloader 和 App 之间通信的"暗号"(Magic Word)。
- 例:App 下载完新固件后写入
0x5A5A,Bootloader 启动时读到该值就知道要执行升级替换。
典型执行流程:
上电 → Bootloader 启动 → 读参数区 → 有升级标志?→ 搬运覆盖 → 有恢复出厂标志?→ 擦 Active 区从 Factory 复制,或直接跳 Factory → 校验目标区 CRC / Hash → 重映射中断向量表(
SCB->VTOR)→ 跳转执行
二、其他两种主流架构
1. 双区 A/B 面架构(Dual Bank / A/B Update)
Flash 够大的项目,现在最流行。
- 布局: Bootloader + App Slot A + App Slot B + 数据区
- 特点: 没有"出厂区"。A 区运行时后台把新固件下载进 B 区,下载完重启,Bootloader 校验 B 区后直接跳 B 运行;下次升级再下到 A,交替进行。
- 优势:
- 无缝升级:不需要 Bootloader 搬运代码,搬运是断电变砖的最大来源。
- 升级极快:少一次擦写。
- 自带回滚(Rollback):B 区启动失败时,重启后 Bootloader 自动跳回旧的 A 区。
2. 外部 Flash 下载架构(Download Partition)
内部 Flash 小到装不下两套代码时用。
- 布局: 内部 = Bootloader + Active App;外部挂一片便宜的 SPI Flash 做 Download 缓存区。
- 特点: App 运行时把新固件下到外部 Flash,重启后 Bootloader 把外部数据拷贝并覆盖到内部 Active App。
- 劣势: 覆盖过程一旦掉电,内部 App 直接损坏。所以 Bootloader 必须实现断点续传 / 防掉电恢复。
三、三种架构怎么选
| 维度 | 三段式(Recovery) | A/B 双区 | 外部 Flash |
|---|---|---|---|
| Flash 占用 | 中 | 大 | 内部小 + 外部芯片 |
| 升级速度 | 中(要搬运) | 快(直接跳) | 慢(要覆盖) |
| 变砖风险 | 中 | 极低 | 高(依赖防掉电) |
| 回滚能力 | 回 Factory 基础版 | 回上个版本 | 弱 |
| BOM 成本 | 低 | 中 | 中(多一片 Flash) |
实现
关键代码骨架(以 STM32 为例)
跳转前必须做的事:关中断 → DeInit 外设 → 重映射 VTOR → 设栈指针 → 跳转。任何一步漏掉都会埋玄学雷。
// 跳转到指定 App 地址,注意调用前必须保证目标区代码已校验通过
typedef void (*app_entry_t)(void);
static void jump_to_app(uint32_t app_addr) {
// 1) 关闭所有中断,避免 App 启动瞬间被遗留中断打断
__disable_irq();
// 2) 反初始化 Bootloader 用过的外设(串口、定时器、DMA...)
HAL_DeInit();
// 3) 清除可能 pending 的中断,复位 SysTick
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
for (uint32_t i = 0; i < 8; i++) {
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
// 4) 重映射中断向量表到 App 起始地址
SCB->VTOR = app_addr;
// 5) 取 App 的 MSP(栈顶)并设置
uint32_t app_msp = *(volatile uint32_t *)(app_addr);
uint32_t app_reset = *(volatile uint32_t *)(app_addr + 4);
__set_MSP(app_msp);
// 6) 跳转到 App 的 Reset_Handler
__enable_irq();
((app_entry_t)app_reset)();
}
升级标志的"双 Magic + 状态机"写法
为了防止单一标志被误擦写或写到一半掉电,参数区通常存两个 Magic Word + 一个状态码,三者交叉判断。
// 参数区结构(放在独立扇区,App 与 Boot 都能访问)
typedef struct {
uint32_t magic_head; // 0xA5A5A5A5
uint32_t state; // IDLE / READY_TO_UPGRADE / TRYING / CONFIRMED
uint32_t target_slot; // 0 = A, 1 = B
uint32_t fw_size;
uint32_t fw_crc32;
uint32_t boot_retry; // 试运行重启计数
uint32_t magic_tail; // 0x5A5A5A5A
} boot_param_t;
Flash 地址规划示例
# STM32F4 / 1MB Flash,A/B 架构示例(仅作参考)
0x0800_0000 - 0x0800_FFFF (64K) Bootloader [Write-Protect]
0x0801_0000 - 0x0807_EFFF (~440K) App Slot A
0x0808_0000 - 0x080E_EFFF (~440K) App Slot B
0x080F_0000 - 0x080F_FFFF (64K) Data / Param [双区冗余]
三、什么是真正的"企业级"?
1. 安全启动(Secure Boot)与签名校验
企业级设备不能随便刷入别人的固件。Bootloader 在跳转前:
- 先算 CRC32 / SHA-256:防文件传输损坏;
- 再校验 RSA / ECDSA 数字签名:防黑客篡改。
只有用企业私钥签过名的固件,Bootloader 才允许运行。公钥写死在 Bootloader 里,受 Write Protection 保护。
2. 看门狗与自动回滚(Watchdog & Rollback)
升级后新代码逻辑死锁、一启动就卡死怎么办?
跳转前开启硬件独立看门狗(IWDG),并把参数区状态置为 TRYING。新 App 必须在规定时间内:
- 周期性"喂狗";
- 业务自检通过后,把状态从
TRYING改为CONFIRMED。
若新代码卡死导致看门狗复位,Bootloader 再次启动时发现 state == TRYING 且 boot_retry >= N,就判定升级失败,自动回滚到旧版本或 Factory。
3. 防掉电设计(Power-Loss Protection)
Bootloader 在擦写 Flash 的几秒钟里,用户拔电源绝对不能变砖。状态机要做到:
- 每次上电都能从参数区识别"上次是拷贝到一半断电了";
- 续上未完成的拷贝,或者回退到一致状态;
- 写参数区时用双备份 + 序号,避免参数区自己被写坏。
4. 跳转前的干净环境
Bootloader 跳 App 之前,所有外设都要 DeInit、所有中断都要关闭,否则 App 刚启动就被 Bootloader 遗留的串口 / 定时器中断打断,会触发各种"玄学死机"。这就是上面代码骨架里 HAL_DeInit() + __disable_irq() + 清 NVIC 那几步存在的意义。
三条不可触碰的红线
- Bootloader 扇区必须 Write-Protect。
- 跳转前必须校验签名 / Hash,CRC 只是基础。
- 任何写 Flash 的操作都必须假定下一毫秒会掉电。
结论
- 架构选型: Flash 够大首选 A/B 双区——无缝升级、自带回滚、风险最低;中等容量用 Recovery / Golden Image 保底;超小容量才考虑外部 Flash 缓存。
- 核心动作: 规划好 Flash 地址映射(Linker / 分散加载)、处理好中断向量表偏移(
SCB->VTOR)、跳转前清干净外设和中断。 - 企业级的分水岭: 不是功能多,而是签名校验 + 看门狗回滚 + 防掉电 + 干净跳转这四件事是否都做到位。
工程上没有银弹——Bootloader 的全部艺术,就是死死守住"绝对不能变砖"这条底线。