2、基础知识:Android系统分区布局、bootloader、recovery模式、A/B系统等核心术语
在深入Android OTA远程升级的实现细节之前,必须先建立一套统一的术语体系和底层架构认知。OTA升级并非简单的文件拷贝,而是涉及系统启动链、分区读写策略、异常恢复机制以及硬件抽象层的复杂系统工程。本章将围绕四个核心维度展开:Android系统分区布局、Bootloader的引导职责、Recovery模式的双重角色,以及A/B(无缝)系统的设计哲学。理解这些基础概念,是后续分析升级脚本、差分算法、签名校验和失败回滚的前提。
2.1 Android系统分区布局:升级操作的对象
Android设备内部存储(eMMC或UFS)被划分为多个固定大小的逻辑区域,称为分区。每个分区承载特定功能,且挂载点、文件系统格式和读写权限各不相同。OTA升级的本质,就是精确地重写这些分区中的部分或全部数据块。因此,理解分区布局是分析升级包内容、判断升级影响范围的基础。
核心分区及其职责
| 分区名 | 挂载点 | 文件系统 | 主要职责 | OTA升级影响 |
|---|---|---|---|---|
| boot | 无(直接加载) | 原始镜像 | Linux内核 + ramdisk(含init进程) | 内核更新时重写 |
| system | /system | ext4 / erofs | Android框架、系统应用、核心库 | 主要升级对象 |
| vendor | /vendor | ext4 / erofs | 硬件抽象层(HAL)、厂商专有库 | 驱动更新时重写 |
| recovery | 独立系统 | 原始镜像 | 恢复模式内核和ramdisk | 非A/B设备升级时使用 |
| cache | /cache | ext4 | OTA包暂存、恢复日志 | 升级前可能被清空 |
| userdata | /data | f2fs / ext4 | 用户应用、设置、数据库 | 通常不参与升级 |
| misc | 无 | 原始 | 启动控制信息(如bootloader message) | 记录升级状态 |
现代Android设备还引入了system-as-root机制,即system分区直接作为根文件系统挂载,而ramdisk被合并进system分区。此外,动态分区(Dynamic Partition)技术允许system、vendor、product等逻辑分区在super物理分区内动态调整大小,这进一步增加了OTA脚本的复杂度——升级包必须处理逻辑分区的大小调整和快照操作。
updater-script中的block_image_update命令,它会明确列出需要写入的分区名和偏移量。如果升级包同时包含system和vendor的补丁,说明这是一次框架+驱动的联合升级。
2.2 Bootloader:启动链的守门人
Bootloader是设备上电后执行的第一段代码,通常存储在专用的启动ROM或分区中。它的核心职责并非“加载系统”,而是验证并引导下一个启动阶段。在OTA升级场景中,Bootloader扮演着两个关键角色:一是决定从哪个槽位启动(A/B设备),二是校验boot分区的完整性和签名。
启动流程与校验机制
- 硬件初始化:设置CPU、内存控制器、时钟频率,建立最小运行环境。
- 读取启动配置:从misc分区读取bootloader message,判断是正常启动还是进入recovery。
- 槽位选择(A/B设备):读取misc分区中的slot信息,根据
bootctrlHAL提供的状态决定启动槽。 - 签名验证:使用公钥验证boot分区头部和内核镜像的签名,防止篡改。
- 加载并跳转:将内核和ramdisk加载到内存,设置启动参数,跳转到内核入口。
对于非A/B设备,Bootloader还会检查recovery分区是否存在且有效。如果用户通过按键组合触发恢复模式,Bootloader会直接加载recovery分区而不是boot分区。
2.3 Recovery模式:传统升级的执行者
Recovery是一个独立于主系统的微型Linux环境,包含自己的内核和ramdisk。在非A/B设备上,Recovery是OTA升级的核心执行者;在A/B设备上,Recovery退化为备用工具,主要用于工厂重置和诊断。
Recovery的工作流程
- 接收命令:通过
/cache/recovery/command文件接收升级指令,内容如--update_package=/sdcard/ota.zip。 - 挂载分区:挂载/cache和/misc分区,读取升级包和状态信息。
- 执行升级脚本:解析
META-INF/com/google/android/updater-script,逐条执行分区写入、权限设置、符号链接创建等操作。 - 写入日志:将升级过程输出到
/cache/recovery/log,供后续排障。 - 重启:升级完成后,清除command文件并重启进入新系统。
Recovery模式的一个关键限制是无法并发读写——在升级过程中,目标分区必须处于未挂载状态。这意味着升级期间系统完全不可用,用户会看到经典的“机器人倒地”界面,且升级时间较长(通常3-10分钟)。
adb shell进入recovery环境,查看/cache/recovery/log发现错误:E:failed to verify whole-file signature。进一步检查发现,OTA包在下载过程中被截断,导致SHA-256校验失败。重新下载完整包后升级成功。此案例说明,Recovery的签名校验是防止损坏包写入的关键防线。
2.4 A/B系统:无缝升级的工程革命
A/B(Seamless)系统将系统分区划分为A和B两个槽位,每个槽位包含完整的boot、system、vendor等分区。设备当前从活动槽启动,OTA升级则在后台将新系统写入非活动槽。升级完成后,只需重启并切换槽位即可生效。
A/B系统的核心优势与代价
| 维度 | 传统模式(非A/B) | A/B模式 |
|---|---|---|
| 升级过程可用性 | 不可用(需进入recovery) | 完全可用(后台写入) |
| 失败回滚 | 困难,可能变砖 | 自动回滚到上一个槽位 |
| 存储占用 | 单份系统空间 | 双份系统空间(约增加2-4GB) |
| 升级时间感知 | 用户等待3-10分钟 | 后台下载+写入,重启仅需几秒 |
| recovery依赖 | 核心依赖 | 仅作为备用 |
A/B系统的关键机制是槽位切换。Bootloader通过读取misc分区中的slot_suffix(如“_a”或“_b”)决定启动哪个槽位。升级时,update_engine服务将新系统写入非活动槽,并在misc分区标记该槽位为“可启动”。重启后,Bootloader尝试从新槽位启动;如果启动失败(如内核panic),Bootloader会自动回退到旧槽位。
adb root; adb shell update_engine_client --rollback。该命令会将活动槽位切换回旧版本,并标记新槽位为不可用。此案例说明,A/B回滚机制主要针对“无法启动”级别的故障,对于功能性问题仍需人工干预。
A/B系统还引入了合并系统(Merge System)概念,即升级包可以只包含差异数据,通过update_engine的Cow(Copy-on-Write)机制在非活动槽上重建完整系统。这大大减少了OTA包的大小,但增加了升级逻辑的复杂度。
adb shell update_engine_client --payload命令手动触发升级,并观察/data/misc/update_engine/log日志。该日志会详细记录每个分区的写入进度和校验结果,是定位升级失败的首选入口。
2.5 核心术语对比与选型考量
在实际项目中,选择传统模式还是A/B模式,取决于产品定位和硬件约束。对于低端设备(存储小于16GB),传统模式可节省存储成本;对于中高端设备,A/B模式带来的用户体验提升(无中断升级、自动回滚)往往更值得投入。此外,还需考虑虚拟A/B(Virtual A/B)方案,它结合了动态分区和A/B的优势,通过快照实现“逻辑双槽”,进一步减少物理存储占用。
| 术语 | 定义 | 与OTA的关系 |
|---|---|---|
| bootloader | 引导加载程序,验证并加载内核 | 决定启动槽位,校验boot签名 |
| recovery | 独立恢复系统 | 传统升级的执行环境 |
| misc | 启动控制信息分区 | 存储槽位状态和升级命令 |
| update_engine | A/B升级守护进程 | 后台执行分区写入和校验 |
| updater-script | 升级脚本(Edify语言) | 定义传统模式下的具体操作 |
| payload.bin | A/B升级数据文件 | 包含所有分区的差分或全量数据 |
理解这些术语的层次关系至关重要:Bootloader是启动链的起点,Recovery是传统升级的执行者,A/B系统是升级架构的演进方向,而分区布局则是所有升级操作的物质基础。在后续章节中,我们将基于这些概念,深入剖析OTA包的生成、签名、差分算法和客户端执行流程。