3、双备份机制:A/B分区切换原理
双备份机制,圈里人更喜欢叫它 A/B 系统。说白了,就是准备两套完整的固件,一套在跑,一套待命。我最早接触这个方案是在做车机项目的时候,当时客户要求 OTA 失败后必须能回滚,不然车开半路黑屏了,那可不是闹着玩的。
A/B分区切换原理
先看分区布局。Flash 里划出两个完全独立的区域,A 区和 B 区。每个区都包含 bootloader、kernel、rootfs 和 app 分区。系统启动时,bootloader 会检查一个叫 slot 状态的标记,决定从哪个区启动。
// 分区布局示意
// 每个 slot 都是完整的系统
struct partition_table {
slot_a: {
bootloader: 0x00000000 - 0x0001FFFF,
kernel: 0x00020000 - 0x0003FFFF,
rootfs: 0x00040000 - 0x0013FFFF,
app: 0x00140000 - 0x0023FFFF
},
slot_b: {
bootloader: 0x00240000 - 0x0025FFFF,
kernel: 0x00260000 - 0x0027FFFF,
rootfs: 0x00280000 - 0x0037FFFF,
app: 0x00380000 - 0x0047FFFF
}
};
切换流程其实不复杂。假设当前系统在 A 区运行,要升级到新版本。OTA 下载的新固件会直接写入 B 区。写入完成后,bootloader 把启动标记从 A 切换到 B,然后重启。如果 B 区能正常启动并运行,就确认切换成功。如果启动失败,bootloader 会自动切回 A 区。
核心要点:切换的关键在于 bootloader 的决策逻辑。它不看版本号,只看 slot 状态。状态包括:正常、待更新、更新中、失败回滚。
双备份的优缺点
先说说优点,这也是为什么很多工业级产品都在用。
- 零停机升级:系统在 A 区跑着,B 区随便写。用户完全感觉不到升级过程。
- 秒级回滚:新版本出问题?重启一下,bootloader 自动切回旧版本。我见过最快的一次回滚只用了 800 毫秒。
- 升级失败零风险:写入过程中断电、断网,最多就是 B 区废了,A 区纹丝不动。
缺点也很明显,说白了就是「空间换安全」。
- Flash 容量翻倍:你想想看,本来 8MB 就能装下的固件,现在得 16MB。对于成本敏感的 IoT 设备,这可不是小数目。
- 升级流程复杂:需要管理 slot 状态、处理边界情况。我在项目中遇到过一个问题:B 区写了一半,用户强制重启,结果两个区都标记为「待更新」,系统直接变砖了。
- 测试工作量翻倍:每个版本都要在两个 slot 上验证,兼容性测试也得做两遍。
| 对比项 | 单分区方案 | A/B双备份 |
|---|---|---|
| Flash占用 | 1x | 2x |
| 升级安全性 | 低(断电即变砖) | 高(总有退路) |
| 回滚速度 | 需重新刷写 | 秒级重启切换 |
| 实现复杂度 | 简单 | 中等 |
如何实现无缝切换
无缝切换,说白了就是让用户完全感知不到升级过程。我总结了几条实战经验。
第一,状态机要严谨。每个 slot 的状态转换必须原子操作。我习惯用这样的状态定义:
enum slot_state {
SLOT_ACTIVE, // 当前运行
SLOT_INACTIVE, // 空闲待用
SLOT_UPDATING, // 正在写入
SLOT_PENDING, // 等待验证
SLOT_FAILED // 验证失败
};
第二,写入策略要保守。不要一次性写完整个分区。我建议分块写入,每块写完后做 CRC 校验。如果某块校验失败,只重写这一块,不用从头再来。
// 分块写入示例
#define BLOCK_SIZE 4096
bool write_firmware_block(uint32_t offset, uint8_t* data, uint32_t len) {
// 先擦除块
if (!flash_erase(offset, BLOCK_SIZE)) {
return false;
}
// 写入数据
if (!flash_write(offset, data, len)) {
return false;
}
// 读回验证
uint8_t verify[BLOCK_SIZE];
flash_read(offset, verify, len);
if (memcmp(data, verify, len) != 0) {
return false; // 校验失败,重试
}
return true;
}
第三,切换时机要选对。不要收到新固件就立刻切换。我建议等系统空闲时再触发切换。比如在智能家居设备上,我会等用户离家后或者深夜再执行切换。
实战技巧:在切换前,先把当前系统的关键状态保存到非易失存储区。这样即使新系统启动失败,回滚后也能恢复到升级前的状态。我曾经因为没做这一步,回滚后设备配置全丢了,被客户骂了一周。
第四,验证机制要可靠。新系统启动后,不要立刻标记为「正常」。我习惯的做法是:先标记为「待验证」,运行一段时间(比如 5 分钟)后,如果没收到异常报告,再正式标记为「正常」。如果期间收到看门狗复位或者崩溃日志,立刻回滚。
避坑指南:我曾经遇到过一个问题:新系统启动后,所有功能都正常,但三天后突然死机。原因是新版本的内存泄漏问题,需要长时间运行才能暴露。所以,验证期不能太短,至少覆盖一个完整的使用周期。
最后说一句,双备份机制不是银弹。如果你的 Flash 容量实在不够,可以考虑压缩固件或者只备份关键分区。但如果你做的是医疗设备、汽车电子这类对可靠性要求极高的产品,别犹豫,上 A/B 系统。安全第一,空间第二。