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 的对等性意味着两个槽位都应具备完整的启动能力。若某个槽位缺少引导程序或校验信息,它就无法作为回退目标,A/B 的可靠性优势将大打折扣。

三、元数据与引导标志:状态机的载体

元数据是 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、元数据分区和引导标志之间的关系。

A/B 启动流程与切换机制 BootROM / 引导程序 读取元数据分区 解析引导标志 元数据分区 misc active_slot / tries successful_boot 用户空间确认服务 置位 successful_boot 清零 tries_remaining Slot A bootloader_a system_a 状态:normal Slot B bootloader_b system_b 状态:trying 回退路径 tries 耗尽 切换 active_slot 加载 normal 槽位 升级写入 启动失败 切换判定条件: 1. 目标槽位校验通过且 tries_remaining > 0 → 尝试启动该槽位 2. 启动后用户空间确认成功 → 置 successful_boot,清除 trying 标记 3. tries_remaining 归零仍未确认 → 回退到另一 normal 槽位,并标记原槽位 invalid

从图中可以看到,元数据分区处于中心位置,引导程序、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 当成简单的“备份分区”。备份分区通常只读且不参与启动切换,而 A/B 的两个槽位都可启动、都可升级,是对等关系。误区二:忽略元数据的掉电保护。若在写 active_slot 时掉电,元数据可能处于半写状态,导致引导程序无法判断启动目标。因此元数据写入必须配合校验和、双副本或事务机制。误区三:忘记在用户空间确认启动成功。若没有确认动作,tries_remaining 会持续递减,最终触发不必要的回退。

下表汇总了 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 系统为何能在升级失败时保持设备可用。后续在实现具体升级逻辑时,所有操作都应围绕“不破坏当前运行槽、正确更新元数据、及时确认启动结果”这三条主线展开。