4. 镜像与槽位机制
MCUboot 的安全启动能力,最终要落到“镜像放在哪里、如何被识别、升级时如何切换、失败时如何退回”这几个工程问题上。镜像与槽位机制就是这套落地规则的总和:它定义了固件的封装格式、Flash 上的分区布局、每个槽位的状态语义,以及升级过程中镜像如何从副槽搬运到主槽。理解这一章,是后续做签名、加密、防回滚和量产升级的前提。
一、镜像格式:不只是二进制,而是带元数据的容器
MCUboot 使用的镜像不是裸的 .bin,而是一个带头部、尾部校验和可选 TLV 区域的容器。最小编织结构如下:
+----------------------+ <- 镜像起始地址
| image_header (32B) | magic / load_addr / hdr_size / img_size
| | flags / ver_major / ver_minor / pad
+----------------------+
| payload (固件本体) |
+----------------------+
| TLV area | 可选:密钥哈希、签名、加密信息
+----------------------+
| trailer (状态区) | swap info / copy done / image ok
+----------------------+ <- 镜像结束
头部中的 magic 用于快速判断该区域是否是一个合法镜像;img_size 决定 payload 长度;ver_major/ver_minor 是版本比较的依据。TLV 区域承载签名、公钥哈希等安全元数据,是安全启动校验的入口。尾部 trailer 则保存槽位状态,是“交换”和“回滚”能够工作的关键。
二、主槽与副槽:Flash 上的双分区布局
MCUboot 在 Flash 上划分出两个镜像槽位:主槽(primary slot) 和 副槽(secondary slot)。主槽是当前运行镜像的存放位置,副槽是升级镜像的暂存位置。两者大小通常相同,且都包含完整的头部、payload、TLV 和 trailer。
主槽与副槽的地址由 Bootloader 的链接脚本和分区表决定,通常通过 flash_layout.h 或设备树中的 partitions 节点配置。两者必须严格对齐,否则交换算法会因地址偏移错误而破坏镜像。
三、槽位状态:trailer 中的三个关键标志
每个槽位的 trailer 中保存着状态信息,MCUboot 通过它们判断“镜像是否可用、是否已完成交换、是否需要回滚”。核心标志有三个:
- image_ok:主槽镜像已被确认可用,升级成功后由应用或 Bootloader 置位,表示“这个版本可以保留”。
- copy_done:副槽镜像已经完整搬运到主槽,交换动作完成。
- swap_info:记录交换进度和方向,用于掉电恢复,保证交换过程可中断、可续传。
| 状态标志 | 所在位置 | 含义 | 典型操作 |
|---|---|---|---|
| image_ok | 主槽 trailer | 当前主槽镜像已被确认 | 应用启动后调用 boot_set_confirmed() |
| copy_done | 副槽 trailer | 副槽已搬运到主槽 | Bootloader 交换完成后置位 |
| swap_info | 两槽 trailer | 交换进度与方向 | 掉电后据此恢复交换 |
四、交换类型:覆盖、交换与直接执行
MCUboot 提供多种升级策略,常见的有三种:
- 覆盖升级(overwrite-only):直接把副槽镜像复制到主槽,不保留旧镜像。实现简单、占用空间小,但升级过程中掉电会导致主槽损坏,且无法回滚。
- 交换升级(swap):主槽与副槽镜像互换位置,旧镜像被保留在副槽中。升级失败或新镜像未确认时,可以回滚到旧版本。这是最常用的安全升级方式。
- 直接执行(direct-xip):不搬运镜像,Bootloader 直接跳转到副槽执行。适合支持 XIP 的 MCU,但需要额外的地址映射和版本管理。
五、升级与回滚的基本流程
以交换升级为例,一次完整的升级与回滚流程如下:
1. 应用将新镜像写入副槽
2. 重启,Bootloader 校验副槽镜像的签名与版本
3. 校验通过 -> 执行交换:副槽镜像搬运到主槽
4. 交换完成,置 copy_done,跳转主槽运行新固件
5. 新固件启动后自检,确认可用 -> 调用 boot_set_confirmed()
6. 若新固件启动失败或未确认 -> 下次重启触发回滚
7. 回滚:将副槽中的旧镜像恢复回主槽,回到旧版本
回滚的触发条件通常包括:新镜像启动后未在规定时间内确认、看门狗复位、应用主动请求回滚。MCUboot 通过 image_ok 是否置位来判断“是否需要回滚”,因此应用必须在启动成功后及时确认,否则会被误判为失败。
六、案例与常见误区
某工业网关项目使用 MCUboot 交换升级,量产时发现部分设备升级后无法启动。排查发现:应用在初始化外设时耗时较长,未在 Bootloader 规定的确认窗口内调用 boot_set_confirmed(),导致设备重启后触发回滚,反复在新旧版本之间切换。解决办法是把确认动作提前到系统初始化早期,或延长确认窗口。
image_ok 当成“升级成功标志”而忽略确认时机;或者认为覆盖升级也能回滚。实际上,覆盖升级不保留旧镜像,一旦新镜像有问题,只能通过外部工具重新烧录。另一个高频问题是槽位大小不一致。副槽若小于主槽,交换时会出现越界写入;若大于主槽,则浪费 Flash。工程上应保证两槽大小完全一致,并在分区表中显式声明。