4. A/B无缝升级机制
A/B 无缝升级的核心目标,是让设备在后台把新系统写入“非当前运行”的槽位,用户继续使用旧系统;重启时由引导链决定进入哪个槽位,从而把停机时间压缩到一次重启。要真正理解这套机制,需要把 slot 元数据、boot control HAL、snapuserd、COW 快照和回滚判定串成一条完整链路来看。
一、slot 与元数据:升级状态的唯一事实来源
设备通常有 slot_a 和 slot_b 两个槽位,每个槽位包含 boot、system、vendor 等分区。当前运行槽位称为 active slot,另一个是 inactive slot。升级时,OTA 客户端把新镜像写入 inactive slot,并更新元数据,标记该槽位为“待尝试启动”。
元数据一般保存在 misc 分区或专门的 bootloader_control 结构中,关键字段包括:
slot_suffix:当前活动槽位,如_a或_b;bootable:该槽位是否可启动;successful:该槽位是否已被标记为启动成功;tries_remaining:剩余尝试启动次数,常见默认值为 3;verity_mode:是否启用 dm-verity 校验。
升级完成后,inactive slot 被置为 bootable=true、successful=false、tries_remaining=3。重启后 bootloader 读取这些字段,决定是否切换到新槽位。
二、boot control HAL:上层与引导链的桥梁
Android 框架不直接写分区,而是通过 boot control HAL 暴露的接口操作元数据。典型接口包括:
getCurrentSlot() // 返回当前活动槽位
markBootSuccessful() // 将当前槽位标记为成功
setActiveBootSlot(slot) // 设置下次启动槽位
setSlotAsUnbootable(slot) // 将某槽位标记为不可启动
升级流程中,OTA 客户端调用 setActiveBootSlot(_b),把下次启动目标切到 B 槽。重启后,如果新系统正常启动,init 会在启动后期调用 markBootSuccessful(),把 B 槽的 successful 置为 true,并清零 tries_remaining。如果启动失败,bootloader 会递减 tries_remaining,归零后自动回退到 A 槽。
tries_remaining 是回滚策略的核心计数器。它由 bootloader 在每次尝试启动时递减,而不是由 Android 框架递减。因此,调试回滚问题时应优先查看 bootloader 日志和 misc 分区内容。三、snapuserd 与 COW 快照:让写入不阻塞运行
在 A/B 升级中,inactive slot 的镜像写入通常采用“先写 COW 快照,再合并”的方式。snapuserd 是 Android 用户空间守护进程,负责管理 dm-snapshot 的 COW 设备。它的作用不是直接写 system 分区,而是把对旧分区的修改记录到 COW 文件中,使旧系统在升级期间仍可正常读写。
以一次典型 OTA 为例:
- OTA 客户端把新镜像下载到
/data/ota_package; - snapuserd 为每个需要更新的分区创建 COW 设备,例如
system_b-cow; - 写入新数据时,dm-snapshot 把原始块读入 COW,再写入新块;
- 升级完成后,COW 数据在重启时由 snapuserd 合并到目标槽位。
这种设计的关键收益是:旧槽位在升级期间保持完整可读,用户不会感知到分区被覆盖;同时,COW 文件位于 /data,可以按需分配空间,避免一次性占用大量连续分区空间。
四、回滚策略:tries_remaining 与启动成功标记
回滚不是“重新安装旧系统”,而是让 bootloader 在多次启动失败后自动切回旧槽位。判定逻辑如下:
- 新槽位启动时,bootloader 读取
tries_remaining,若大于 0 则递减并尝试启动; - 若系统成功启动到
init后期,调用markBootSuccessful(),将successful置为 true,tries_remaining清零; - 若连续启动失败,
tries_remaining归零,bootloader 将 active slot 切回旧槽位,并保持旧槽位successful=true; - 回滚后,旧槽位继续正常运行,新槽位可被标记为
unbootable,等待下一次 OTA 重新写入。
markBootSuccessful() 的调用时机非常关键。如果系统在启动早期崩溃,尚未调用该接口,则 tries_remaining 会继续递减,最终触发回滚。因此,调试“升级后无法进入新系统”时,应检查 init 日志中 markBootSuccessful 是否被调用,以及 bootloader 是否已递减计数器。五、实际调试与验证要点
在工程实践中,可以通过以下方式验证 A/B 升级链路是否正常:
# 查看当前 slot
adb shell getprop ro.boot.slot_suffix
# 查看 boot control HAL 状态
adb shell dumpsys boot_control
# 查看 snapuserd 状态
adb shell ps -A | grep snapuserd
# 查看 COW 设备
adb shell ls -l /dev/block/mapper/ | grep cow
如果 dumpsys boot_control 显示 current_slot 与预期不符,应检查 OTA 客户端是否调用了 setActiveBootSlot;如果 snapuserd 未运行,则 COW 合并可能失败,导致新槽位数据不完整。回滚后,可通过 getprop ro.boot.slot_suffix 确认是否已切回旧槽位,并检查旧槽位的 successful 字段是否仍为 true。
整体来看,A/B 无缝升级的可靠性依赖三个层面的协同:boot control HAL 负责元数据切换,snapuserd 负责 COW 快照与合并,bootloader 负责基于 tries_remaining 的回滚判定。理解这三者的分工与数据流向,才能在 OTA 失败时快速定位是写入问题、元数据问题还是启动判定问题。