3. 升级失败场景分析:中断电、包损坏、空间不足、校验失败
做 AB 分区升级,最怕什么?
不是升级本身复杂,而是你根本不知道它会怎么失败。我这些年处理过的线上故障,十有八九都出在这四个场景里:升级中断电、升级包损坏、分区空间不足、校验失败。每个场景的根因、表象、恢复手段都不一样。今天咱们一个一个拆开看。
核心观点:AB 分区的设计初衷就是「失败可回退」。但前提是——你得知道失败是怎么发生的,才能在代码里正确判断「该不该回退」。
3.1 升级中断电
这是最粗暴的失败场景。升级写到一半,啪,没电了。
我印象最深的一次,是在某款智能音箱的 OTA 测试中。升级刚把 system_a 擦除到 60%,测试人员直接拔了电源。重启后设备卡在 bootloader,连 fastboot 都进不去。为什么?因为 bootloader 所在的分区也被波及了——那款芯片的设计把 bootloader 和升级缓存放在同一块 flash 上。
断电场景下,AB 分区能保什么?
- 如果正在写 inactive 分区(比如当前跑的是 A,正在升级 B):重启后 bootloader 检测到 B 的升级标记不完整,直接回退到 A。这是最理想的情况。
- 如果正在写 metadata 或 slot 标记:metadata 损坏会导致 bootloader 不知道哪个 slot 是好的。这时候需要 fallback 到「尝试所有 slot」模式。
- 如果正在写 bootloader 自身区域:嗯,这就是我遇到的那种情况——变砖。唯一的办法是进 recovery 模式或串口强制烧写。
避坑指南:我曾经在项目里踩过一个坑——升级流程中先擦除目标分区,再写入数据。如果断电发生在「擦除完成、写入未开始」的间隙,目标分区是空的。重启后 bootloader 发现 slot 标记为「updating」,但分区内容为空,直接报错。后来我们改成「先写入临时区域,写入成功后再交换 slot 标记」,才彻底解决。
3.2 升级包损坏
升级包损坏,说白了就是下载下来的 zip 或 img 文件跟服务器上的不一样。原因可能是网络传输丢包、存储介质坏块、或者下载过程中被中断。
怎么判断包坏了?
- 文件级校验:对比整个文件的 MD5/SHA256。下载完成后立刻算一遍,跟服务器下发的 hash 对比。不一致就直接丢弃,重新下载。
- 块级校验:对于大包(比如超过 2GB 的系统镜像),全文件校验太慢。可以分块校验,每块 1MB,每块带一个 CRC32。写入前校验当前块,失败则重试该块。
- 签名校验:这是最后一道防线。即使 hash 匹配,也要验签名。防止有人替换了升级包。
| 校验层级 | 校验内容 | 失败处理 | 性能开销 |
|---|---|---|---|
| 文件级 hash | 整个文件的 MD5/SHA256 | 丢弃包,重新下载 | 低(一次计算) |
| 块级 CRC | 每 1MB 块的 CRC32 | 重试该块下载 | 中(逐块计算) |
| 签名校验 | RSA/ECDSA 签名 | 拒绝升级,记录告警 | 高(非对称解密) |
我的习惯:在项目中,我一般把文件级 hash 放在下载完成后做,块级 CRC 放在写入 flash 前做。这样既能快速发现坏包,又能避免把坏数据写进 flash 导致不可逆损坏。
3.3 分区空间不足
这个场景其实比想象中常见。尤其是那些「出厂时分区大小刚好够用」的设备,经过几次系统升级,新版本镜像越来越大,某天突然就写不进去了。
空间不足分两种:
- 目标分区空间不足:比如 system_b 分区只有 512MB,但新镜像有 600MB。写入到一半报错「no space left on device」。
- 缓存分区空间不足:有些方案会把升级包先解压到 cache 分区再写入。如果 cache 分区太小,解压到一半就满了。
怎么处理?
- 升级前预检:在开始写入之前,先读取目标分区的大小,跟镜像头里声明的 size 对比。空间不够直接报错,不要等到写一半才失败。
- 动态分区方案:如果芯片支持,可以用 logical partition 或 super 分区。这样分区大小是动态的,只要总空间够就行。
- 流式写入:不缓存整个包,边下载边写入。这样对缓存空间的需求降到最低。
我曾经遇到过一个 case:某款路由器,升级包只有 32MB,但 cache 分区只有 16MB。升级流程里有个步骤是把包解压到 cache 再校验,结果每次升级都失败。后来改成「直接流式写入目标分区,边写边校验」,问题解决。你想想看,一个 32MB 的包,非要先塞进 16MB 的 cache,这不是设计缺陷是什么?
3.4 校验失败
校验失败是升级过程中最让人头疼的——因为它可能发生在任何阶段,而且原因五花八门。
常见的校验失败场景:
- 写入后校验:数据写进 flash 后,读出来跟原始数据不一致。原因可能是 flash 坏块、电压不稳、或者时序问题。
- 跨分区校验:升级完成后,bootloader 启动新系统前,会校验整个分区的完整性。如果某个字节被篡改或损坏,校验失败。
- 运行时校验:系统启动后,后台线程定期校验关键分区。如果发现静默损坏,触发告警或自动回退。
校验失败后的恢复策略:
- 单次重试:如果写入后校验失败,先重试一次写入。有时候是 flash 操作时序问题,重试就能过。
- 切换坏块:如果重试仍然失败,标记当前块为坏块,跳过它,继续写下一块。前提是文件系统或 flash 驱动支持坏块管理。
- 回退到旧版本:如果整个分区校验都失败,说明这个 slot 已经不可用了。bootloader 直接切到另一个 slot 启动。
- 进入 recovery 模式:如果两个 slot 都校验失败(比如双分区都损坏了),那就只能进 recovery 模式,通过串口或 USB 强制刷机。
关键原则:校验失败不等于升级失败。好的系统应该能区分「可恢复的校验失败」和「不可恢复的校验失败」。前者重试或跳过,后者才触发回退。
3.5 四种场景的对比与应对
我把这四种场景放在一起对比一下,方便你理解它们的差异:
| 失败场景 | 发生阶段 | 典型根因 | 恢复手段 | 严重程度 |
|---|---|---|---|---|
| 升级中断电 | 写入阶段 | 电源不稳定、用户拔电 | 回退到另一个 slot | 高(可能变砖) |
| 升级包损坏 | 下载/解压阶段 | 网络丢包、存储坏块 | 重新下载、块级重试 | 中 |
| 分区空间不足 | 写入前/写入中 | 镜像过大、分区规划不合理 | 预检报错、动态分区 | 中 |
| 校验失败 | 写入后/启动前 | flash 坏块、时序问题 | 重试、坏块管理、回退 | 中高 |
说白了,这四种场景覆盖了 AB 分区升级中 90% 以上的失败情况。你在设计升级流程时,只要把这四个场景的检测和恢复逻辑写清楚,剩下的就是细节打磨了。
嗯,这张图基本把四种场景的根因和恢复手段都串起来了。你在设计升级流程时,可以把它当作一个检查清单——每个场景的检测逻辑、恢复逻辑、告警逻辑,都要在代码里落地。