4. 双备份方案(A/B分区):状态管理、切换逻辑、回滚触发条件

双备份方案,圈里人常叫它 A/B 分区升级。说白了,就是给固件准备两个“窝”。一个跑着,另一个等着。我做了这么多年固件,这方案救过我不少次。你想想看,要是升级到一半断电了,或者新固件有 bug 起不来,至少还有个老固件能顶上。

这一章,咱们把 A/B 分区的核心掰开揉碎。状态怎么管?切换怎么跳?什么情况下必须滚回去?

4.1 状态管理:谁在跑,谁在等

A/B 方案里,系统得时刻知道两件事:当前哪个分区在运行,以及另一个分区里装的是什么版本。这些信息不能丢,得存到持久化存储里。

我个人习惯用 Bootloader 共享数据区,或者叫 Metadata 分区。它很小,但至关重要。

状态字段 说明 典型值
active_slot 当前启动的分区 A / B
trial_slot 试运行中的新分区 A / B / NULL
boot_attempt 试运行启动次数 0 ~ N
slot_status[A/B] 每个分区的健康状态 OK / FAILED / UNKNOWN

这里有个坑。我曾经遇到过,设备在写入 Metadata 时突然掉电,结果两个分区都变成了 UNKNOWN。嗯,后来我加了个 双备份校验——Metadata 写两份,启动时交叉验证。如果有一份坏了,用另一份恢复。

核心原则:状态数据必须原子写入。要么全写成功,要么全不写。千万别写到一半断电,留下个“四不像”。

4.2 切换逻辑:从下载到生效的完整链路

切换不是简单地把启动标志位翻一下。它是一套严谨的流程。我把它拆成四个阶段:

  1. 下载阶段:新固件写入非活跃分区。比如当前跑的是 A,那就往 B 里写。写完后校验完整性,标记 B 为 PENDING_VERIFY。
  2. 切换阶段:设置 active_slot 为 B,trial_slot 也为 B,boot_attempt 清零。然后复位。
  3. 试运行阶段:Bootloader 看到 trial_slot 不为空,启动 B。应用层上报“我起来了”。每上报一次,boot_attempt 加 1。
  4. 确认阶段:如果 boot_attempt 达到预设值(比如 3 次),且应用层持续上报健康,就把 trial_slot 清空,B 正式转正。

为什么要有试运行?我见过太多“启动一次就确认”的悲剧。设备刚起来时一切正常,跑了两小时突然死机。所以,多跑几次,跑久一点,才能暴露问题。

我的经验:试运行次数别设太少。消费类产品 3 次够了,工业设备我建议 5 次以上。有些 bug 是偶发的,次数多了才能抓到。

4.3 回滚触发条件:什么时候必须滚回去

回滚不是你想滚就能滚的。得有一套明确的规则。我把它分成三类:

4.3.1 主动回滚

用户或运维发现新固件有问题,手动触发回滚。这种最简单,直接设置 active_slot 为旧分区,复位即可。

4.3.2 被动回滚(自动触发)

系统自己判断,不需要人参与。常见场景:

  • 启动失败:Bootloader 尝试启动新分区,但卡在某个阶段,Watchdog 超时复位。连续 N 次失败后,自动切回旧分区。
  • 应用层崩溃:新固件跑起来了,但关键服务(比如网络、传感器)频繁崩溃。应用层主动向 Bootloader 发回滚请求。
  • 试运行超时:新分区在试运行阶段,一直没收到“确认健康”的信号。比如设备离线了,或者上报机制坏了。

4.3.3 强制回滚

这是最后的保险。当 Metadata 损坏,或者两个分区都标记为 FAILED 时,Bootloader 进入恢复模式,从外部存储(比如 SD 卡、U 盘)加载固件。

注意:强制回滚是“死马当活马医”。它依赖外部介质,如果外部介质也坏了,设备就真变砖了。所以,永远不要依赖强制回滚作为主要方案。

4.4 核心流程图:A/B 分区状态机

下面这张图,是我自己画的状态机。它把整个流程串起来了。你仔细看,每个箭头都对应一个触发条件。

A/B 分区状态机 空闲 (IDLE) 下载中 (DOWNLOAD) 待验证 (PENDING) 试运行 (TRIAL) 已确认 (CONFIRMED) 回滚 (ROLLBACK) 开始下载 校验通过 切换分区 试运行成功 确认健康 试运行失败 / 超时 状态机说明: 1. 空闲 → 下载:收到升级指令,开始往非活跃分区写数据。 2. 下载 → 待验证:数据写完了,CRC/SHA 校验通过。 3. 待验证 → 试运行:Bootloader 切换启动分区,开始试跑。 4. 试运行 → 已确认:应用层连续上报健康,达到试运行次数。 5. 试运行 → 回滚:启动失败、应用崩溃、或超时未确认。 6. 回滚 → 空闲:切回旧分区,恢复运行。 注意: 回滚后,旧分区状态不变,新分区标记为 FAILED。

4.5 避坑指南:我踩过的几个雷

最后,分享几个实战中容易翻车的地方。这些都是我用真金白银换来的教训。

  • Metadata 写坏:我曾经遇到过,设备在写 Metadata 时断电,结果两个分区都变成了 UNKNOWN。后来我加了双备份校验,才解决。
  • 试运行次数设太少:有个项目,试运行次数设了 2 次。结果设备每次启动都正常,但跑 10 分钟后必死。后来改成 5 次,问题才暴露。
  • 回滚后不清理:回滚后,新分区还标记着 PENDING_VERIFY。下次升级时,系统以为它还在试运行,直接跳过。嗯,回滚后一定要把新分区状态重置为 UNKNOWN。
  • 忽略 Watchdog:Bootloader 阶段也要开 Watchdog。我见过 Bootloader 卡在某个循环里,设备彻底死机。加了 Watchdog 后,至少能自动复位。

总结一下:A/B 分区方案的核心,就是状态机。状态管好了,切换逻辑清晰了,回滚条件明确了,这方案就稳了。别想着偷懒,每一步都要做扎实。