LV004-升级方案维度与选型
一、 OTA 方式的四个维度
"OTA 怎么做"不是一个单选题,而是四个维度各做一个选择,组合出最终方案。这四个维度按决策的可逆性分成两类,分类结果直接决定了它们该在什么阶段拍板:
| 类别 | 维度 | 可选方式 | 回答的问题 | 何时必须定 |
|---|---|---|---|---|
| 架构维度 | 存储布局 | A/B 双区 / Recovery / 单区复制 | Flash 上放几份固件、往哪写 | 硬件选型与分区设计阶段 |
| 架构维度 | 执行方式 | XIP / RAM 运行 | 代码从哪取指执行 | 由芯片决定,几乎无选择余地 |
| 运营维度 | 升级包粒度 | 整包 / 差分 | 网络上传多少数据 | 可等上线后再叠加 |
| 运营维度 | 触发与安装时机 | 手动 / 轮询 / 推送;静默 / 需确认 / 强制;后台 / 前台 / 定时 | 升级在用户侧怎么发生、什么时候打扰 | 可随时按业务调整 |
两类的区别是本篇的组织依据:
- 架构维度决定方案骨架,量产后无法更改(改存储布局等于重新分区、重新链接、重刷全量设备),必须在设计期想清楚,对应本篇第二、三节
- 运营维度不影响骨架,可以上线后按流量成本与用户体验持续调优,对应本篇第四节
例如 Android 手机的组合是"A/B 双区 + XIP + 差分包 + 轮询提示确认 + 后台升级",STM32F103 上常见的组合是"单区复制 + XIP + 整包 + 静默后台升级"。第六节把四个维度串成一张决策树。
二、 存储布局:固件放在哪、往哪写
存储布局决定升级的原子性和回滚能力,是四个维度中最关键的一个。
1. A/B 双区(无缝升级)
Flash 上保留两套系统区,当前从 A 运行,新固件写入 B:
┌─────────────┬──────────────────┬──────────────────┬─────────┐
│ Bootloader │ 系统 A(运行中) │ 系统 B(写入中) │ 参数区 │
└─────────────┴──────────────────┴──────────────────┴─────────┘工作过程:后台下载并写入 B 槽 → 校验通过 → 参数区置"下次启 B" → 重启 → Bootloader 引导 B → 新系统自检通过则转正,失败则切回 A。
- 优点:升级全程旧系统不受干扰、天然具备回滚能力、用户无感(仅一次重启)
- 缺点:需要约双倍系统空间;MCU 上 XIP 执行时有地址绑定问题(见本篇第三节)
Android 7.0+ 的 Seamless Updates、Linux 的 RAUC / Mender 都采用此布局。
2. Recovery 模式
设备上有一个独立的最小恢复系统(Recovery 分区),升级时重启进入它,由它把新固件刷入主系统区:
┌─────────────┬──────────────┬────────────────────────┐
│ Bootloader │ Recovery 系统 │ 主系统(被刷写) │
└─────────────┴──────────────┴────────────────────────┘- 优点:主系统独占大部分空间,Recovery 可以做得比完整系统小
- 缺点:升级期间设备完全不可用(在 Recovery 里刷机);刷写主系统的过程本身不原子,中途掉电主系统损坏,只能靠 Recovery 重新刷——Recovery 是救星而不是回滚,设备退回的是"重刷流程"而不是"旧版本"
早期 Android、很多 Linux 电视盒子用这种方式。
3. 单区 + 暂存复制
只有一个 App 运行区,新固件先下载到暂存区(内部 Flash 剩余空间或外挂 SPI Flash),再由 Bootloader 复制覆盖到 App 区:
┌─────────────┬──────────────────┬──────────────────┐
│ Bootloader │ App 区(唯一) │ 暂存区(新固件) │
└─────────────┴──────────────────┴──────────────────┘
▲ 复制覆盖 ◀────────┘- 优点:空间利用率高于 A/B,App 只需要一份链接地址
- 缺点:复制一开始,旧固件就被破坏,复制中途掉电设备变砖(只能靠 Bootloader 里的恢复通道救回);没有旧版本可回滚
这是资源受限 MCU 的常见妥协方案,详细实现见 LV022-MCU无BankSwap落地方案。
4. 三种布局对比
| 维度 | A/B 双区 | Recovery | 单区复制 |
|---|---|---|---|
| 升级中设备可用 | 是(仅重启一次) | 否 | 否 |
| 掉电安全 | 强(双区天然原子) | 中(Recovery 可重刷) | 弱(复制中途掉电变砖) |
| 版本回滚 | 原生支持 | 不支持(只能重刷) | 不支持 |
| 空间开销 | 2 倍系统 | 系统 + 小 Recovery | 系统 + 暂存区 |
| 典型场景 | Android、车载、Linux 网关 | 早期机顶盒、Recovery 刷机 | 小容量 MCU |
三、 执行方式:XIP 与 RAM 运行
1. XIP 就地执行
XIP(eXecute In Place)指 CPU 直接从 Flash 取指执行,代码不搬进 RAM。绝大多数单片机(内部 Flash 内存映射)和 Linux 的 XiP 内核都属于此类。
XIP 对 OTA 的影响是地址绑定:固件编译时链接地址固定(如 0x08004000),镜像里向量表、绝对寻址都指向这个地址。同一份 bin 从 A 槽能跑,放到 B 槽(0x08040000)就跑不起来。于是 A/B 双区 + XIP 在 MCU 上有两条出路:
(1)编译两份镜像(分别链接 A、B 地址),OTA 时按目标槽位下发对应变体
(2)芯片支持硬件 Bank Swap,把两个 Bank 的地址映射对调,单一链接地址即可(见 LV025-MCU有BankSwap落地方案)
Linux 平台没有这个问题:镜像放哪个分区由 U-Boot 决定,内核与 rootfs 不感知物理分区。
2. RAM 运行
把固件整体从 Flash 拷贝到 RAM,再从 RAM 执行。此时"存储地址"与"运行地址"解耦:固件只链接到一个固定的 RAM 地址,存在 Flash 哪个槽都无所谓,Bootloader 选中哪个槽就拷贝哪个。
- 优点:单份镜像即可 A/B,回滚天然安全;RAM 取指零等待,执行更快
- 缺点:RAM 必须装得下整个 App(代码 + data + bss + 栈堆);固件在 Flash 和 RAM 中占双份空间;每次上电多一步拷贝
适合 RAM 充裕的 MCU(如 STM32F7/H7、i.MX RT 系列)。i.MX RT 干脆片内没有 Flash,"外部 Flash 存储 + 内部 SRAM / 外部 SDRAM 运行"就是它的原生模式。
3. 位置无关代码(PIC)
理论上还有第三条路:编译成位置无关代码(ARM Compiler 的 ROPI/RWPI、GCC 的 -fpic),镜像放哪都能跑。但 Cortex-M 上工具链支持零碎、有间接寻址开销、中断向量表仍需特殊处理,工程上极少采用,不如"RAM 运行"或"编译两份"实在。
四、 运营维度:升级包粒度与触发安装时机
架构维度定完之后,剩下两个维度都可以在不改骨架的前提下持续调整。它们的共同点是:只影响"传多少数据"和"升级在用户侧怎么发生",不影响 Flash 布局与链接地址。
1. 整包升级
升级包就是一份完整的新固件镜像,设备收到后直接整体写入目标区域。
- 优点:逻辑简单、任何旧版本都能直接升、设备端不需要重建镜像的计算
- 缺点:包体大。一个 200 KB 的 MCU 固件改一行代码也要全量下发;Linux 根文件系统动辄几百 MB,对流量计费的 4G 设备是硬伤
2. 差分升级
差分(Delta / Incremental)升级只下发新旧版本之间的"差异补丁",设备端把补丁应用到旧镜像上,重建出新镜像。主流差分算法是 bsdiff,原理详见 LV007-升级包格式与传输机制。
- 优点:包体通常只有整包的 5%~30%,省流量、下载快
- 缺点:
(1)源版本严格匹配:V1→V2 的补丁只能打在 V1 上,设备当前版本不符就得换整包
(2)设备端需要额外的计算与临时空间(旧镜像 + 补丁 → 新镜像,三者可能同时存在)
(3)服务器要为每个版本对预生成补丁,版本多了组合爆炸(n 个历史版本就要 n 个到最新版的补丁)
这三条缺点决定了差分只能作为优化项、不能作为唯一路径:任何一环不满足都要能退回整包。算法对比、嵌入式移植的 RAM 约束、失败降级的完整决策流程,权威出处是 LV010-差分算法对比与嵌入式移植。
3. 粒度选型
| 条件 | 建议 |
|---|---|
| MCU + Wi-Fi / 以太网(流量免费) | 整包即可,简单可靠 |
| MCU + 4G(流量计费) | 固件大于 100 KB 时考虑差分 |
| Linux 大 rootfs | 差分 / OSTree 必选,整包只作兜底 |
| 版本碎片化严重 | 整包为主,差分只维护"最近一两个版本→最新" |
4. 触发、确认与安装时机
"升级在用户侧怎么发生"要连做三个选择,三者正交,组合起来才是完整答案:
(1)怎么发现新版本:手动触发(用户点"检查更新",服务器压力最小、版本收敛最慢)、设备轮询(定期拉版本描述文件比对,实现简单、服务器无状态,是绝大多数方案的选择)、服务器推送(MQTT 等长连接主动通知,实时性好、适合需要统一调度的设备群)。轮询与推送的协议细节见 LV001-OTA全景与设计目标 第三节。
(2)发现后要不要用户同意:静默自动(下载、写入、重启全程不打招呼,只在升级上报里留痕)、提示需确认(弹"发现新版本,是否立即升级",用户不点就不升,代价是版本碎片化最严重)、强制(不给取消按钮,只用于安全补丁与严重缺陷)。
(3)什么时候执行:后台下载 + 重启切换(最常用,下载写入都在后台限速进行,只在最后重启时打断一次,A/B 方案下感知时间 = 一次正常开机)、强制前台(停业务、显示进度条)、定时窗口(约定凌晨等业务低谷自动升级,常见于共享设备、工业网关)。
三个选择的组合决定用户感知:手机是"轮询 + 提示需确认 + 后台下载重启切换",共享设备是"轮询 + 静默 + 定时窗口",车载安全补丁是"推送 + 强制 + 前台"。MCU 场景用户容忍度通常更高(重启就是几百毫秒),Linux / 车载场景则必须把"无感"作为硬指标。发布节奏与灰度分批的运营策略见 LV031-发布与量产运维。
五、 升级期间的设备可用性
同样叫"OTA 升级",手机升系统时还能继续用其它应用,机顶盒升固件时却黑屏十分钟。这个差异不来自运营策略,而来自四个物理根因——第一个由存储布局注定,后三个可以靠工程手段缓解:
| 用户看到的现象 | 根因 | 由什么决定 | 能否缓解 |
|---|---|---|---|
| 升级全程业务照跑,只重启一次 | 写入的是非活动区,运行区 untouched | 存储布局(A/B 双区) | 布局选定即注定 |
| 升级期间完全不可用 | 必须停业务去刷正在运行的那份 | 存储布局(Recovery / 单区复制) | 只能换布局 |
| 后台升级仍有毫秒级卡顿或通信超时 | 片内 Flash 擦写阻塞取指、关中断 | 执行方式(XIP) | 能:擦除单元拆小、限速、避开业务高峰 |
| 升级很慢、业务被拖慢 | 下载校验占用 RAM / CPU / 带宽 | 运营策略 | 能:限速、定时窗口,或停业务换速度 |
| 最后那一下不可用 | 重启切换窗口 | 存储布局 + Bootloader 速度 | 能:A/B 下窗口等于一次正常开机 |
1. 写入的是不是正在运行的那份
A/B 双区写 B 槽时 A 槽照常跑,业务对升级无感,唯一的打断是最后那次重启。Recovery 模式要重启进恢复系统去刷主系统区,主系统此刻不在运行,设备自然全停。单区复制更极端:复制覆盖 App 区的那一刻起旧固件就在被拆,业务不停就是跑在施工中的地板上,所以必须停。
结论是:"升级中设备可用吗"在第二节对比表的第一行就已经答完了,运营策略改不了它。
2. 片内 Flash 擦写的瞬时阻塞
MCU 上即使写的是非活动槽,擦写瞬间 CPU 从 Flash 取指也会被总线阻塞;STM32 HAL 的写 Flash 例程在执行期间关中断等待(见 LV007-升级包格式与传输机制 第四节)。用户侧的表现是毫秒级卡顿、通信超时、按键迟滞。若擦写的是代码自身所在的 Bank,则必须停业务把擦写例程搬到 RAM 执行(见 LV022-MCU无BankSwap落地方案 第三节)。
Linux 上写 eMMC / NAND 不阻塞 CPU 取指,内核与业务照常运行——这是"Linux 升级无感、MCU 后台升级仍会卡"的硬件根源之一。缓解手段是把擦除单元拆小、限速、把擦写安排在业务空闲间隙。
3. 重启切换窗口
任何方案最后都要重启一次,把控制权交还 Bootloader。窗口长度 = Bootloader 校验 + 必要拷贝 + 引导的时间:A/B 下只有校验与引导(几百毫秒到几秒);单区复制下 Bootloader 还要先把整包从暂存区复制进 App 区(几十秒);Recovery 下则是整个刷写过程(分钟级)。
"重启一下就好"与"黑屏十分钟"的差别就在这里,它同样是布局的函数而不是运营策略的函数。
4. 资源占用与限速取舍
下载、校验、差分重建要占 RAM、CPU、通信带宽与 Flash 带宽。弱资源设备后台全速跑会拖业务:4G 模组抢 UART、校验占满 CPU、写 Flash 挤掉业务的中断响应。于是一部分产品反过来选择"停业务换速度"——前台升级不是落后设计,而是资源受限下的主动取舍。
运营侧对应的旋钮是限速与定时窗口(见 LV031-发布与量产运维 第三节),用时间换业务平稳。
四个根因里只有第一个是"全停 vs 无感"的分水岭,其余三个决定的是"无感得多干净"。排查"为什么我的设备升级时不可用",按本章开头的表从上往下对:先看布局,再确认是不是 MCU 的擦写卡顿被感知成了不可用,最后量重启窗口。
六、 选型决策树
把四个维度串成一张决策图,从平台出发逐层收敛。注意决策树只覆盖架构维度——运营维度不进树,因为它们不改变结论:
几个补充结论:
- 能 A/B 就 A/B,回滚能力是 OTA 可靠性的根基,空间换可靠性几乎总是值得的
- MCU 上"RAM 装得下"是个分水岭:装得下就用 RAM 运行方案,单镜像 + 天然回滚,省掉双变体编译的构建复杂度
- 单区复制方案必须配 Bootloader 兜底恢复通道(UART / USB 等待重刷),并接受"无版本回滚"这个缺陷;四种落地方案的完整对比与代码见 LV022-MCU无BankSwap落地方案
- 差分与否可以在整包方案上线后再叠加,不影响存储布局设计
- 通道选择同样与架构维度解耦:近场通道(UART / USB / CAN / BLE)与网络通道可以共用同一套升级逻辑,见 LV013-升级通道协议
- 升级期间设备能不能保持可用,在选存储布局时就已注定:运营策略只能改善卡顿与重启窗口,改不了"全停"。四个根因与现象对照表见第五节
本文档由 markdowncli 技能辅助生成