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 启动新系统前,会校验整个分区的完整性。如果某个字节被篡改或损坏,校验失败。
  • 运行时校验:系统启动后,后台线程定期校验关键分区。如果发现静默损坏,触发告警或自动回退。

校验失败后的恢复策略:

  1. 单次重试:如果写入后校验失败,先重试一次写入。有时候是 flash 操作时序问题,重试就能过。
  2. 切换坏块:如果重试仍然失败,标记当前块为坏块,跳过它,继续写下一块。前提是文件系统或 flash 驱动支持坏块管理。
  3. 回退到旧版本:如果整个分区校验都失败,说明这个 slot 已经不可用了。bootloader 直接切到另一个 slot 启动。
  4. 进入 recovery 模式:如果两个 slot 都校验失败(比如双分区都损坏了),那就只能进 recovery 模式,通过串口或 USB 强制刷机。

关键原则:校验失败不等于升级失败。好的系统应该能区分「可恢复的校验失败」和「不可恢复的校验失败」。前者重试或跳过,后者才触发回退。

3.5 四种场景的对比与应对

我把这四种场景放在一起对比一下,方便你理解它们的差异:

失败场景 发生阶段 典型根因 恢复手段 严重程度
升级中断电 写入阶段 电源不稳定、用户拔电 回退到另一个 slot 高(可能变砖)
升级包损坏 下载/解压阶段 网络丢包、存储坏块 重新下载、块级重试 中
分区空间不足 写入前/写入中 镜像过大、分区规划不合理 预检报错、动态分区 中
校验失败 写入后/启动前 flash 坏块、时序问题 重试、坏块管理、回退 中高

说白了,这四种场景覆盖了 AB 分区升级中 90% 以上的失败情况。你在设计升级流程时,只要把这四个场景的检测和恢复逻辑写清楚,剩下的就是细节打磨了。

AB 分区升级失败场景总览 升级失败场景 升级中断电 升级包损坏 分区空间不足 校验失败 典型根因 电源不稳定 用户意外拔电 典型根因 网络传输丢包 存储介质坏块 典型根因 镜像体积过大 分区规划不合理 典型根因 flash 坏块 时序/电压问题 恢复手段 回退到另一 slot recovery 模式 恢复手段 重新下载 块级重试 恢复手段 升级前预检 动态分区方案 恢复手段 单次重试 坏块管理/回退 严重程度:高 严重程度:中 严重程度:中 严重程度:中高

嗯,这张图基本把四种场景的根因和恢复手段都串起来了。你在设计升级流程时,可以把它当作一个检查清单——每个场景的检测逻辑、恢复逻辑、告警逻辑,都要在代码里落地。