写在前面的话:如果你曾经因为手机升级失败变砖而抓狂,或者担心你的智能设备在 OTA 更新时突然断电导致报废,那这篇文章就是为你准备的。我会用最直白的语言,带你理解为什么现代嵌入式 Linux 系统能做到"永不变砖"——即使你在升级过程中拔电源。
一、先说个恐怖故事:传统升级方式有多危险?
想象一下这个场景:
你的智能路由器提示有新固件可以升级。你点了"确认",然后设备开始下载、写入新系统。就在写入进行到一半的时候,突然停电了。
传统的单分区升级方式下,会发生什么?
- ❌ 旧系统已经被覆盖了一半 → 不完整,无法启动
- ❌ 新系统还没写完 → 也不完整,同样无法启动
- ❌ 设备彻底变砖 → 你只能拿着螺丝刀拆机,用串口线刷机救砖
这就是传统升级方案的"单点失效"噩梦——一旦升级过程中断,设备就是一块废铁。
二、A/B 分区的核心思想:永远留一条后路
A/B 分区升级方案的设计哲学非常简单,就像你在走钢丝时,永远有一张安全网在下面接着你。
核心原理:两套系统,轮流上岗
想象你的设备存储空间被分成了两个"房间":
- 房间 A (Slot A):当前正在运行的系统住在这里
- 房间 B (Slot B):备用房间,平时空着
当 OTA 升级来了:
- 系统继续在房间 A 运行 → 你的设备正常工作,用户无感知
- 新版本悄悄写入房间 B → 后台静默下载、校验、安装
- 写入完成后,设备重启 → Bootloader 引导程序说:"这次我们去房间 B 启动"
- 如果房间 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 次都进不了系统),它会:
- 🔄 切换回房间 A
- 🧹 重置启动计数器
- ✅ 设备恢复正常,就像升级从未发生过
用户体验:设备可能重启了几次,但最终还是能用。
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(数据分区),它:
- 🔒 不参与 A/B 切换 → 永远存在
- 📂 挂载到固定路径 → 比如
/data或/var/lib - 🔗 通过软链接共享 → 应用读取
/etc/config,实际指向/data/config
示意图
┌──────────────────────────────────────────┐
│ 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)机制:
- 每个固件版本有一个安全版本号(SWVN,Software Version Number)
- 芯片内部有一个硬件版本号(HWVN,Hardware Version Number)
- 启动时 Bootloader 会比较: 如果
SWVN < HWVN,拒绝启动 - 升级成功后,烧写 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 升级方案的核心价值,可以用三个词概括:
- 冗余(Redundancy):永远有一个备份系统可用
- 原子性(Atomicity):要么升级成功,要么完全回滚,没有中间状态
- 自愈(Self-Healing):无需人工干预,系统自动恢复
最后一句话:如果你正在开发一个不能随便"变砖"的设备(比如远程部署的基站、医疗设备、自动驾驶汽车),A/B 分区不是可选项,而是必选项。
参考资料
本文参考了以下技术文档和最佳实践:
- Mender.io - Robust OTA updates with A/B Partitions
- Bootlin - Implementing A/B System Updates with U-Boot
- RAUC Documentation
- U-Boot Boot Count Limit Documentation
- NVIDIA Jetson - Rollback Protection
写作时间: 2026-01-23
作者: 80aj.com 技术团队
关键词: A/B分区, OTA升级, Bootloader, U-Boot, 嵌入式Linux, 系统更新, 防变砖