固件升级失败后,设备为什么还能开机
耳机连着手机升级固件,进度条走到 80%,连接忽然断了。重新戴上,你担心的是"是不是被我刷坏了"。
大多数情况下没有。在固件升级里,“写一份新的、坏的就丢掉"只是最后一层保险,前面还有好几层。这篇文章讲清这套机制:升级会失败在三个地方、A/B 双分区怎么兜底、以及为什么"能回滚"和"不许回滚"其实是两件相反的事。
升级会失败在三个地方
第一类是传输中断。 蓝牙链路断了、手机把 App 切到后台、耳机或手机电量耗尽,都会让固件镜像只传了一半。
第二类是镜像本身不可信。 传输过程中数据出错,或者有人把镜像换成了别的东西。这类问题的判断标准很明确:内容对不对、来源可不可信。
第三类是新固件"装上了但跑不起来”。 镜像完整、签名正确,但新代码在初始化阶段就有 bug,一开机就卡死或者反复重启。这一类最危险,因为从传输角度看,它是"升级成功"的。
前两类靠校验和签名就能拦住:固件在发布前用私钥签名,设备里的 bootloader 内置对应公钥,在把新镜像搬进可执行槽之前先算哈希、验签名,对不上就拒绝执行(对已经躺在可执行槽里的镜像,是否每次启动都再复验一遍是可配置的)。攻击者改动镜像里的任何一个字节,签名就对不上。这套流程和字段定义,可以参考开源的 MCUboot 设计文档。

图 1 · 三类失败与各自的拦截点
但第三类拦不住——它是"合法的坏固件"。要处理它,靠的是下面这套分区机制。
A/B 双槽:新固件先在备份位待着
以 MCUboot 的实现为例,flash 被划分成两个镜像槽:primary(主槽)和 secondary(备槽)。设备平时只从主槽启动。
升级时,新固件写的不是主槽,而是备槽。写完之后重启,bootloader 把新镜像换进主槽并启动它——这一步叫 test swap(试运行交换)。新固件启动后需要自己跑一遍自检,确认基本功能正常,再主动把自己标记为 OK。这个标记打上,升级才算真正完成。
如果没有打上呢?比如新固件一开机就崩、根本来不及标记,那么下一次启动时 bootloader 会把镜像换回去,启动旧的固件——这叫 revert(回退)。
这就是"升坏了还能救回来"的全部原理:设备变砖的前提是唯一那份可执行固件坏了。只要旧固件还在,最坏的结果就是回到升级前。 Android 的系统更新用的是同一个思路,官方文档把这类流程称为 A/B (seamless) system updates。

图 2 · 新固件先写备槽,试运行通过才留下
中途断电怎么办
交换过程要擦写 flash。一旦在这个几秒到几十秒的窗口里断电,两个槽可能都处于"半新半旧"的状态,谁也不能直接启动。
所以 bootloader 必须记住自己走到哪一步了。MCUboot 的做法是维护一块 swap status 区域,把每个扇区的交换状态逐条记录下来,并且这些记录是逐字节独立写入的——这样即使写状态本身又被打断,也能判断出当前的进度。重启后读这块记录,就知道该继续往前交换,还是该退回去。
反过来说,设计良好的升级流程本来就该扛住断电,但这不是免费的:每次升级都要为这份"进度记录"多付出额外的擦写次数。
代价:flash 要双份,寿命要算账
留一个备槽意味着 flash 容量至少翻倍。对空间动辄只有几百 KB 的耳机、手环来说,这是很重的成本,所以也常见"单槽升级"的简化方案——省空间,抗风险能力就明显下降。
另一笔账是擦写寿命。MCUboot 文档里给了一个直接的算式:升级次数 ≈ flash 擦写寿命 ÷ (镜像大小 ÷ scratch 大小)。文档举的例子是:一颗标称 10000 次擦写的 flash,150 KB 的镜像配 4 KB 的 scratch,大约只能做 267 次升级;把 scratch 提到 16 KB,同一颗 flash 能撑到约 1067 次。同样的硬件,只换一个参数,余量差 4 倍——这就是"可靠"和"便宜"之间要做出的具体取舍。

图 3 · scratch 大小与可升级次数(MCUboot 文档示例)
“能回滚"和"不许回滚”
前面说升级失败可以回退到旧固件。但安全上还有另一条规则:不允许随便降级。旧版本固件可能有已经被修掉的漏洞,如果攻击者能把你刷回旧版本,回滚机制本身就成了攻击入口。
于是有了回滚保护(rollback protection):镜像里带一个只增不减的版本号或安全计数器,设备把它记在可信存储里,启动前比对,比设备记录更旧的镜像一律拒绝。MCUboot 把这叫 downgrade prevention。
所以"回滚"在这里有两个含义:升级失败时的自动回退属于故障恢复;主动刷回旧版本是安全上要禁止的动作。两者不冲突,但实现上必须分得很清楚。
三条实用建议
- 升级时保持电量和连接。 断了通常能恢复,但恢复过程本身也要擦写 flash,多来几次没有好处。
- 失败了先重试,别急着"手动强刷"。 第三方刷机工具往往绕过签名校验和回滚保护,刷错版本反而得不偿失。
- 分批推送不是厂商偷懒。 第三类失败(合法的坏固件)在出厂前拦不住,只能靠小范围灰度,把爆炸半径压到最小。