第四章:AB系统升级原理

AB系统升级,说白了就是让设备拥有两套系统分区。一套在跑,一套待命。我刚开始接触这个机制时,觉得它挺绕的。但搞懂了之后,你会发现它其实很巧妙。

4.1 AB Slot机制

AB系统把存储空间分成两个槽位:Slot A 和 Slot B。每个槽位都包含完整的系统分区——boot、system、vendor 等等。你想想看,这就像飞机有两个引擎,一个在工作,另一个随时准备顶上。

为什么要这么设计?

传统升级方式有个致命问题:升级过程中一旦断电或出错,设备就变砖了。我在项目中遇到过好几次这种情况,用户升级到一半手机没电了,回来就开不了机。那叫一个头疼。

AB系统完美解决了这个问题。升级时,系统在 unactive slot 上操作,active slot 继续正常运行。就算升级失败,大不了回退到原来的系统。

核心概念:

  • active slot:当前正在运行的系统槽位
  • unactive slot:待升级或待切换的槽位
  • 两个槽位大小相同,分区布局一致

4.2 active slot 与 unactive slot 切换

切换逻辑其实不复杂。我来给你捋一捋:

  1. 设备启动时,bootloader 读取 misc 分区中的 slot 信息
  2. 确定哪个 slot 是 active,然后从它启动
  3. 升级时,把新系统写入 unactive slot
  4. 写入完成后,标记 unactive slot 为可启动
  5. 重启后,bootloader 切换到新的 slot

嗯,这里要注意一点。切换不是简单地把 active 和 unactive 互换。系统会记录每个 slot 的启动次数和状态。我记得有一次调试,发现设备反复在两个 slot 之间跳来跳去,查了半天才发现是启动计数逻辑写错了。

个人经验:我建议你在切换逻辑中加入「回滚保护」。比如新 slot 启动失败超过3次,就自动切回旧 slot。这个机制救过我不少次。

4.3 boot_control HAL 接口

boot_control HAL 是连接系统和 bootloader 的桥梁。说白了,它就是一套标准接口,让上层应用能控制底层槽位切换。

来看看核心接口定义:

// hardware/libhardware/include/hardware/boot_control.h

typedef struct boot_control_module {
    struct hw_module_t common;

    // 获取当前 active slot
    unsigned (*getActiveBootSlot)(struct boot_control_module *module);

    // 设置 active slot
    int (*setActiveBootSlot)(struct boot_control_module *module,
                             unsigned slot);

    // 标记 slot 为可启动
    int (*markBootSuccessful)(struct boot_control_module *module);

    // 获取 slot 信息
    int (*getSlotInfo)(struct boot_control_module *module,
                       unsigned slot,
                       struct boot_slot_info *info);
} boot_control_module_t;

这些接口看着简单,但实现起来有不少坑。我拿 markBootSuccessful 举个例子:

  • 系统启动后,需要调用这个接口告诉 bootloader「我起来了」
  • 如果没调用,bootloader 会认为这次启动失败
  • 连续失败几次,就会触发回滚

你可能会问:什么时候调用最合适?我个人习惯是在系统服务启动完成后调用。太早调用,万一后面崩溃了,bootloader 还以为系统是好的。太晚调用,又可能被误判为启动失败。

避坑指南:我曾经在一个项目里,把 markBootSuccessful 放在了 init.rc 中。结果有个服务启动特别慢,bootloader 等不及就切回去了。后来我改成了在 SystemServer 启动完成后调用,问题就解决了。

4.4 实际移植中的注意事项

移植 AB 系统时,有几个关键点你得盯紧了:

组件 注意事项
bootloader 必须支持读取 misc 分区,理解 slot 切换逻辑
分区表 每个槽位要有独立的分区,大小要一致
recovery AB 系统下 recovery 可以放在 boot 分区中
升级脚本 要能正确识别当前 active slot,写入 unactive slot

嗯,分区表这块最容易出问题。我见过有人把 Slot A 和 Slot B 的大小搞不一样,结果升级时数据写不进去。你想想看,这得多尴尬。

4.5 总结

AB 系统升级的核心就三点:

  • 两个槽位,一个跑一个备
  • 切换逻辑要可靠,带回滚保护
  • boot_control HAL 接口要正确实现

搞懂了这些,你就能理解为什么现在的手机升级很少变砖了。说白了,就是 AB 系统在背后兜底。我做了这么多年移植,最深的体会就是:系统升级这件事,宁可慢一点,也要稳一点。毕竟用户的数据比什么都重要。