4. A/B分区系统架构
A/B 双分区升级系统的核心思想,是在存储介质上同时保留两套可独立启动的固件副本,并通过一组可持久化的元数据记录“当前该从哪一套启动”。这样,升级过程不再覆盖正在运行的固件,而是把新固件写入空闲分区,待校验通过后再通过一次“切换”动作让引导程序改从新分区启动。理解 A/B 架构,关键要看清四件事:分区如何布局、Slot A 与 Slot B 如何对等、元数据与引导标志如何记录状态、启动流程如何依据这些标志完成选择与回退。
一、分区布局:成对出现,元数据独立
典型的 A/B 布局会把可升级的固件组件按槽位成对划分。以嵌入式设备常见的 eMMC 或 SPI NOR 为例,分区表通常包含 bootloader_a/bootloader_b、system_a/system_b、vendor_a/vendor_b 等成对分区,同时保留若干不参与 A/B 切换的共享分区,例如持久化数据分区、出厂参数分区,以及专门存放 A/B 状态信息的元数据分区。元数据分区必须独立于两个槽位之外,否则一旦某个槽位损坏,状态记录也会随之丢失。
分区布局设计要遵循两个原则:其一,成对分区的容量、偏移对齐方式必须一致,便于引导程序用同一套逻辑寻址;其二,元数据分区应具备冗余副本或校验字段,防止写入过程中掉电导致状态不可读。下面是一个简化的分区表示意:
分区名 偏移 大小 说明
bootloader_a 0x000000 1MB Slot A 引导
bootloader_b 0x100000 1MB Slot B 引导
system_a 0x200000 32MB Slot A 系统
system_b 0x2200000 32MB Slot B 系统
misc 0x4200000 1MB A/B 元数据与引导标志
data 0x4300000 剩余 共享持久化数据
二、Slot A 与 Slot B:对等而非主备
Slot A 与 Slot B 在结构上完全对等,没有天然的“主分区”概念。任意一个槽位都可以成为当前运行槽,另一个则作为升级目标槽。这种对等性带来两个重要结果:第一,升级方向可以交替进行,本次从 A 升到 B,下次从 B 升到 A,避免同一分区被反复擦写;第二,回退逻辑简单,只要把引导标志指回旧槽位即可,无需重新写入旧固件。
需要区分“运行槽”和“活动槽”两个概念。运行槽是当前 CPU 正在执行的固件所在分区;活动槽是元数据中标记为下次启动应加载的分区。正常稳定运行时两者一致;在升级完成但尚未重启、或已标记切换但尚未重启的窗口期内,两者可能不一致。理解这一点,是排查“升级后仍启动旧版本”问题的关键。
三、元数据与引导标志:状态机的载体
元数据是 A/B 系统的“大脑”,通常包含以下字段:当前活动槽位(active_slot)、各槽位的启动成功计数(successful_boot)、各槽位的尝试剩余次数(tries_remaining)、槽位优先级(priority)、以及整体校验和。引导标志则是元数据中直接决定启动路径的布尔量或枚举量,例如 boot_a_ok、boot_b_ok、active_slot 等。
这些字段共同构成一个状态机。以常见的实现为例,每个槽位有三个关键状态:
- 正常(normal):该槽位已成功启动过,successful_boot 为真,可作为稳定回退目标。
- 尝试中(trying):该槽位被标记为下次启动目标,但尚未确认启动成功,tries_remaining 大于零。
- 无效(invalid):该槽位校验失败或尝试次数耗尽,引导程序应跳过它。
引导程序在每次启动时读取元数据,依据 active_slot 和槽位状态决定加载哪个分区。若目标槽位处于 trying 状态,引导程序会递减 tries_remaining;若系统成功启动到用户空间,用户空间服务再将 successful_boot 置位,完成“确认”。若在 tries_remaining 耗尽前始终未确认,引导程序会自动回退到另一个 normal 槽位。
四、启动流程与切换机制
完整的启动与切换流程可以概括为“读元数据 → 选槽位 → 尝试启动 → 确认或回退”。下面用一张信号与状态流转图展示这一过程,图中同时体现 Slot A、Slot B、元数据分区和引导标志之间的关系。
从图中可以看到,元数据分区处于中心位置,引导程序、Slot A、Slot B 和用户空间确认服务都围绕它交互。升级时,新固件被写入非活动槽位;切换时,只需修改元数据中的 active_slot 和对应槽位的 trying 标志,无需搬移数据。这种“写空闲、改标志、再确认”的机制,使升级过程具备原子性和可回退性。
五、典型场景与常见误区
假设设备当前运行 Slot A,收到新固件后写入 Slot B,并将 active_slot 置为 B、B 的 tries_remaining 设为 3。重启后引导程序加载 Slot B,若系统正常启动,用户空间服务将 B 标记为 successful_boot;若 Slot B 因镜像损坏连续三次启动失败,引导程序自动把 active_slot 改回 A,设备仍能正常运行。整个过程用户几乎无感知,这就是 A/B 架构的价值。
下表汇总了 A/B 架构中几个关键对象及其职责,便于对照理解:
| 对象 | 职责 | 关键字段/标志 | 异常时的行为 |
|---|---|---|---|
| Slot A | 可独立启动的固件副本 | boot_a_ok、successful_boot | 校验失败则跳过,尝试 Slot B |
| Slot B | 可独立启动的固件副本 | boot_b_ok、tries_remaining | 尝试次数耗尽则回退 Slot A |
| 元数据分区 | 记录活动槽位与启动状态 | active_slot、priority、checksum | 校验失败则使用默认槽位或双副本恢复 |
| 引导标志 | 决定本次启动加载哪个槽位 | active_slot、trying 标记 | 标志非法时回退到 normal 槽位 |
| 用户空间确认服务 | 确认新槽位启动成功 | successful_boot 置位 | 未运行则触发自动回退 |
掌握分区布局、槽位对等关系、元数据字段和启动切换流程之后,就能理解 A/B 系统为何能在升级失败时保持设备可用。后续在实现具体升级逻辑时,所有操作都应围绕“不破坏当前运行槽、正确更新元数据、及时确认启动结果”这三条主线展开。