2026-01-22 · 实战
32
实战 · 2026-01-22

你的 Linux 设备为什么永远不会"变砖"?揭秘 A/B 分区 OTA 升级的黑魔法

写在前面的话:如果你曾经因为手机升级失败变砖而抓狂,或者担心你的智能设备在 OTA 更新时突然断电导致报废,那这篇文章就是为你准备的。我会用最直白的语言,带你理解为什么现代嵌入式 Linux 系统能做到"永不变砖"——即使你在升级过程中拔电源。


一、先说个恐怖故事:传统升级方式有多危险?

想象一下这个场景:

你的智能路由器提示有新固件可以升级。你点了"确认",然后设备开始下载、写入新系统。就在写入进行到一半的时候,突然停电了。

传统的单分区升级方式下,会发生什么?

这就是传统升级方案的"单点失效"噩梦——一旦升级过程中断,设备就是一块废铁。


二、A/B 分区的核心思想:永远留一条后路

A/B 分区升级方案的设计哲学非常简单,就像你在走钢丝时,永远有一张安全网在下面接着你。

核心原理:两套系统,轮流上岗

想象你的设备存储空间被分成了两个"房间":

当 OTA 升级来了:

  1. 系统继续在房间 A 运行 → 你的设备正常工作,用户无感知
  2. 新版本悄悄写入房间 B → 后台静默下载、校验、安装
  3. 写入完成后,设备重启 → Bootloader 引导程序说:"这次我们去房间 B 启动"
  4. 如果房间 B 的新系统有问题 → Bootloader 自动切回房间 A,就像什么都没发生过

关键点:整个过程中,房间 A 的旧系统从未被动过。即使升级失败、断电、网络中断,设备永远能从房间 A 启动。


三、Bootloader:系统启动的"门卫大叔"

在 A/B 升级方案中,Bootloader(引导程序)扮演了一个超级关键的角色——它就像一个聪明的门卫,负责决定每次开机时应该进哪个"房间"。

Bootloader 的三大职责

1. 记录启动尝试次数(防止无限重启)

Bootloader 内部有一个计数器,叫 bootcount(启动计数)。每次尝试启动新系统时,它会:

第1次启动房间B → bootcount = 1
第2次启动房间B → bootcount = 2
第3次启动房间B → bootcount = 3

如果 bootcount 超过了预设的阈值(比如 3 次),Bootloader 就会判定:"房间 B 的系统有问题,我不再尝试了。"

2. 自动回滚(Plan B 永远在线)

当 Bootloader 发现房间 B 启动失败(比如连续 3 次都进不了系统),它会:

用户体验:设备可能重启了几次,但最终还是能用。

3. 等待用户空间确认(不是启动成功就算成功)

这是最精妙的设计:Bootloader 不会自作主张认为"系统启动成功了"。它会等待 Linux 用户空间的明确确认信号。

为什么要这样?

因为"内核加载完成"≠"系统正常运行"。可能出现这些情况:

只有当系统完整跑起来,所有关键服务都正常后,才会有一个 systemd 服务(比如 rauc-mark-good.service)告诉 Bootloader:

"老大,这次升级成功了,你可以把 bootcount 归零了,以后就默认启动房间 B 吧。"

如果这个确认信号在规定时间内没来,Bootloader 就知道:"房间 B 有问题",然后触发回滚。


四、技术细节:U-Boot 是怎么实现的?

U-Boot 是嵌入式 Linux 领域最常用的 Bootloader。它通过几个关键的环境变量来实现 A/B 逻辑:

核心变量解析

变量名
作用
举例

upgrade_available
升级开关信号
1 = 有新版本待验证,启动计数逻辑

bootcount
启动尝试次数
每次重启自动 +1

bootlimit
容错阈值
通常设为 3,超过就回滚

mender_boot_part
当前启动分区
2 = Slot A, 3 = Slot B

bootcmd
正常启动命令
尝试加载房间 B

altbootcmd
备选启动命令
回滚到房间 A,重置计数器

启动流程示意

┌─────────────────────────────────────────────┐
│  设备上电 → U-Boot 启动                      │
└─────────────────────────────────────────────┘
                    ↓
         检查 upgrade_available == 1 ?
                    ↓
              是 → bootcount++
                    ↓
         检查 bootcount > bootlimit ?
                    ↓
         ┌──────────┴──────────┐
         否                    是
         ↓                     ↓
   执行 bootcmd          执行 altbootcmd
   (启动房间B)           (回滚到房间A)
         ↓                     ↓
   进入 Linux 用户空间    重置 bootcount = 0
         ↓                 upgrade_available = 0
   关键服务启动成功?
         ↓
   是 → 调用 rauc-mark-good.service
         ↓
   设置 upgrade_available = 0
   设置 bootcount = 0
         ↓
   ✅ 升级完成,下次默认启动房间B

五、数据怎么办?用户配置不会丢吧?

这是个好问题!A/B 分区只是把系统分区(内核、rootfs)做了双份,但用户数据、配置文件、日志等必须在升级后保留

解决方案:独立的持久化分区

最佳实践是创建一个第三个分区,叫 Data Partition(数据分区),它:

示意图

┌──────────────────────────────────────────┐
│  Slot A (房间A)                           │
│  ├── /boot (内核)                         │
│  ├── /etc → /data/etc (软链接)            │
│  └── /rootfs (只读系统文件)               │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  Slot B (房间B)                           │
│  ├── /boot (内核)                         │
│  ├── /etc → /data/etc (软链接)            │
│  └── /rootfs (只读系统文件)               │
└──────────────────────────────────────────┘

┌──────────────────────────────────────────┐
│  Data Partition (数据分区)                │
│  ├── /data/etc (用户配置)                 │
│  ├── /data/logs (日志)                    │
│  └── /data/db (数据库)                    │
└──────────────────────────────────────────┘

关键点:无论系统从 Slot A 还是 Slot B 启动,都会挂载同一个 Data Partition,所以用户数据永远不会丢。


六、安全性:如何防止恶意降级?

A/B 方案虽然能防止升级失败,但如果攻击者强制把系统回滚到一个有已知漏洞的旧版本呢?

防回滚保护(Anti-Rollback)

高端嵌入式芯片(如 NVIDIA Jetson、高通骁龙)引入了硬件熔丝(eFuse)机制:

  1. 每个固件版本有一个安全版本号(SWVN,Software Version Number)
  2. 芯片内部有一个硬件版本号(HWVN,Hardware Version Number)
  3. 启动时 Bootloader 会比较: 如果 SWVN < HWVN,拒绝启动
  4. 升级成功后,烧写 eFuse: 让 HWVN 提升到当前 SWVN

eFuse 的特性:一旦烧写,不可逆。就像保险丝烧断后无法恢复。

结果:即使攻击者拿到物理设备,手动切换到旧版本分区,芯片也会拒绝启动,因为硬件版本号已经被"锁定"在更高的值了。


七、对比:A/B vs 传统方案

维度
传统单分区 + 恢复模式
A/B 双分区方案

断电风险
❌ 极高,可能变砖
✅ 极低,旧系统完好

停机时间
⏱️ 较长(需进恢复模式)
⚡ 极短(仅重启时间,<60秒)

回滚机制
🔄 需重新下载或手动刷机
🔄 自动,无需人工干预

存储占用
💾 低(仅1套系统)
💾 高(需2倍系统空间)

原子性
⚠️ 弱,依赖工具可靠性
✅ 强,由 Bootloader 保障

结论:A/B 方案用空间换可靠性,对于关键设备(医疗、汽车、工业控制)来说,这笔买卖非常值。


八、主流 OTA 框架对比

框架
复杂度
存储效率
适用场景

Mender
🟡 中等
🟡 一般(完整镜像)
通用 IoT、网关

RAUC
🔴 较高
🟢 高(支持差分更新)
汽车、工业控制器

SWUpdate
🟢 较低
🟢 高(流式写入)
路由器、消费电子

Android Virtual A/B
🔴 极高
🟢 极高(仅存储变更快照)
高端手机、平板

选型建议:
- 新手入门 → Mender(文档完善,社区活跃)
- 追求极致可靠性 → RAUC(汽车级标准)
- 存储空间受限 → SWUpdate(支持单分区恢复模式)


九、未来趋势:容器化 + A/B 的组合拳

未来的嵌入式系统会采用分层更新策略:

┌─────────────────────────────────────┐
│  应用层 (Docker 容器)                │  ← 快速迭代,独立更新
│  - 业务逻辑                          │
│  - 用户界面                          │
└─────────────────────────────────────┘
              ↓
┌─────────────────────────────────────┐
│  系统层 (A/B 分区)                   │  ← 慢速迭代,稳定为主
│  - Linux 内核                        │
│  - 核心系统服务                      │
└─────────────────────────────────────┘

好处:
- 🚀 应用更新快:只需下载几 MB 的容器镜像
- 🛡️ 系统更新稳:底层 OS 采用 A/B 方案,确保不变砖
- 🔄 独立回滚:应用出问题回滚应用,系统出问题回滚系统


十、总结:为什么 A/B 是"永不变砖"的终极答案?

A/B 分区 OTA 升级方案的核心价值,可以用三个词概括:

  1. 冗余(Redundancy):永远有一个备份系统可用
  2. 原子性(Atomicity):要么升级成功,要么完全回滚,没有中间状态
  3. 自愈(Self-Healing):无需人工干预,系统自动恢复

最后一句话:如果你正在开发一个不能随便"变砖"的设备(比如远程部署的基站、医疗设备、自动驾驶汽车),A/B 分区不是可选项,而是必选项


参考资料

本文参考了以下技术文档和最佳实践:

  1. Mender.io - Robust OTA updates with A/B Partitions
  2. Bootlin - Implementing A/B System Updates with U-Boot
  3. RAUC Documentation
  4. U-Boot Boot Count Limit Documentation
  5. NVIDIA Jetson - Rollback Protection

写作时间: 2026-01-23
作者: 80aj.com 技术团队
关键词: A/B分区, OTA升级, Bootloader, U-Boot, 嵌入式Linux, 系统更新, 防变砖

目录 最新
← 左侧翻上一屏 · 右侧翻下一屏 · 中间唤出菜单