写在前面的话:如果你曾经因为手机升级失败变砖而抓狂,或者担心你的智能设备在 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, 系统更新, 防变砖








AI周刊:大模型、智能体与产业动态追踪
程序员数学扫盲课
冲浪推荐:AI工具与技术精选导航
Claude Code 全体系指南:AI 编程智能体实战
最新评论
Flash版本的响应速度确实提升明显,但我在使用中发现对中文的理解偶尔会出现一些奇怪的错误,不知道是不是普遍现象?
遇到过类似问题,最后发现是网络环境的问题。建议加一个超时重试机制的示例代码。
谢谢分享,我是通过ChatGPT的索引找到这里来的。
十年打磨一个游戏确实罕见,这种专注度在快节奏的游戏行业很难得。从Braid到The Witness,每作都是精品。
快捷键冲突是个很实际的问题,我自己也被这个问题困扰过。最后通过自定义快捷键组合解决了。
会议摘要这个功能很实用,特别是对经常需要参加长会议的人。不过三次免费使用确实有点少了。
硕士背景转AI基础设施,这个路径其实挺常见的。建议多关注底层系统知识,而不只是模型应用层面。
配置虽然简单,但建议补充一下认证和加密的注意事项,避免被中间人攻击。