3. A/B升级机制原理
OTA升级最怕的不是“升级失败”,而是“升级失败后设备再也起不来”。A/B升级机制正是为解决这一风险而设计的:设备内部保留两套可独立运行的系统镜像,新固件写入非当前运行槽位,只有确认新系统能够正常启动并完成自检后,才把启动权正式交给它。这样,升级过程即使断电、写入出错或新固件启动异常,旧系统仍然完整保留,设备具备回退到可用状态的能力。
双系统分区设计:不是简单复制两份 rootfs
在STM32MP平台上,A/B机制通常围绕eMMC或SD卡上的GPT分区表展开。以一套典型布局为例,除了FSBL、SSBL等公共引导阶段外,系统会划分出两个对称的槽位:
- bootfs_a / bootfs_b:存放各自槽位的Linux内核、设备树与启动所需文件;
- rootfs_a / rootfs_b:存放各自槽位的根文件系统;
- 公共数据区:如用户数据、配置、日志等,通常不随槽位切换而复制,但需要保证升级前后兼容;
- 启动元数据区:保存当前活动槽位、槽位状态、尝试次数、校验值等,是A/B机制的“控制面”。
双分区并不等于把整个系统无脑复制两份。工程上更常见的做法是:内核与根文件系统按槽位成对存在,而大容量用户数据、日志、标定参数放在共享分区中,通过版本兼容策略避免回滚后数据不可读。对于STM32MP这类带MMC控制器的平台,分区表本身也可以由U-Boot读取,从而在引导阶段完成槽位选择。
槽位管理:谁在运行、谁在待命
槽位管理的核心是“活动槽位”与“目标槽位”的区分。设备正常运行时,U-Boot根据启动元数据中的active_slot选择从A或B启动。当OTA客户端下载新固件后,它不会覆盖当前运行的分区,而是把镜像写入另一个槽位,并更新元数据中的目标槽位状态。例如:
# 示例:U-Boot 环境变量中的槽位控制字段
active_slot=A
slot_a_status=good
slot_b_status=pending
slot_b_retry=3
bootcount=0
upgrade_available=1
这些字段的含义是:当前从A启动,A状态为good;B已写入新固件但尚未验证,允许尝试启动3次;upgrade_available=1表示存在待验证升级。U-Boot在启动时会读取这些变量,决定加载哪个槽位的内核。若B连续启动失败达到重试上限,引导逻辑应自动切回A,并把B标记为bad或invalid。
启动标志与回滚策略
启动标志是A/B机制从“能启动”走向“可回滚”的关键。常见做法是在用户空间由OTA服务维护一个“启动成功确认”流程:新系统首次启动后,服务执行自检(网络、存储、关键外设、应用进程),全部通过后调用fw_setenv boot_success 1或写入misc分区,将当前槽位标记为good,并清除重试计数。若自检失败或系统在限定时间内未确认,U-Boot在下一次启动时递减重试计数,归零后回退到旧槽位。
回滚策略需要明确三个边界条件:
- 回滚触发条件:启动失败、内核panic、根文件系统挂载失败、自检超时、关键服务未就绪;
- 回滚目标:始终回退到最近一个状态为good的槽位,而不是简单地在A/B之间来回切换;
- 回滚后的数据一致性:共享数据区若已被新版本迁移过,旧版本必须能够识别并兼容,否则回滚后应用可能无法读取配置。
原子性更新:让“切换”成为一个不可分割的动作
原子性更新指的是:系统从旧槽位切换到新槽位的过程,要么完整发生,要么完全不发生,不存在“一半新一半旧”的中间状态。在A/B机制中,原子性主要依靠两个手段保证:
- 镜像写入与校验分离:新固件先完整写入目标槽位,再计算并比对sha256或签名,校验通过后才更新元数据中的目标槽位状态;
- 启动决策单点化:U-Boot只根据元数据中的一个活动槽位字段决定加载路径,避免多个标志位互相矛盾。
以一次典型升级为例,其状态迁移可以概括为:当前A运行且good → 下载v1.3.0写入B → 校验B通过 → 设置slot_b_status=pending、upgrade_available=1 → 重启 → U-Boot尝试启动B → B自检通过 → 标记B为good、A为old → 完成。若B启动失败,重试计数递减,归零后U-Boot切回A,B标记为bad,升级流程结束并上报失败。
工程实现中的关键参数与判断依据
为了让A/B机制真正可落地,需要明确一组可配置参数。以下是一份示例参数表,用于说明各字段的作用与建议取值:
| 参数 | 作用 | 示例值 | 判断依据 |
|---|---|---|---|
slot_retry_max |
新槽位最大启动尝试次数 | 3 | 过小易误回滚,过大延长故障恢复时间 |
boot_success_timeout |
启动后确认成功的超时时间 | 120 s | 需覆盖内核启动、根文件系统挂载与自检 |
verify_method |
镜像校验方式 | sha256 + 签名 | 安全要求高时启用签名校验 |
rollback_policy |
回滚目标选择策略 | 最近good槽位 | 避免在多个坏槽位之间反复切换 |
shared_data_compat |
共享数据兼容策略 | 向前兼容 | 确保回滚后旧版本可读 |
这些参数不是孤立的,它们共同决定了升级的“安全边界”。例如,slot_retry_max与boot_success_timeout需要配合:如果超时时间太短,系统可能还没完成自检就被判定失败;如果重试次数太多,设备可能在坏固件上反复重启,影响现场可用性。工程上通常先通过串口日志观察一次完整启动耗时,再据此设置超时和重试次数。
最后需要强调,A/B升级的本质不是“多占一份存储”,而是用空间换可靠性。它把升级风险从“设备可能变砖”降低为“最多回退到旧版本”。对于STM32MP这类需要长期现场运行、且现场维护成本较高的设备,这种机制带来的可靠性收益通常远大于存储开销。