3、双分区架构:A/B分区原理、双分区切换逻辑、双分区的优缺点
好,咱们今天聊聊双分区架构。说实话,这是OTA升级里最经典、也最稳妥的方案之一。我最早接触这个,是在做车机系统的时候。那时候客户要求升级不能中断,哪怕升级到一半断电了,下次开机还得能用。嗯,双分区就是干这个用的。
3.1 A/B分区原理
说白了,就是把存储空间分成两个区:A区和B区。一个跑着当前系统,另一个闲着。升级的时候,往闲着的那个分区写新固件。写完了,下次启动就切过去。
你想想看,这就像你有两把钥匙。一把在用,一把备用。用坏了一把,换另一把就行。系统也是这个道理。
核心思想:任何时候,至少有一个分区是可用的、能启动的。
具体怎么分?我见过两种常见做法:
- 完全镜像:A和B分区大小一样,内容完全独立。每个分区都有自己的系统、应用、数据。升级时整个分区替换。
- 共享数据区:系统分区A/B各一份,但用户数据区共享。这样升级只动系统,用户数据不动。我比较推荐这种,因为升级完用户不用重新登录、不用重新配置。
我记得有个项目,客户非要搞完全镜像。结果每次升级完,用户都得重新设置Wi-Fi密码,被骂惨了。后来改成共享数据区,问题就解决了。
3.2 双分区切换逻辑
切换逻辑,说白了就是「谁先启动、谁当备胎」的问题。这里有个关键概念叫「槽位切换计数器」。
我画个简单的流程图给你看:
当前运行在A区
↓
下载新固件到B区
↓
校验B区固件完整性(CRC/SHA256)
↓
设置启动标志:下次从B区启动
↓
重启系统
↓
Bootloader检查启动标志
↓
尝试从B区启动
↓
启动成功? → 是 → 标记B区为「成功启动」,切换完成
↓
否
↓
回滚到A区,标记B区为「失败」
↓
下次还是从A区启动
这里有个细节,我特别想强调:「成功启动」的判定。不是进了内核就算成功,而是应用层跑起来了、关键服务都正常了,才算成功。我曾经见过一个坑:系统内核起来了,但Wi-Fi驱动挂了,用户连不上网。这算成功吗?当然不算。
我的习惯:在应用层加一个「健康心跳」信号。应用层完全就绪后,主动通知Bootloader「我好了」。Bootloader收到这个信号,才标记为成功启动。这样更靠谱。
切换逻辑里还有个「回滚次数限制」。比如最多允许回滚3次。超过3次,系统就停在当前分区,不再尝试切换。为什么?因为连续回滚3次,说明新固件大概率有问题,别再折腾了。
3.3 双分区的优缺点
任何方案都有两面性。双分区也不例外。我用了这么多年,总结下来就是:稳,但贵。
| 优点 | 缺点 |
|---|---|
| 升级失败可回滚,系统永远可用 | 存储空间翻倍,成本高 |
| 升级过程中不影响当前系统运行 | 升级耗时较长(需要写整个分区) |
| 实现简单,逻辑清晰 | 不支持增量升级(除非额外做差分) |
| 适合对可靠性要求极高的场景 | 对Flash寿命有影响(频繁擦写) |
咱们展开说说。
优点方面:
- 零停机升级:用户几乎感觉不到在升级。后台下载、后台校验,下次重启就切过去了。我做过一个智能家居网关,用户晚上睡觉时自动升级,早上起来系统已经换了,完全无感。
- 失败回滚:这是双分区最大的价值。升级失败了?没关系,下次启动自动回到旧分区。用户甚至不知道发生过升级。
- 调试方便:开发阶段,我可以随时在两个分区之间切换,对比新旧版本的行为。这个在定位问题时特别有用。
缺点方面:
- 存储成本翻倍:这是硬伤。比如系统分区需要256MB,双分区就得512MB。对于成本敏感的IoT设备,这可能是致命的。我有个项目,就因为Flash成本高了2毛钱,被产品经理追着砍。
- 升级时间长:全量写入整个分区,不像增量升级只写变化的部分。如果固件有100MB,就得写100MB。对于低速Flash,这个过程可能持续几分钟。
- 不支持增量:除非你自己做差分算法(比如bsdiff),否则每次都是全量升级。流量消耗大,对蜂窝网络设备不友好。
注意:双分区不是万能的。它解决的是「升级失败后系统还能用」的问题,而不是「升级不会失败」的问题。校验、签名、断点续传这些机制,该有还得有。
我个人觉得,双分区最适合的场景是:对可靠性要求极高、存储成本不敏感、用户不能接受设备变砖。比如汽车ECU、医疗设备、工业控制器。对于几块钱的智能灯泡,双分区可能就太奢侈了。
嗯,双分区就聊到这儿。下一节咱们讲讲「增量升级与差分算法」,那个更省空间,但实现起来也更有挑战。到时候我再分享几个踩过的坑。