Skip to content

LV004-升级方案维度与选型 ​

一、 OTA 方式的四个维度 ​

"OTA 怎么做"不是一个单选题,而是四个维度各做一个选择,组合出最终方案。这四个维度按决策的可逆性分成两类,分类结果直接决定了它们该在什么阶段拍板:

类别维度可选方式回答的问题何时必须定
架构维度存储布局A/B 双区 / Recovery / 单区复制Flash 上放几份固件、往哪写硬件选型与分区设计阶段
架构维度执行方式XIP / RAM 运行代码从哪取指执行由芯片决定,几乎无选择余地
运营维度升级包粒度整包 / 差分网络上传多少数据可等上线后再叠加
运营维度触发与安装时机手动 / 轮询 / 推送;静默 / 需确认 / 强制;后台 / 前台 / 定时升级在用户侧怎么发生、什么时候打扰可随时按业务调整

两类的区别是本篇的组织依据:

  • 架构维度决定方案骨架,量产后无法更改(改存储布局等于重新分区、重新链接、重刷全量设备),必须在设计期想清楚,对应本篇第二、三节
  • 运营维度不影响骨架,可以上线后按流量成本与用户体验持续调优,对应本篇第四节

例如 Android 手机的组合是"A/B 双区 + XIP + 差分包 + 轮询提示确认 + 后台升级",STM32F103 上常见的组合是"单区复制 + XIP + 整包 + 静默后台升级"。第六节把四个维度串成一张决策树。

二、 存储布局:固件放在哪、往哪写 ​

存储布局决定升级的原子性和回滚能力,是四个维度中最关键的一个。

1. A/B 双区(无缝升级) ​

Flash 上保留两套系统区,当前从 A 运行,新固件写入 B:

text
┌─────────────┬──────────────────┬──────────────────┬─────────┐
│ Bootloader  │   系统 A(运行中) │   系统 B(写入中) │  参数区  │
└─────────────┴──────────────────┴──────────────────┴─────────┘

工作过程:后台下载并写入 B 槽 → 校验通过 → 参数区置"下次启 B" → 重启 → Bootloader 引导 B → 新系统自检通过则转正,失败则切回 A。

  • 优点:升级全程旧系统不受干扰、天然具备回滚能力、用户无感(仅一次重启)
  • 缺点:需要约双倍系统空间;MCU 上 XIP 执行时有地址绑定问题(见本篇第三节)

Android 7.0+ 的 Seamless Updates、Linux 的 RAUC / Mender 都采用此布局。

2. Recovery 模式 ​

设备上有一个独立的最小恢复系统(Recovery 分区),升级时重启进入它,由它把新固件刷入主系统区:

text
┌─────────────┬──────────────┬────────────────────────┐
│ Bootloader  │ Recovery 系统 │      主系统(被刷写)     │
└─────────────┴──────────────┴────────────────────────┘
  • 优点:主系统独占大部分空间,Recovery 可以做得比完整系统小
  • 缺点:升级期间设备完全不可用(在 Recovery 里刷机);刷写主系统的过程本身不原子,中途掉电主系统损坏,只能靠 Recovery 重新刷——Recovery 是救星而不是回滚,设备退回的是"重刷流程"而不是"旧版本"

早期 Android、很多 Linux 电视盒子用这种方式。

3. 单区 + 暂存复制 ​

只有一个 App 运行区,新固件先下载到暂存区(内部 Flash 剩余空间或外挂 SPI Flash),再由 Bootloader 复制覆盖到 App 区:

text
┌─────────────┬──────────────────┬──────────────────┐
│ 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 选中哪个槽就拷贝哪个。

XIP 与 RAM 运行对比

  • 优点:单份镜像即可 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 的擦写卡顿被感知成了不可用,最后量重启窗口。

六、 选型决策树 ​

把四个维度串成一张决策图,从平台出发逐层收敛。注意决策树只覆盖架构维度——运营维度不进树,因为它们不改变结论:

OTA 存储布局选型决策

几个补充结论:

  • 能 A/B 就 A/B,回滚能力是 OTA 可靠性的根基,空间换可靠性几乎总是值得的
  • MCU 上"RAM 装得下"是个分水岭:装得下就用 RAM 运行方案,单镜像 + 天然回滚,省掉双变体编译的构建复杂度
  • 单区复制方案必须配 Bootloader 兜底恢复通道(UART / USB 等待重刷),并接受"无版本回滚"这个缺陷;四种落地方案的完整对比与代码见 LV022-MCU无BankSwap落地方案
  • 差分与否可以在整包方案上线后再叠加,不影响存储布局设计
  • 通道选择同样与架构维度解耦:近场通道(UART / USB / CAN / BLE)与网络通道可以共用同一套升级逻辑,见 LV013-升级通道协议
  • 升级期间设备能不能保持可用,在选存储布局时就已注定:运营策略只能改善卡顿与重启窗口,改不了"全停"。四个根因与现象对照表见第五节

本文档由 markdowncli 技能辅助生成