空中下载(OTA)更新是 Android 设备获取全新系统镜像、安全补丁以及功能更新的机制,无需物理接入设备或者手动刷机。该机制最初只是简单的 "下载 zip 包,进入 recovery 模式,执行更新" 模型,如今已经演变为 AOSP 中最复杂的子系统之一:包含专用原生守护进程update_engine、内核级写时复制快照、Bootloader 交互协议,以及流式处理流水线,无需将完整镜像写入 userdata 分区即可对 GB 级更新载荷执行更新。
本章完整跟踪一次 OTA 更新的全流程:从服务器宣告更新可用,到设备重启进入新版软件并标记插槽更新成功。我们会逐层分析:载荷二进制格式、update_engine内部动作流水线、A/B 与 Virtual A/B 插槽切换机制、实现用户态压缩写时复制的snapuserd守护进程、用于生成更新载荷的 Python 工具链、作为传统降级方案的 Recovery 模式,以及把所有模块串联起来的框架 API。
55.1 OTA 架构总览
55.1.1 三种更新方案
Android 发展历程中一共使用过三种截然不同的 OTA 方案。理解全部三种方案十分必要,因为量产设备仍然在使用这全部方案。
源码路径: system/update_engine/ ------ A/B 和 Virtual A/B 引擎 bootable/recovery/ ------ 非 A/B 的 recovery 更新器 system/fs/fs_mgr/libsnapshot/ ------ Virtual A/B 快照
Android 17 中快照相关代码已经移出system/core:libsnapshot、snapuserd以及 COW 格式实现全部迁移至system/fs/fs_mgr/libsnapshot/(旧版本使用的system/core/fs_mgr/libsnapshot/路径已经不复存在)。本章中所有快照相关引用均使用新路径。
非 A/B(传统方案):Android1.0 至 Android9 使用的原始方案(目前仍然保留支持)。设备仅有一套分区(system、boot、vendor 等)外加独立的 recovery 分区。更新时设备重启进入 recovery,挂载 OTA 压缩包(签名 zip,包含更新二进制与镜像数据),原地执行块级补丁更新。如果更新中途失败,设备可能直接无法开机,也就是俗称的 "变砖"。
A/B(无缝更新) :Android7.0 引入。设备为每一个可更新分区维护两份副本:A 插槽与 B 插槽。用户运行其中一个插槽时,update_engine在后台将新镜像写入另一个未使用插槽。写入完成后通知 Bootloader 切换激活插槽。如果新插槽启动失败,Bootloader 会自动回滚。整个 OTA 流程不需要进入 recovery 模式,写入阶段用户完全无感知。代价是分区存储空间几乎翻倍。
Virtual A/B :Android11 引入,Android13 起强制要求。Virtual A/B 保留 A/B 方案的无缝更新体验,但不再要求完整复制全部分区。它依靠 device‑mapper 快照(Android12 起通过snapuserd实现压缩写时复制),更新过程中仅存储发生变更的数据块。重启并校验成功后,快照会合并回基础分区,释放临时占用空间。兼顾 A/B 方案的可靠性与接近传统非 A/B 方案的存储开销。
| 方案 | 系统版本 | 分区配置 | 更新环境 | 特点 |
|---|---|---|---|---|
| 非 A/B(传统) | Android1.0‑9 | 单套分区 | Recovery 模式 | 更新需要停机;失败有变砖风险 |
| A/B(无缝) | Android7.0+ | 双套分区 (A/B 插槽) | update_engine 后台写入 | 零写入停机时间;自动 Bootloader 回滚;存储开销约 100% |
| Virtual A/B | Android11+ | 单套物理分区 | COW 快照保存变更块 | 无缝更新 + 存储高效;Android12 + 启用 snapuserd 压缩;Android17 可选 UBLK 用户态块后端;REPLACE 操作支持 zstd 压缩;移除 squashfs OTA 支持 |

55.1.2 高层数据流
无论采用哪一种方案,所有 OTA 更新都遵循统一生命周期:

55.1.3 分区布局对比
下表汇总三种方案下分区组织方式:
表格
| 对比项 | 非 A/B | A/B | Virtual A/B |
|---|---|---|---|
| 物理分区 | system, boot, vendor, recovery | system_a/b, boot_a/b, vendor_a/b | system_a/b (逻辑), boot_a/b (物理) |
| Recovery 分区 | 独立存在 | 无(recovery 嵌入 boot) | 无(recovery 嵌入 boot 或 init_boot) |
| 存储开销 | 约 0% | 约 100%(完整复制) | 约 5‑15%(仅 COW 变更块) |
| 更新目标 | 正在运行的分区原地更新 | 非激活插槽 | 映射在非激活插槽之上的 COW 设备 |
| 回滚能力 | 无法保证 | Bootloader 自动回滚 | Bootloader 自动回滚 |
| 停机时间 | 完整重启 + 更新执行时间 | 仅重启(约 30 秒) | 仅重启(约 30 秒) |
| 更新后合并操作 | 无 | 无 | 后台执行 COW 合并 |
| 最低 Android 版本 | 1.0 | 7.0 | 11 |
55.1.4 关键系统属性
更新方案由系统属性与 fstab 配置决定:
Go
# A/B设备检测
ro.boot.slot_suffix=_a # A/B与Virtual A/B设备存在此属性
ro.build.ab_update=true # 支持A/B更新
# Virtual A/B检测
ro.virtual_ab.enabled=true # 开启Virtual A/B
ro.virtual_ab.retrofit=true # 为后期升级支持,而非出厂原生支持
# Virtual A/B压缩配置
ro.virtual_ab.compression.enabled=true
ro.virtual_ab.userspace.snapshots.enabled=true
ro.virtual_ab.compression.xor.enabled=true
# Android17 Virtual A/B UBLK后端
ro.virtual_ab.ublk.enabled=true # 设备配置使用UBLK快照后端
属性ro.virtual_ab.ublk.enabled只是IsUblkEnabled()函数判断的三个条件之一,只有该属性、aconfig 功能标记、6.6 及以上内核版本同时满足,快照才会使用 UBLK 而不是 dm‑user;详见 55.26 节。
能力检测源码位置:system/fs/fs_mgr/libsnapshot/capabilities.cpp
相关功能标记检测代码位于: system/update_engine/aosp/dynamic_partition_control_android.h
DynamicPartitionControlAndroid对外暴露接口:
cpp
FeatureFlag GetDynamicPartitionsFeatureFlag() override;
FeatureFlag GetVirtualAbFeatureFlag() override;
FeatureFlag GetVirtualAbCompressionFeatureFlag() override;
FeatureFlag GetVirtualAbCompressionXorFeatureFlag() override;
每一个FeatureFlag取值可为NONE、RETROFIT、LAUNCH,用于区分设备是后期升级获得该特性,还是出厂原生搭载该特性。
55.1.5 源码目录总览
system/update_engine/
main.cc # 守护进程入口
aosp/
daemon_android.cc # Android平台守护进程初始化
update_attempter_android.cc # 编排更新执行流程
boot_control_android.cc # 通过HAL控制A/B插槽
binder_service_android.cc # 供框架调用的Binder接口
dynamic_partition_control_android.cc # 动态分区+VAB控制
cleanup_previous_update_action.cc # 重启后触发快照合并
payload_consumer/
delta_performer.cc # 执行载荷操作
payload_metadata.cc # 解析载荷头部元数据
payload_constants.cc # 魔数、版本常量
vabc_partition_writer.cc # Virtual A/B压缩写入器
partition_writer.cc # 标准分区写入器
install_plan.h # 更新计划数据结构
payload_generator/
delta_diff_generator.cc # 生成差分载荷
full_update_generator.cc # 生成全量载荷
common/
boot_control_interface.h # 插槽管理抽象接口
action_processor.cc # Action流水线调度器
scripts/
brillo_update_payload # 载荷操作shell工具
build/make/tools/releasetools/
ota_from_target_files.py # OTA包生成主脚本
non_ab_ota.py # 传统非A/B OTA生成脚本
bootable/recovery/
recovery_main.cpp # Recovery入口
recovery.cpp # Recovery主逻辑
install/install.cpp # 更新包安装逻辑
update_verifier/ # 开机后完整性校验
system/fs/fs_mgr/libsnapshot/ # Android17从system/core迁移至此
snapshot.cpp # 快照管理器
capabilities.cpp # UBLK能力判断
libsnapshot_cow/
writer_v3.cpp # COW v3写入器
snapuserd/
snapuserd_daemon.cpp # 守护进程入口,选择UBLK/dm‑user后端
dm_user_block_server.cpp # dm‑user后端实现
ublk_block_server.cpp # Android17新增UBLK后端
user‑space‑merge/
snapuserd_core.cpp # 合并核心逻辑
frameworks/base/core/java/android/os/
UpdateEngine.java # 框架层API封装
55.2 update_engine
update_engine是实现 A/B 与 Virtual A/B 更新的原生守护进程。最初为 Chrome OS 开发,从 Android7.0 的 A/B 方案开始适配 Android 平台。在 Android 中它作为常驻系统服务运行,通过 Binder 接收更新指令。
55.2.1 守护进程生命周期
守护进程入口在main.cc:
cpp
int main(int argc, char** argv) {
chromeos_update_engine::Terminator::Init();
gflags::SetUsageMessage("A/B Update Engine");
gflags::ParseCommandLineFlags(&argc, &argv, true);
// ... 日志初始化 ...
xz_crc32_init();
umask(S_IRWXG | S_IRWXO); // 严格权限掩码
auto daemon = chromeos_update_engine::DaemonBase::CreateInstance();
int exit_code = daemon->Run();
// ...
}
Android 平台下,DaemonBase::CreateInstance()返回DaemonAndroid实例。
源码位置:system/update_engine/aosp/daemon_android.cc
cpp
int DaemonAndroid::OnInit() {
subprocess_.Init(this);
int exit_code = brillo::Daemon::OnInit();
android::BinderWrapper::Create();
binder_watcher_.Init();
DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid();
daemon_state_.reset(daemon_state_android);
daemon_state_android->Initialize();
// 注册Binder服务
binder_service_ = new BinderUpdateEngineAndroidService{
daemon_state_android->service_delegate()};
binder_wrapper->RegisterService(
binder_service_->ServiceName(), binder_service_);
// 同时注册用于跨版本兼容的stable AIDL服务
stable_binder_service_ = new BinderUpdateEngineAndroidStableService{
daemon_state_android->service_delegate()};
binder_wrapper->RegisterService(
stable_binder_service_->ServiceName(), stable_binder_service_);
daemon_state_->StartUpdater();
return EX_OK;
}
守护进程注册两个 Binder 服务:
android.os.UpdateEngineService------ 主接口- stable AIDL 变体 ------ 用于跨版本兼容性
55.2.2 Action 流水线
update_engine采用 Action 流水线模式。更新的每一步都是Action的子类,由ActionProcessor串联执行。Action 之间依靠类型安全的ActionPipe传递数据。
典型 Action

源码:system/update_engine/common/action.h、system/update_engine/common/action_processor.h
ActionProcessor同一时刻只执行一个 Action。当一个 Action 执行完毕(成功或失败),调度器执行下一个或者终止流水线。
cpp
// action_processor.cc
void ActionProcessor::ActionComplete(AbstractAction* actionptr,
ErrorCode code) {
// ... notify delegate ...
if (code != ErrorCode::kSuccess) {
// Pipeline failed
actions_.clear();
// ... error handling ...
} else {
// Advance to next action
actions_.erase(actions_.begin());
if (!actions_.empty()) {
actions_.front()->PerformAction();
}
}
}
55.2.3 UpdateAttempterAndroid
UpdateAttempterAndroid是 Android 更新的顶层编排类。它实现ServiceDelegateAndroidInterface(Binder 服务调用入口)以及ActionProcessorDelegate(接收流水线回调)。
源码位置:system/update_engine/aosp/update_attempter_android.h
主要职责:
ApplyPayload:更新入口,解析 URL / 文件描述符、请求头,构造InstallPlan,构建并启动 Action 流水线SuspendUpdate / ResumeUpdate:暂停、恢复正在进行的下载CancelUpdate:终止正在运行的更新并清理资源ResetStatus:清除已完成或失败更新的持久化状态CleanupSuccessfulUpdate:触发 Virtual A/B 快照合并
更新状态机枚举:
cpp
enum class UpdateStatus {
IDLE,
CHECKING_FOR_UPDATE,
UPDATE_AVAILABLE,
DOWNLOADING,
VERIFYING,
FINALIZING,
UPDATED_NEED_REBOOT,
REPORTING_ERROR_EVENT,
ATTEMPTING_ROLLBACK,
DISABLED,
CLEANUP_PREVIOUS_UPDATE,
};

55.2.4 OTA 结果追踪
重启后,update_engine会判断上一次更新尝试的结果。
cpp
enum class OTAResult {
NOT_ATTEMPTED,
ROLLED_BACK,
UPDATED_NEED_REBOOT,
OTA_SUCCESSFUL,
};
GetOTAUpdateResult()读取持久化配置以及 Boot 插槽状态,判断更新是成功、回滚还是从未执行。该结果用于指标上报以及合并任务调度。
55.2.5 构建更新 Action
调用ApplyPayload时,BuildUpdateActions构建流水线:

55.2.6 Binder 服务接口
Binder 接口对外暴露主要方法:
| 方法 | 说明 |
|---|---|
applyPayload(url, offset, size, headers) |
从 URL 启动更新 |
applyPayloadFd(fd, offset, size, headers) |
从文件描述符启动更新 |
bind(callback) |
注册状态回调 |
suspend() |
暂停下载 |
resume() |
恢复下载 |
cancel() |
取消更新 |
resetStatus() |
清除完成 / 失败状态 |
verifyPayloadApplicable(metadata_file) |
校验载荷是否可以应用到本机 |
allocateSpaceForPayload(metadata, headers) |
为 VAB 预分配存储空间 |
cleanupSuccessfulUpdate(callback) |
触发快照合并 |
setShouldSwitchSlotOnReboot(metadata) |
设置重启切换插槽标记 |
resetShouldSwitchSlotOnReboot() |
清除重启切换插槽标记 |
triggerPostinstall(partition) |
执行指定分区 postinstall 脚本 |
applyPayload传入的 headers 是一组键值对,控制更新行为:
cpp
METADATA_HASH=<base64> -- 预期的载荷元数据哈希
METADATA_SIZE=<bytes> -- 元数据大小
PAYLOAD_HASH=<base64> -- 完整载荷预期哈希
PAYLOAD_SIZE=<bytes> -- 完整载荷大小
SWITCH_SLOT_ON_REBOOT=1 -- 是否切换插槽,默认1
RUN_POST_INSTALL=1 -- 是否运行postinstall,默认1
NETWORK_ID=<id> -- 指定下载使用的网络
55.3 载荷格式
OTA 载荷是二进制文件,包含将源分区布局转换为目标分区布局所需全部信息。全量更新与差分(增量)更新使用同一套格式。
55.3.1 载荷二进制结构
源码:system/update_engine/payload_consumer/payload_constants.cc、system/update_engine/payload_consumer/payload_metadata.cc
载荷结构:
- 载荷头部(24 字节)
- Magic:
CrAU(4 字节) - Major Version(8 字节 uint64)
- Manifest Size(8 字节 uint64)
- Metadata Sig Size(4 字节 uint32)
- Magic:
DeltaArchiveManifest(protobuf):分区列表、操作、块大小、时间戳- 元数据签名(长度由头部指定,可选)
- 二进制数据块:各个操作对应的压缩 / 原始数据
- 载荷签名:文件末尾追加
头部解析逻辑位于PayloadMetadata::ParsePayloadHeader
const uint64_t PayloadMetadata::kDeltaVersionOffset = sizeof(kDeltaMagic); // 4
const uint64_t PayloadMetadata::kDeltaVersionSize = 8;
const uint64_t PayloadMetadata::kDeltaManifestSizeOffset =
kDeltaVersionOffset + kDeltaVersionSize; // 12
const uint64_t PayloadMetadata::kDeltaManifestSizeSize = 8;
const uint64_t PayloadMetadata::kDeltaMetadataSignatureSizeSize = 4;
// 头部总长度:4 + 8 + 8 + 4 = 24 bytes
魔数字符串CrAU继承自 Chrome OS 更新格式:
const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'};
55.3.2 主版本与次版本
载荷格式包含两个版本号:
-
主版本:标识整体格式,当前仅支持版本 2(Brillo)
const uint64_t kBrilloMajorPayloadVersion = 2;
const uint64_t kMinSupportedMajorPayloadVersion = kBrilloMajorPayloadVersion;
const uint64_t kMaxSupportedMajorPayloadVersion = kBrilloMajorPayloadVersion; -
次版本:标识支持的操作与特性集合
表格
| 次版本 | 常量 | 特性 |
|---|---|---|
| 0 | kFullPayloadMinorVersion |
全量载荷(不需要源镜像) |
| 2 | kSourceMinorPayloadVersion |
基于源的差分更新(A‑to‑B) |
| 3 | kOpSrcHashMinorPayloadVersion |
每个操作携带源哈希 |
| 4 | kBrotliBsdiffMinorPayloadVersion |
BROTLI_BSDIFF、ZERO、DISCARD |
| 5 | kPuffdiffMinorPayloadVersion |
PUFFDIFF 操作 |
| 6 | kVerityMinorPayloadVersion |
Verity 哈希树 + FEC 生成 |
| 7 | kPartialUpdateMinorPayloadVersion |
局部更新(例如仅更新内核) |
| 8 | kZucchiniMinorPayloadVersion |
ZUCCHINI 二进制差分 |
| 9 | kLZ4DIFFMinorPayloadVersion |
EROFS 使用 LZ4DIFF |
| 10 | kZstdMinorPayloadVersion |
REPLACE_ZSTD,zstd 压缩 REPLACE 操作 |
源码:system/update_engine/payload_consumer/payload_constants.h
Android17 新增次版本 10(kZstdMinorPayloadVersion),kMaxSupportedMinorPayloadVersion更新为此值。载荷次版本为其所使用的最高特性版本;设备拒绝次版本超过本机支持上限的载荷,错误码 45,对应kUnsupportedMinorPayloadVersion。
55.3.3 DeltaArchiveManifest
Manifest 是 protobuf 消息,描述每一个分区以及生成目标镜像需要执行的全部操作。关键字段:
message DeltaArchiveManifest {
repeated PartitionUpdate partitions = 13;
uint32 block_size = 3; // 通常4096
uint32 minor_version = 12;
uint64 max_timestamp = 14; // 防回滚时间戳
DynamicPartitionMetadata dynamic_partition_metadata = 15;
}
message PartitionUpdate {
string partition_name = 1;
repeated InstallOperation operations = 7;
PartitionInfo old_partition_info = 10;
PartitionInfo new_partition_info = 11;
// Verity/FEC字段 ...
repeated CowMergeOperation merge_operations = 18;
}
message InstallOperation {
enum Type {
REPLACE = 0;
REPLACE_BZ = 1;
SOURCE_COPY = 4;
SOURCE_BSDIFF = 5;
ZERO = 6;
DISCARD = 7;
REPLACE_XZ = 8;
PUFFDIFF = 9;
BROTLI_BSDIFF = 10;
ZUCCHINI = 11;
LZ4DIFF_BSDIFF = 12;
LZ4DIFF_PUFFDIFF = 13;
REPLACE_ZSTD = 14; // Android17:写入zstd解压后数据
}
Type type = 1;
repeated Extent src_extents = 6;
repeated Extent dst_extents = 8;
uint64 data_offset = 4;
uint64 data_length = 5;
bytes src_sha256_hash = 9;
bytes data_sha256_hash = 10;
}
55.3.4 InstallOperation 操作类型
每一类操作将源块转换为目标块:
差分操作(依赖源分区)
SOURCE_COPY:从源复制块SOURCE_BSDIFF:应用 bsdiff 补丁BROTLI_BSDIFF:Brotli 压缩的 bsdiffPUFFDIFF:感知 deflate 格式差分ZUCCHINI:可执行文件专用二进制差分LZ4DIFF_*:EROFS 的 LZ4 感知差分
全量操作(不依赖源分区)
REPLACE:写入原始数据REPLACE_BZ:bzip2 解压后写入REPLACE_XZ:XZ 解压后写入REPLACE_ZSTD:zstd 解压后写入(Android17)ZERO:填充 0DISCARD:执行 TRIM/discard
表格
| 操作 | 是否需要源 | 说明 |
|---|---|---|
| REPLACE | 否 | 向目标区域写入未压缩原始数据 |
| REPLACE_BZ | 否 | 解压 bzip2 块写入目标 |
| REPLACE_XZ | 否 | 解压 XZ 块写入目标 |
| REPLACE_ZSTD | 否 | 解压 zstd 块写入目标(Android17) |
| ZERO | 否 | 目标区域全部置零 |
| DISCARD | 否 | 对目标区域执行 discard/trim |
| SOURCE_COPY | 是 | 将源区域复制至目标 |
| SOURCE_BSDIFF | 是 | 读取源,应用 bsdiff 补丁,输出目标 |
| BROTLI_BSDIFF | 是 | 类似 SOURCE_BSDIFF,但补丁使用 Brotli 压缩 |
| PUFFDIFF | 是 | 感知 deflate 流差分,处理 gzip/zlib |
| ZUCCHINI | 是 | 针对可执行程序的二进制差分 |
| LZ4DIFF_BSDIFF | 是 | LZ4 压缩块差分(EROFS 优化) |
| LZ4DIFF_PUFFDIFF | 是 | LZ4 + puffdiff 组合 |
55.3.5 全量载荷 vs 差分载荷
全量载荷 :包含完整目标镜像,全部操作都是 REPLACE 系列、ZERO、DISCARD。不需要源分区。次版本为 0(kFullPayloadMinorVersion)。体积更大,但可以用于任意状态的设备。
差分(增量)载荷 :只编码源镜像到目标镜像之间的差异。使用SOURCE_COPY、SOURCE_BSDIFF、PUFFDIFF、ZUCCHINI等引用源块的操作。体积小很多(通常 50‑200MB,对比全量 2‑4GB),但设备必须运行指定的源版本构建。
- 差分载荷:源分区 → 差分引擎 → 补丁 + 复制块 → 目标分区
- 全量载荷:载荷数据块 → 目标分区
55.3.6 载荷签名与校验
载荷使用密码学签名防止篡改:
- 元数据签名:对头部 + manifest 签名;解析 manifest 之前完成校验,防止 protobuf 解析漏洞被利用
- 载荷签名:对整个载荷(签名段除外)签名;全部操作执行完成后校验
源码:system/update_engine/payload_consumer/payload_verifier.h
设备信任证书存放于/system/etc/security/otacerts.zip(或kUpdateCertificatesPath指定路径)。校验阶段PayloadVerifier从证书取出公钥,校验 RSA/EC 签名。
校验流程:
- update_engine 拿到载荷(头部 + manifest + 数据 + 签名)
- 校验元数据签名:读取 otacerts.zip 证书,校验头部 + manifest 的 RSA/EC 签名;元数据校验 OK
- 执行更新操作
- 对除去签名段的完整载荷计算哈希,与签名内哈希比对,校验载荷签名;载荷校验 OK
55.4 DeltaPerformer
DeltaPerformer是实际向目标分区执行载荷操作的核心工作类。实现FileWriter接口,在下载过程中增量接收载荷字节流。
55.4.1 流式执行
源码:system/update_engine/payload_consumer/delta_performer.h、system/update_engine/payload_consumer/delta_performer.cc
DeltaPerformer::Write()会在网络收到载荷分片时被反复调用。内部维护状态,记录解析与执行进度。
处理流程:
- 通过
Write接收字节 - 判断头部是否解析完成;未完成则向缓冲区累积数据;数据不足返回等待更多数据;数据足够解析魔数、版本、manifest 大小、签名大小
- 判断 manifest 是否接收完整;完整则解析 protobuf manifest,执行 manifest 校验,准备更新分区
- 判断是否全部操作处理完毕;未完成则等待当前操作所需数据充足,执行操作,推进到下一个操作
- 全部完成后提取并校验签名
DeltaPerformer关键状态成员变量:
class DeltaPerformer : public FileWriter {
DeltaArchiveManifest manifest_;
bool manifest_parsed_{false};
bool manifest_valid_{false};
std::vector<PartitionUpdate> partitions_;
size_t current_partition_{0};
size_t next_operation_num_{0};
size_t num_total_operations_{0};
brillo::Blob buffer_; // 累积接收的数据
uint64_t buffer_offset_{0}; // 在数据段内的偏移
uint32_t block_size_{0}; // 取自manifest,通常4096
HashCalculator payload_hash_calculator_;
HashCalculator signed_hash_calculator_;
};
55.4.2 操作分发
manifest 解析完成、分区准备就绪后,根据操作类型分发执行:
bool DeltaPerformer::PerformInstallOperation(
const InstallOperation& operation) {
switch (operation.type()) {
case InstallOperation::REPLACE:
case InstallOperation::REPLACE_BZ:
case InstallOperation::REPLACE_XZ:
case InstallOperation::REPLACE_ZSTD: // Android17
return PerformReplaceOperation(operation);
case InstallOperation::ZERO:
case InstallOperation::DISCARD:
return PerformZeroOrDiscardOperation(operation);
case InstallOperation::SOURCE_COPY:
return PerformSourceCopyOperation(operation, &error);
case InstallOperation::SOURCE_BSDIFF:
case InstallOperation::BROTLI_BSDIFF:
case InstallOperation::PUFFDIFF:
case InstallOperation::ZUCCHINI:
case InstallOperation::LZ4DIFF_BSDIFF:
case InstallOperation::LZ4DIFF_PUFFDIFF:
return PerformDiffOperation(operation, &error);
}
}
REPLACE 系列操作依靠堆叠不同的ExtentWriter实现解压。InstallOperationExecutor::ExecuteReplaceOperation根据操作类型,在基础写入器外层包装BzipExtentWriter、XzExtentWriter,Android17 新增ZstdExtentWriter,再将解压后字节写入目标区域。
源码:system/update_engine/payload_consumer/install_operation_executor.cc、system/update_engine/payload_consumer/zstd_extent_writer.cc
if (operation.type() == InstallOperation::REPLACE_BZ) {
writer = std::make_unique<BzipExtentWriter>(std::move(writer));
} else if (operation.type() == InstallOperation::REPLACE_XZ) {
writer = std::make_unique<XzExtentWriter>(std::move(writer));
} else if (operation.type() == InstallOperation::REPLACE_ZSTD) {
writer = std::make_unique<ZstdExtentWriter>(std::move(writer));
}
ZstdExtentWriter使用流式ZSTD_DStream处理输入字节,将解压结果交给下一层写入器。
55.4.3 PartitionWriter 分区写入器
实际 IO 操作由PartitionWriterInterface实现类完成:
- 标准 A/B 更新:
PartitionWriter直接操作块设备文件描述符 - 开启压缩的 Virtual A/B:
VABCPartitionWriter通过 COW 写入层完成写入
源码:system/update_engine/payload_consumer/vabc_partition_writer.h
接口PartitionWriterInterface对外方法:
Init()PerformZeroOrDiscardOperation()PerformSourceCopyOperation()PerformReplaceOperation()PerformDiffOperation()CheckpointUpdateProgress()FinishedInstallOps()Close()
PartitionWriter:持有目标 fd,直接向块设备写入。 VABCPartitionWriter:持有ICowWriter cow_writer_、ExtentMap xor_map_,所有写入经过 COW 层。
VABC 分区写入器会把 OTA 操作翻译为 COW 操作:
表格
| OTA 操作 | COW 操作 |
|---|---|
| ZERO | COW_ZERO |
| SOURCE_COPY | COW_COPY |
| REPLACE / *_BSDIFF 等 | COW_REPLACE |
55.4.4 检查点与断点续传
DeltaPerformer支持中断更新后恢复。会按照固定周期(kCheckpointFrequencySeconds)把进度保存到持久化配置:
bool DeltaPerformer::CheckpointUpdateProgress(bool force) {
// 保存:当前操作序号、manifest元数据哈希、分区状态等
Checkpoint();
// 恢复更新时,CanResumeUpdate()对比保存的哈希与新载荷哈希,判断是否可以续传
}
如果更新中途断电、设备崩溃,下次调用ApplyPayload会检测已保存检查点,从上次中断位置继续,跳过已经完成的操作。
55.5 A/B 更新:插槽切换与回滚
55.5.1 Boot Control HAL
插槽管理层被抽象在BootControlInterface接口之后。
源码:system/update_engine/common/boot_control_interface.h
class BootControlInterface {
public:
using Slot = unsigned int;
static const Slot kInvalidSlot = UINT_MAX;
virtual unsigned int GetNumSlots() const = 0;
virtual Slot GetCurrentSlot() const = 0;
virtual bool GetPartitionDevice(const std::string& partition_name,
Slot slot, std::string* device) const = 0;
virtual bool IsSlotBootable(Slot slot) const = 0;
virtual bool MarkSlotUnbootable(Slot slot) = 0;
virtual bool SetActiveBootSlot(Slot slot) = 0;
virtual Slot GetActiveBootSlot() = 0;
virtual bool MarkBootSuccessfulAsync(
base::Callback<void(bool)> callback) = 0;
virtual bool IsSlotMarkedSuccessful(Slot slot) const = 0;
};
Android 平台上,BootControlAndroid通过 Boot Control HAL(IBootControl)实现该接口。
源码:system/update_engine/aosp/boot_control_android.h
class BootControlAndroid final : public BootControlInterface {
std::unique_ptr<android::hal::BootControlClient> module_;
std::unique_ptr<DynamicPartitionControlAndroid> dynamic_control_;
};
55.5.2 插槽命名约定
AOSP 最多支持 26 个插槽(A‑Z),实际产品仅使用 2 个。
static std::string SlotName(Slot slot) {
if (slot == kInvalidSlot) return "INVALID";
if (slot < 26) return std::string(1, 'A' + slot);
return "TOO_BIG";
}
分区名携带后缀:system_a、system_b、boot_a、boot_b以此类推。
55.5.3 A/B 更新完整生命周期
- OTA 客户端调用
applyPayload(url, headers) - update_engine 获取当前插槽为 A,下载并将载荷写入插槽 B
- 调用
SetActiveBootSlot(B);状态变为UPDATED_NEED_REBOOT,提示用户重启 - 设备重启,从新激活的插槽 B 启动
- Bootloader 维护重试计数器
update_verifier读取 care_map,校验 dm‑verity 块完整性- 校验成功执行
MarkBootSuccessful() GetOTAUpdateResult()得到OTA_SUCCESSFUL- 执行
CleanupPreviousUpdate(Virtual A/B 执行合并)
55.5.4 Bootloader 交互
Bootloader 维护每一个插槽元数据:
表格
| 字段 | 说明 |
|---|---|
| bootable | 该插槽是否可启动 |
| successful | 是否已经校验启动成功 |
| active | 下次开机要启动的插槽 |
| retry_count | 标记为不可启动之前剩余的启动重试次数 |
Bootloader 启动逻辑:
- Bootloader 启动
- 判断 active 插槽是否可启动;不可启动切换另一插槽
retry_count > 0:计数器自减,尝试启动该插槽- 插槽被标记
successful:正常启动 Android - 插槽多次启动失败:标记该插槽不可启动,切换到另一个插槽
- Android 系统启动后运行
update_verifier update_verifier校验通过 →MarkBootSuccessful()
55.5.5 回滚机制
回滚完全自动,不需要用户操作:
- 启动失败:设备完全无法启动新插槽,Bootloader 重试计数器耗尽,自动切回旧插槽
- 校验失败 :
update_verifier读取 care_map 内全部块,依靠 dm‑verity 检测损坏。读取失败则设备重启;多次失败后 Bootloader 将插槽标记为不可启动。 - 显式回滚:update_engine 可以主动将旧插槽重新置为激活,但一般不会暴露给普通用户。
源码:bootable/recovery/update_verifier/update_verifier.cpp
update_verifier依赖 device‑mapper‑verity (dm‑verity) 捕获被校验分区的损坏。如果设备没有开启 dm‑verity,该校验逻辑会被跳过。检测到校验失败时设备会执行重启。
55.5.6 care_map
care_map 是 protobuf 文件,记录每个分区包含有效数据的块范围(区别于空闲未使用空间)。update_verifier只读取 care_map 标记的块,触发 dm‑verity 校验,而不需要读取整个分区。
message CareMap {
message PartitionInfo {
string name = 1;
string ranges = 2; // 块范围,例如 "0‑1000,2000‑3000"
string id = 3; // 哈希/id
string fingerprint = 4; // 构建指纹
}
repeated PartitionInfo partitions = 1;
}
源码:bootable/recovery/update_verifier/care_map.proto
55.6 Virtual A/B 更新
Virtual A/B 是最复杂的更新方案。它在保留 A/B 无缝更新体验的同时,依靠写时复制(COW)快照,存储开销接近传统非 A/B 方案。
55.6.1 架构总览
源码:system/fs/fs_mgr/libsnapshot/、system/update_engine/aosp/dynamic_partition_control_android.h
核心思想:不再保存分区完整副本,Virtual A/B 仅保存正在运行版本(源)和更新后版本(目标)之间的差异。差异以 COW 格式存储;守护进程snapuserd向系统其他组件提供基础分区 + COW 数据的合并视图。
阶段示意:
- 更新过程中 :系统运行
system_a;update_engine把变更块写入 COW 设备(位于 /data 或者 super 分区);dm‑verity 保护源分区。 - 重启后(合并前) :
snapuserd读取基础system_b镜像 + COW 数据,通过 dm‑user 设备输出合并视图给内核,dm‑verity 校验合并后视图。 - 合并完成后 :COW 数据合并写入
system_b,COW 数据被删除,得到完整更新好的system_b分区。
55.6.2 动态分区与 super 分区
Virtual A/B 建立在 Android10 引入的动态分区特性之上。super 是物理分区,内部存放类似 GPT 的元数据LpMetadata,描述内部逻辑分区 system、vendor、product 等。
Virtual A/B 环境下,元数据内同时存在逻辑分区 A/B 条目,但实际数据可以互相重叠;非激活插槽物理实体不会提前存在,直到 COW 快照被创建。
55.6.3 Snapshot 管理器
ISnapshotManager接口(实现类SnapshotManager)统一协调快照创建、合并、清理。
源码:system/fs/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
class ISnapshotManager {
public:
virtual bool BeginUpdate() = 0;
virtual bool CancelUpdate() = 0;
virtual bool FinishedSnapshotWrites(bool wipe) = 0;
// init第一阶段挂载快照分区
virtual bool MapAllSnapshots(const std::chrono::milliseconds& timeout) = 0;
virtual bool UnmapAllSnapshots() = 0;
// 发起全部快照合并
virtual bool InitiateMerge() = 0;
// 处理合并,循环调用直到完成
virtual UpdateState ProcessUpdateState(
const std::function<bool()>& callback,
const std::function<bool()>& before_cancel) = 0;
// 获取整体更新状态
virtual UpdateState GetUpdateState(double* progress = nullptr) = 0;
};
55.6.4 COW 格式
写时复制 COW 格式高效存储变更块。AOSP 经历多轮迭代,v2、v3 都可被读取;Android17 写入器CowWriterV3输出 v3 版本(header_.prefix.major_version = 3)。
源码:system/fs/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp、system/fs/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
v3 格式每条操作携带压缩元数据;一份 COW 镜像可以混合无压缩、lz4、zstd 块,支持--compression_factor选择更大压缩粒度(4k‑256k)。
COW 操作类型:
表格
| 操作 | 说明 |
|---|---|
| COW_COPY | 块没有变更,直接读取源块 |
| COW_REPLACE | 块被替换,完整新数据存放在 COW |
| COW_ZERO | 块全部置 0 |
| COW_XOR | 块改动很小,保存新旧块异或差分 |
| COW_LABEL | 崩溃恢复使用的检查点标记 |
CowOperation结构:
- type: COPY/REPLACE/ZERO/XOR
- source_block: 源块偏移
- new_block: 目标块
- data_offset: 在数据段内偏移
- compression: lz4/zstd/none
COW 文件布局:
- 头部:版本、块大小、操作计数
- 操作表:连续一组 CowOperation 条目
- 数据段:REPLACE 操作对应的压缩数据块
55.6.5 snapuserd
snapuserd是用户态守护进程,负责对外提供快照块设备。它在开机极早期(init 第一阶段)启动,通过用户态块设备向内核输出基础分区 + COW 数据的合并视图。块设备抽象为IBlockServer接口;历史上后端只有 dm‑user;Android17 新增 UBLK 后端,守护进程启动阶段选择使用哪一个。接口头文件snapuserd/include/snapuserd/block_server.h;两个实现分别为dm_user_block_server.cpp、ublk_block_server.cpp。
源码:system/fs/fs_mgr/libsnapshot/snapuserd/、system/fs/fs_mgr/libsnapshot/snapuserd/user‑space‑merge/
运行架构:
- 用户态:snapuserd 守护进程;内部包含 ReadWorker、MergeWorker、ReadAhead 线程
- 内核态:dm‑user 设备
/dev/dm‑N,叠加 dm‑verity - 存储:super 分区内基础
system_b镜像;/data 分区存放 COW 设备;最终对外提供可挂载文件系统
SnapshotHandler管理单个分区快照:
class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
public:
SnapshotHandler(std::string misc_name,
std::string cow_device,
std::string backing_device,
std::string base_path_merge,
std::shared_ptr<IBlockServerOpener> opener,
HandlerOptions options);
bool InitCowDevice();
bool Start();
bool InitializeWorkers();
// ...
};
55.6.6 合并流程
设备启动进入新插槽,update_verifier校验完整性之后,COW 数据必须合并写入基础分区,永久完成更新并释放 COW 存储空间。
合并由CleanupPreviousUpdateAction编排后台执行。
源码:system/update_engine/aosp/cleanup_previous_update_action.h
class CleanupPreviousUpdateAction : public Action<...> {
void PerformAction() override;
// 内部流程:
// 1. ScheduleWaitBootCompleted
// 2. CheckSlotMarkedSuccessfulOrSchedule
// 3. StartMerge -> InitiateMergeAndWait
// 4. WaitForMergeOrSchedule (轮询合并进度)
// 5. ReportMergeStats
};
完整流程:
CleanupPreviousUpdateAction执行;等待BootCompleted事件- 确认插槽已经标记启动成功
- 调用
InitiateMerge()启动合并 worker - ReadAhead 预读 COW 块;MergeWorker 把块写入基础分区
- 更新合并状态;全部完成执行
CommitMerge(num_ops) - 清理快照资源;Action 返回成功
55.6.7 合并状态机
MERGE_READY:COW 已创建,已经重启完毕MERGE_BEGIN:调用 InitiateMerge,worker 启动MERGE_IN_PROGRESS:正在处理块MERGE_COMPLETE:全部块合并完成MERGE_FAILED:IO 错误,可重试,清理资源
snapuserd内部以块组为单位维护细粒度合并状态:
enum class MERGE_GROUP_STATE {
GROUP_MERGE_PENDING,
GROUP_MERGE_RA_READY,
GROUP_MERGE_IN_PROGRESS,
GROUP_MERGE_COMPLETED,
GROUP_MERGE_FAILED,
GROUP_INVALID,
};
55.6.8 压缩与 XOR
Virtual A/B Compression (VABC) 对 COW 数据做压缩,减少空间占用。支持算法:
表格
| 算法 | 属性值 | 特点 |
|---|---|---|
| LZ4 | lz4 | 解压速度快,压缩比中等 |
| Zstandard | zstd | 压缩比更好,速度表现优秀 |
| None | none | 不压缩 |
XOR 压缩(属性ro.virtual_ab.compression.xor.enabled=true)进一步减小 COW 体积:当块改动很小(例如头部时间戳),新旧块做异或,异或结果大部分为 0,压缩效率远高于直接保存完整新块。
- 开启 XOR:旧块 ^ 新块 → XOR 差分(大量零)→ lz4/zstd 压缩保存,占用很小存储空间
- 关闭 XOR:直接保存完整 4096 字节新块
55.6.9 空间分配
更新开始前,update_engine必须确认有足够空间存放 COW 数据。由AllocateSpaceForPayload完成。
uint64_t AllocateSpaceForPayload(
const std::string& metadata_filename,
const std::vector<std::string>& key_value_pair_headers,
Error* error) override;
COW 数据可以存放于:
- super 分区空闲空间:super 分区还有剩余容量
- userdata 分区:/data 分区作为溢出存储
系统读取ro.virtual_ab.compression.enabled,根据 payload manifest 估算 COW 占用大小。如果空间不足,API 返回需要的空间大小,上层框架提示用户释放存储空间。
55.7 载荷生成
OTA 载荷在构建服务器上由 Python 脚本和原生工具生成。
55.7.1 ota_from_target_files
OTA 生成的主入口脚本。
源码:build/make/tools/releasetools/ota_from_target_files.py
用法:
# 全量OTA
ota_from_target_files target‑files.zip ota_package.zip
# 增量OTA
ota_from_target_files ‑i source‑target‑files.zip \
target‑target‑files.zip ota_package.zip
关键选项:
OPTIONS.wipe_user_data = False
OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
OPTIONS.two_step = False
OPTIONS.include_secondary = False
OPTIONS.block_based = True
OPTIONS.disable_vabc = False
OPTIONS.enable_vabc_xor = True
OPTIONS.enable_zucchini = False
OPTIONS.enable_puffdiff = None
OPTIONS.enable_lz4diff = False
OPTIONS.vabc_compression_param = None # lz4, zstd, none
OPTIONS.max_threads = None
OPTIONS.vabc_cow_version = None
OPTIONS.compression_factor = None # 4k‑256k
OPTIONS.enable_replace_zstd = False # Android17:REPLACE操作使用zstd
Android17 新增‑‑enable_replace_zstd,使delta_generator输出REPLACE_ZSTD操作而不是普通 REPLACE,减小全量载荷与增量载荷内全量部分体积。该参数传递到原生生成器‑‑enable_replace_zstd=true;与‑‑disable_replace_compression互斥。
生成阶段读取的关键元数据文件:
META/postinstall_config.txtMETA/dynamic_partitions_info.txtMETA/misc_info.txtMETA/ab_partitions.txt
55.7.2 生成流程
输入:target‑files.zip(全部镜像、元数据、密钥);增量更新额外需要source‑files.zip
- 解析
META/misc_info.txt - 判断是否 A/B 设备
- A/B 设备:生成 A/B 载荷
- 非 A/B 设备:生成传统 OTA 包
- 从 target‑files 解压镜像
- 调用
PayloadGenerator,内部调用delta_generator生成payload.bin - 对 payload 签名
- 生成 properties 属性文件
- 打包为 OTA zip 压缩包,对 zip 整体签名
- 输出
ota_package.zip,内部包含 payload.bin、properties、各类元数据
55.7.3 brillo_update_payload
底层 shell 工具,直接操作载荷文件。
源码:system/update_engine/scripts/brillo_update_payload
常用子命令:
# 生成未签名载荷
brillo_update_payload generate \
--payload output.bin \
--target_image target.img \
[--source_image source.img] # 全量载荷省略source
# 生成用于签名的哈希
brillo_update_payload hash \
--unsigned_payload payload.bin \
--signature_size 256 \
--payload_hash_file payload_hash \
--metadata_hash_file metadata_hash
# 写入签名
brillo_update_payload sign \
--unsigned_payload unsigned.bin \
--payload signed.bin \
--signature_size 256 \
--payload_signature_file payload.sig \
--metadata_signature_file metadata.sig
# 导出properties
brillo_update_payload properties \
--payload signed.bin \
--properties_file props.txt
# 校验载荷
brillo_update_payload verify \
--payload signed.bin \
--target_image target.img \
[--source_image source.img]
55.7.4 delta_generator
执行差分计算的原生二进制程序。
源码:system/update_engine/payload_generator/generate_delta_main.cc、system/update_engine/payload_generator/delta_diff_generator.h
bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config,
const std::string& output_path,
const std::string& private_key_path,
uint64_t* metadata_size);
针对每一个分区,delta_generator执行:
- 读取源镜像与目标镜像
- 识别文件系统类型(ext4、EROFS 等)
- 按文件对块分组,优化差分效果
- 为每一段块范围选择最优差分算法:
- 完全相同块:
SOURCE_COPY - 全零块:
ZERO - 根据内容选择 BSDIFF、PUFFDIFF、ZUCCHINI、LZ4DIFF
- 都不适合时使用带压缩的 REPLACE 作为兜底
- 完全相同块:
- 将操作与数据块序列化为 payload 格式
55.7.5 差分算法选择逻辑
- 判断块范围是否与源完全相同 → SOURCE_COPY
- 判断是否全部为零块 → ZERO
- 是否为 LZ4 压缩
55.7.6 OTA 软件包结构
对于 A/B 设备,输出的 OTA 压缩包包含:
ota_package.zip
payload.bin -- 二进制有效载荷
payload_properties.txt -- 键值形式元数据
META‑INF/
com/android/metadata -- 软件包元数据
com/android/metadata.pb -- Protobuf元数据
care_map.pb -- 用于校验的块保护映射表
payload_properties.txt保存客户端所需参数:
FILE_HASH=<sha256>
FILE_SIZE=<bytes>
METADATA_HASH=<sha256>
METADATA_SIZE=<bytes>
对于非 A/B 设备,压缩包内不包含payload.bin,而是传统的updater‑script与update‑binary。
55.8 流式更新
A/B 模式的一大核心优势是支持流式更新:有效载荷可以边下载边执行升级,无需完整保存全部文件。
55.8.1 下载架构
存储、update_engine、网络、OTA 服务端 HTTPS 交互流程: HTTP GET 带 Range 范围请求 → 字节块经由 HttpFetcher(基于 libcurl)交给 DownloadAction → DeltaPerformer 执行处理 → 块写入目标分区或者 COW 设备。
LibcurlHttpFetcher 负责:
- 通过 TLS 完成 HTTP/HTTPS 下载
- 使用 Range 请求实现断点续传
- 通过 NetworkSelectorInterface 完成网络选型(蜂窝网络 / Wi‑Fi)
55.8.2 流式处理流程
DeltaPerformer 可以增量处理有效载荷:
- 从首个数据块解析 24 字节头部
- 持续接收字节,直到完整清单数据就绪
- 解析清单并完成分区预处理
- 后续每一项操作,等待对应数据块到达后立刻执行
写入采用流水线机制:当前操作的数据正在写入目标设备的同时,下一份操作数据正在后台下载。 设备不需要预留等同于完整有效载荷大小的空闲空间。DeltaPerformer::buffer_缓冲区仅保存当前正在处理的操作数据。
55.8.3 基于文件描述符的更新
除 URL 流式更新外,也可以通过本地文件描述符执行升级:
// UpdateEngine.java
updateEngine.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
该接口用于以下场景:
- ADB 侧载:
adb sideload ota_package.zip - SD 卡本地安装
- OTA 客户端 App 下载到本地存储后的升级
55.8.4 网络相关注意事项
update_engine 具备如下能力:
- 暂停 / 恢复:网络断开时下载暂停;恢复后使用 HTTP Range 头从断点继续下载
- 多网络支持:
NETWORK_ID头部可以指定使用的网络接口 - 按流量计费网络感知:是否在计量网络下下载,由上层 OTA 客户端决定,并非 update_engine 本身处理
55.9 恢复模式
尽管 A/B 与 Virtual A/B 更新流程可以绕开恢复模式,但恢复模式依然是非 A/B 设备的升级载体,同时提供重要的故障回退能力。
55.9.1 恢复模式架构
源码路径:bootable/recovery/recovery_main.cpp、bootable/recovery/recovery.cpp
Recovery 是一套极简 Linux 环境,拥有独立 init 进程、UI 组件以及裁剪后的工具集。
- 非 A/B 设备:Recovery 存放在独立 recovery 分区
- A/B 设备:Recovery 内嵌在 boot 或者 init_boot 分区,开机时解压使用
恢复模式组件流程: Bootloader → boot‑recovery /fastboot/default;Bootloader 读取 BCB;Recovery init 执行recovery_main.cpp main(),调用recovery.cpp start_recovery();启动 RecoveryUI 文本图形界面;install.cpp InstallPackage()负责安装;fastboot.cpp StartFastboot()进入 fastboot。
55.9.2 引导加载程序控制块(BCB)
Recovery 与主系统通过Bootloader Control Block(BCB) 通信,它是 misc 分区内预定义的数据结构。 源码路径:bootable/recovery/bootloader_message/
struct bootloader_message {
char command[32]; // "boot‑recovery", "boot‑fastboot" 等
char status[32]; // 状态字符串(已废弃)
char recovery[768]; // 换行分隔的Recovery启动参数
char stage[32]; // 多阶段更新进度标记
char reserved[1184]; // 保留字段,供后续扩展
};
BCB 工作协议:
- 主系统向 command 字段写入
boot‑recovery,将 recovery 参数写入 recovery 字段 - Bootloader 读取 command,启动进入 Recovery 模式
- Recovery 从 BCB 读取启动参数
- 升级完成后,Recovery 清空 BCB,设备正常开机
55.9.3 Recovery 命令
源码路径:bootable/recovery/recovery.cpp(start_recovery函数)
Recovery 可以读取 BCB 或者/cache/recovery/command中的命令:
表格
| 命令 | 说明 |
|---|---|
--update_package=<path> |
安装 OTA 软件包 |
--install_with_fuse |
大软件包使用 FUSE 机制 |
--wipe_data |
恢复出厂设置 |
--wipe_cache |
清空 cache 分区 |
--prompt_and_wipe_data |
弹出损坏提示,提供重置选项 |
--sideload |
进入 ADB 侧载模式 |
--sideload_auto_reboot |
侧载完成后自动重启 |
--rescue |
进入救援模式 |
--just_exit |
不执行操作,直接重启 |
--shutdown_after |
执行完成后关机而非重启 |
--show_text |
启用文本模式 UI |
55.9.4 Recovery 下 OTA 软件包安装流程
非 A/B 设备由 Recovery 完成 OTA 安装。
执行流程:Recovery 校验 ZIP 签名 → 提取 update‑binary → fork 并执行 update‑binary → 解析 edify 语言编写的 updater‑script → 执行块级补丁更新 → 通过管道上报进度 → 返回执行状态 → 清空 BCB → 设备重启。
install.cpp中InstallPackage()函数负责:
-
使用
/system/etc/security/otacerts.zip校验 ZIP 签名 -
提取并执行
META‑INF/com/google/android/update‑binary -
通过管道监听进度(指令:progress、set_progress、ui_print)
-
IO 错误最多自动重试 4 次
// install.cpp
static constexpr int kRecoveryApiVersion = 3;
static constexpr int VERIFICATION_PROGRESS_TIME = 60;
static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25;// recovery.cpp -- 瞬时错误自动重试次数
static constexpr int RETRY_LIMIT = 4;
55.9.5 ADB 侧载
Recovery 支持通过 ADB 接收 OTA 软件包:
# 主机端执行
adb sideload ota_package.zip
进入侧载模式后,Recovery 启动小型 ADB 守护进程minadbd,通过 USB 接收软件包并交给安装器处理。
55.9.6 Recovery 用户界面
源码路径:bootable/recovery/recovery_ui/
Recovery 提供文本 / 图形交互 UI,支持:
- 使用音量键、电源键操作菜单
- 安装、校验进度条展示
- 多分辨率资源(res‑hdpi、res‑xhdpi 等)
- 多语言文本叠加
菜单选项(Device::GetMenuItems()):
表格
| 菜单项 | 动作常量 |
|---|---|
| Reboot system now | REBOOT |
| Reboot to bootloader | REBOOT_BOOTLOADER |
| Enter fastboot | ENTER_FASTBOOT |
| Apply update from ADB | APPLY_ADB_SIDELOAD |
| Apply update from SD card | APPLY_SDCARD |
| Wipe data/factory reset | WIPE_DATA |
| Wipe cache partition | WIPE_CACHE |
| Mount /system | MOUNT_SYSTEM |
| View recovery logs | VIEW_RECOVERY_LOGS |
| Run graphics test | RUN_GRAPHICS_TEST |
| Power off | SHUTDOWN |
55.9.7 Recovery 对 Virtual A/B 的兼容
Recovery 能够识别 Virtual A/B 快照。挂载 system 分区时会优先创建快照设备:
// recovery.cpp
case Device::MOUNT_SYSTEM:
// For Virtual A/B, set up the snapshot devices (if exist).
if (!CreateSnapshotPartitions()) {
ui->Print("Virtual A/B: snapshot partitions creation failed.\n");
break;
}
if (ensure_path_mounted_at(
android::fs_mgr::GetSystemRoot(), "/mnt/system") != -1) {
ui->Print("Mounted /system.\n");
}
break;
Recovery 也可以取消正在进行的 Virtual A/B 更新(例如用户希望侧载另一个 OTA 包)。
// In ask_to_cancel_ota()
std::vector<std::string> headers{
"Overwrite in‑progress update?",
"An update may already be in progress. If you proceed, "
"the existing OS may not longer boot, and completing "
"an update via ADB will be required."
};
55.10 框架集成:UpdateEngine API
55.10.1 UpdateEngine Java API
源码路径:frameworks/base/core/java/android/os/UpdateEngine.java
UpdateEngine是@SystemApi类,封装访问 update_engine 的 Binder 接口。在 Google 设备上,GmsCore(Google Play 服务)是主要调用客户端。
@SystemApi
public class UpdateEngine {
private static final String UPDATE_ENGINE_SERVICE =
"android.os.UpdateEngineService";
// 使用流程:
// 1. 创建实例
UpdateEngine engine = new UpdateEngine();
// 2. 注册回调
engine.bind(new UpdateEngineCallback() {
@Override
public void onStatusUpdate(int status, float percent) {
// 更新UI
}
@Override
public void onPayloadApplicationComplete(int errorCode) {
// 处理升级完成事件
}
});
// 3. 执行升级
engine.applyPayload(url, offset, size, headerKeyValuePairs);
}
55.10.2 错误码
源码路径:frameworks/base/core/java/android/os/UpdateEngine.java
ErrorCodeConstants暴露 update_engine 错误码:
表格
| 常量 | 值 | 含义 |
|---|---|---|
| SUCCESS | 0 | 升级执行成功 |
| ERROR | 1 | 通用未知错误 |
| FILESYSTEM_COPIER_ERROR | 4 | 文件系统复制失败 |
| POST_INSTALL_RUNNER_ERROR | 5 | 后置安装脚本执行失败 |
| PAYLOAD_MISMATCHED_TYPE_ERROR | 6 | 有效载荷版本不兼容 |
| INSTALL_DEVICE_OPEN_ERROR | 7 | 无法打开目标设备 |
| KERNEL_DEVICE_OPEN_ERROR | 8 | 无法打开内核设备 |
| DOWNLOAD_TRANSFER_ERROR | 9 | 网络下载失败 |
| PAYLOAD_HASH_MISMATCH_ERROR | 10 | 有效载荷哈希校验不匹配 |
| PAYLOAD_SIZE_MISMATCH_ERROR | 11 | 有效载荷大小不匹配 |
| DOWNLOAD_PAYLOAD_VERIFICATION_ERROR | 12 | 签名校验失败 |
| PAYLOAD_TIMESTAMP_ERROR | 51 | 回滚保护时间戳校验失败 |
| UPDATED_BUT_NOT_ACTIVE | 52 | 写入完成,但未切换启动槽位 |
55.10.3 更新状态码
public static final class UpdateStatusConstants {
public static final int IDLE = 0;
public static final int CHECKING_FOR_UPDATE = 1;
public static final int UPDATE_AVAILABLE = 2;
public static final int DOWNLOADING = 3;
public static final int VERIFYING = 4;
public static final int FINALIZING = 5;
public static final int UPDATED_NEED_REBOOT = 6;
public static final int REPORTING_ERROR_EVENT = 7;
public static final int ATTEMPTING_ROLLBACK = 8;
public static final int DISABLED = 9;
}
Java 层UpdateStatusConstants仅定义到DISABLED = 9。原生层枚举UpdateStatus还包含另外两个状态,没有映射到 Java 常量:
NEED_PERMISSION_TO_UPDATE = 10CLEANUP_PREVIOUS_UPDATE = 11(Virtual A/B 升级重启之后的快照合并阶段)
55.10.4 UpdateEngineStable
为了让 OEM 升级程序能够跨 Android 版本兼容,AOSP 提供UpdateEngineStable。 源码路径:frameworks/base/core/java/android/os/UpdateEngineStable.java
它绑定一套稳定 AIDL 接口而非版本化接口,保证核心applyPayload / bind / cancel接口向前、向后兼容。
55.10.5 Updater 示例应用
AOSP 内置一份 OTA 客户端示例代码。 源码路径:bootable/recovery/updater_sample/
示例演示完整 UpdateEngine API 使用流程:
- 解析 OTA 服务端响应
- 携带请求头调用
applyPayload - 展示下载与校验进度
- 处理升级完成,发起重启请求
55.10.6 端到端升级流程
角色组件:OTA 客户端 App (GmsCore)、UpdateEngine (Java API)、update_engine (Native 守护进程)、Boot Control HAL、SnapshotManager、update_verifier、snapuserd、Bootloader、OTA 服务端。
阶段 1:检查更新 客户端请求可用 OTA,获取元数据(URL、大小、哈希等)。
阶段 2:执行升级 新建 UpdateEngine 并注册回调,调用applyPayload;通过 Binder 下发请求;获取当前槽位;Virtual A/B 执行BeginUpdate()、CreateUpdateSnapshots();构建动作流水线;DownloadAction 流式接收 payload;DeltaPerformer 执行操作;通过onStatusUpdate回调上报进度;FilesystemVerifierAction 校验哈希;PostinstallRunnerAction 执行脚本;设置新的活动 Boot 槽;完成快照写入;回调onPayloadApplicationComplete(SUCCESS);通知用户,等待重启。
阶段 3:重启与校验 Bootloader 切换到 B 槽;Virtual A/B 加载快照;dm‑verity 完成校验;调用MarkBootSuccessful()。
阶段 4:升级后合并 执行CleanupPreviousUpdateAction,发起快照合并,将 COW 数据合并到基础分区。
55.11 Postinstall(升级后置执行)
55.11.1 什么是 Postinstall
分区全部写入并校验完成后,update_engine 可以在新写入的目标分区运行 postinstall 脚本。主要用途:
- 新系统版本下系统 App 做 dex 优化(dex2oat)
- 文件系统 SELinux 重新打标签
- OEM 自定义后置处理步骤
55.11.2 Postinstall 配置
postinstall 配置保存在 OTA 有效载荷清单内。
message PartitionUpdate {
bool run_postinstall = 13;
string postinstall_path = 14; // e.g., "bin/postinstall"
string filesystem_type = 15; // e.g., "ext4"
bool postinstall_optional = 16; // 执行失败是否允许跳过
}
55.11.3 PostinstallRunnerAction
执行逻辑:
- PostinstallRunnerAction 启动
- 遍历所有标记
run_postinstall的分区 - 以只读方式挂载目标分区
- fork 并执行
postinstall_path脚本 - 判断退出码是否为 0
- 成功:卸载分区,处理下一个分区
- 失败:判断
postinstall_optional- true:输出警告,继续后续流程
- false:整个 OTA 升级失败
- 全部处理完成结束
脚本运行在受限环境:
- 目标分区挂载到临时路径
- 进程继承 update_engine 的 UID/GID
- SELinux 上下文为 update_engine
- 通过管道向外上报执行进度
55.11.4 单独触发 Postinstall
Binder 接口支持针对指定分区单独触发 postinstall,不需要完整 OTA 升级。
// binder_service_android.h
android::binder::Status triggerPostinstall(
const android::String16& partition) override;
该接口适合更新 APEX 包后需要执行后置处理的场景。
55.12 回滚防护
55.12.1 基于时间戳的防护
OTA 有效载荷清单携带max_timestamp字段。DeltaPerformer 会将它与设备当前构建时间戳做比对。
ErrorCode DeltaPerformer::CheckTimestampError() const {
// If the new build's timestamp is older than current,
// return kPayloadTimestampError unless explicitly allowed.
}
该机制防止设备降级到存在安全漏洞的旧版本系统。
55.12.2 安全补丁级别(SPL)校验
OTA 安装过程校验 SPL。 源码路径:bootable/recovery/install/include/install/spl_check.h
如果目标构建 SPL 比源版本旧,OTA 会被拒绝;只有生成 OTA 时添加--spl_downgrade标志才允许降级 SPL。
55.12.3 与验证启动 (Verified Boot) 集成
A/B 与 Virtual A/B 设备行为:
- 每个 slot 拥有独立 vbmeta 分区,保存 Android Verified Boot 元数据
- Bootloader 启动槽位之前校验信任链
- 运行时 dm‑verity 保护分区完整性
- 首次开机 update_verifier 对 care_map 标记块做完整 dm‑verity 扫描
流程:Bootloader 校验 vbmeta_b 签名、校验 vbmeta 内 boot_b 哈希;从 boot_b 启动内核;init 为 system_b、vendor_b 等挂载 dm‑verity;update_verifier 读取 care_map 块;通过 dm‑verity 读取所有块;读取全部成功则调用 MarkBootSuccessful;读取失败则重启,Bootloader 耗尽重试次数后自动回滚。
55.13 指标统计与日志
55.13.1 更新指标
update_engine 收集每一次升级尝试的详细统计指标。 源码路径:system/update_engine/aosp/update_attempter_android.h
跟踪指标包括:
kPrefsPayloadAttemptNumber:当前有效载荷重试次数kPrefsNumReboots:升级过程重启次数kPrefsCurrentBytesDownloaded:已下载字节数kPrefsTotalBytesDownloaded:全部尝试累计下载字节kPrefsUpdateTimestampStart:升级开始时间戳kPrefsUpdateBootTimestampStart:开机版本的升级开始时间戳
升级成功或失败后,通过MetricsReporterInterface上报指标。
55.13.2 合并统计
Virtual A/B 环境,合并性能记录在 protobuf 结构体SnapshotMergeReport。合并结束,CleanupPreviousUpdateAction::ReportMergeStats调用SnapshotManager::ReadMergeReport(),通过SNAPSHOT_MERGE_REPORTED上报给 statsd。
源码路径: system/update_engine/aosp/cleanup_previous_update_action.cc (ReportMergeStats) system/fs/fs_mgr/libsnapshot/android/snapshot/snapshot.proto (SnapshotMergeReport)
上报字段:
merge_total_time_ms:合并总耗时resume_count:合并被中断恢复的次数cow_file_size, total_cow_size_bytes, estimated_cow_size_bytes:COW 存储实际与预估占用merge_failure_code:合并失败错误码compression_enabled, xor_compression_used, iouring_used:COW 启用的特性ublk_used:Android17 新增,标记快照是否使用 UBLK 后端而不是 dm‑user
Android17 移除旧的ISnapshotMergeStats / snapshot_stats.h统计累加器;全部统计从持久化SnapshotMergeReport读取。
55.13.3 日志位置
表格
| 日志 | 查看方式 | 时机 |
|---|---|---|
| update_engine 守护进程 | `logcat -b all | grep update_engine` |
| update_engine 日志文件 | /data/misc/update_engine_log/ |
持久保存 |
| Recovery 日志 | /cache/recovery/last_log |
Recovery 模式执行完毕后 |
| Recovery 内核消息 | /cache/recovery/last_kmsg |
Recovery 模式执行完毕后 |
| update_verifier | `logcat -b all | grep update_verifier` |
| snapuserd | `logcat -b all | grep snapuserd` |
55.14 OTA 故障排查
55.14.1 常见故障模式
表格
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| DOWNLOAD_TRANSFER_ERROR | 网络问题 | 检查网络连接,重试 |
| PAYLOAD_HASH_MISMATCH_ERROR | 下载文件损坏 | 重新下载 payload |
| PAYLOAD_TIMESTAMP_ERROR | 回滚保护拦截 | 目标版本比当前版本旧 |
| FILESYSTEM_COPIER_ERROR | 存储 IO 错误 | 检查存储硬件健康状态 |
| POST_INSTALL_RUNNER_ERROR | postinstall 脚本执行失败 | 查看 postinstall 相关日志 |
| Merge stalls 合并卡死 | IO 资源争抢 | 查看 snapuserd 日志,观察存储负载 |
| OTA 之后开机循环 | 新版本存在致命 bug | 重试耗尽 Bootloader 自动回滚 |
| VABC 空间不足 | 没有足够空间存放 COW | 释放 /data 空间,检查 super 分区剩余空间 |
55.14.2 调试 update_engine
# 开启详细日志
adb shell setprop persist.update_engine.log_level DEBUG
# 强制输出日志dump
adb shell kill -SIGUSR1 $(adb shell pidof update_engine)
# 查看持久化配置项
adb shell ls /data/misc/update_engine/prefs/
55.14.3 调试 snapuserd
# 查看snapuserd进程是否运行
adb shell ps -A | grep snapuserd
# 查看dm‑user设备
adb shell ls -la /dev/dm‑*
adb shell cat /sys/block/dm‑*/dm/name
# 查看快照元数据状态
adb shell snapshotctl dump
55.14.4 Virtual A/B 升级失败恢复
升级还未重启阶段:
# 取消升级
adb shell update_engine_client --cancel
# 或者重置状态
adb shell update_engine_client --reset_status
升级后设备陷入开机循环: Bootloader 在重试耗尽后会自动回滚。 如果卡住,进入 Recovery,执行 Wipe data,或者侧载一份可用 OTA 包。
55.15 内部深度剖析:完整数据流
为加深理解,追踪一条 REPLACE 操作从网络字节到磁盘块的完整调用栈。
55.15.1 单次 REPLACE 操作
示例:delta OTA 中,system 分区某 4KB 块需要完整替换新内容。
- 构建服务器阶段:delta_generator 对比源、目标镜像;生成 InstallOperation:type=REPLACE dst_extents=block42 data_offset=X data_length=4096;4096 字节写入 payload 数据段。
- 设备下载阶段:HTTP 字节流交给 LibcurlHttpFetcher → DownloadAction::ReceivedBytes → DeltaPerformer::Write → 在 buffer_缓存;判断是否收到完整当前操作数据。
- 设备解析执行阶段:执行 PerformReplaceOperation;判断是否 Virtual A/B
- 普通 A/B:PartitionWriter 调用 pwrite 写入
/dev/block/...system_b - Virtual A/B:VABCPartitionWriter 通过 ICowWriter 写入 COW_REPLACE 操作到 /data 下 COW 设备
- 普通 A/B:PartitionWriter 调用 pwrite 写入
- 重启之后 (Virtual A/B):snapuserd ReadWorker 处理 IO;dm‑user 对外提供合并后的 block42;dm‑verity 完成校验;文件系统读取更新后的块。
- 合并完成 (Virtual A/B):MergeWorker 读取 COW,写入底层 system_b;block42 永久固化到 system_b 分区。
55.15.2 SOURCE_COPY 操作数据流
SOURCE_COPY 不需要携带额外数据 blob:
- Virtual A/B:转换为 COW_COPY,记录源块引用;读取时 snapuserd 直接从源分区读取数据,不存储副本,效率最高。
- A/B:读取源 slot 块,直接写入目标 slot。
55.15.3 XOR 操作数据流
开启 XOR 压缩,小量变更生成体积更小的 COW 条目:
- 读取源分区旧块
- 和 payload 中新块做异或运算,得到 XOR 差分数据,大部分为 0
- 使用 lz4/zstd 压缩,保存为 COW_XOR 写入 COW 设备
- 读取的时候:解压 XOR 差分,读取源块,异或运算得到最终新块。
55.16 高级主题
55.16.1 部分更新
payload minor 版本 7 开始支持 partial 更新,只升级部分分区。使用--partial参数。
ota_from_target_files.py --partial "boot vendor" \
-i source.zip target.zip partial_ota.zip
适用场景:
- 仅更新内核安全补丁,不改动 system
- 独立升级 vendor 分区
- 针对特定组件快速 OTA 迭代
InstallPlan内untouched_dynamic_partitions记录保持不修改的分区列表。
55.16.2 多 Payload 更新
InstallPlan结构体的payloads向量支持顺序执行多个 payload。
struct InstallPlan {
std::vector<Payload> payloads;
// First payload might update system/vendor
// Second payload might update a secondary slot image
};
配合--include_secondary参数使用,分阶段完成主、次 slot 镜像更新。
55.16.3 通过 OTA 更新 APEX
现代 Android 部分系统组件以 APEX 包分发。OTA 系统与 APEX 处理逻辑集成。 源码路径:system/update_engine/aosp/apex_handler_android.h
postinstall 阶段,新版本中的 APEX 包执行解压、激活;ApexHandlerInterface负责该部分集成逻辑。
55.16.4 动态分区扩容
Virtual A/B 支持升级过程中调整动态分区大小。如果目标版本 system 分区更大:
-
从 manifest 的
dynamic_partition_metadata读取目标分区布局 -
更新 super 分区内逻辑分区元数据
-
创建适配新布局大小的 COW 快照
// dynamic_partition_control_android.h
bool PreparePartitionsForUpdate(uint32_t source_slot,
uint32_t target_slot,
const DeltaArchiveManifest& manifest,
bool update,
uint64_t* required_size,
ErrorCode* error);
55.16.5 非 A/B OTA 内部实现
非 A/B 走完全独立代码路径。 源码路径:build/make/tools/releasetools/non_ab_ota.py
非 A/B OTA 使用 edify 脚本语言编写 updater‑script。
# Example updater‑script fragment
assert(getprop("ro.product.device") == "walleye");
show_progress(0.750000, 0);
block_image_update("/dev/block/.../system",
package_extract_file("system.transfer.list"),
"system.new.dat.br",
"system.patch.dat");
update‑binary(新版本设备一般为update_engine_sideload)解析脚本,执行块级补丁。
55.16.6 两步式更新
--two_step标志生成两步 OTA:先升级 recovery 分区,重启进入新 recovery,再升级 system、vendor 等其他分区。保证脚本需要的新特性可用。
- 阶段 1:更新 recovery 分区
- 重启进入新版 recovery
- 阶段 2:更新 system、vendor 等
- 重启进入升级完成系统
55.16.7 Brick OTA
一类特殊 OTA,用于主动让设备无法启动(运营商回收、设备集群管理场景)。 源码路径:build/make/tools/releasetools/create_brick_ota.py
该类 OTA 权限严格受控,需要特定签名密钥。
55.17 安全考量
55.17.1 有效载荷签名
所有量产 OTA payload 必须使用设备 OTA 密钥签名。签名链路:
- 构建系统使用 release 密钥对 payload 签名
- 设备在
otacerts.zip存放对应证书 - update_engine 或者 Recovery 在执行升级前校验签名
开发版本使用测试密钥,路径build/make/target/product/security/。
55.17.2 元数据签名
元数据(头部 + 清单)与完整 payload 分开签名。update_engine 可以在处理任何操作之前完成清单签名校验,防范清单解析漏洞攻击。
55.17.3 传输层安全
update_engine 基于 libcurl 使用 HTTPS 下载,提供传输加密、服务端身份认证。payload 签名实现端到端完整性校验,不依赖传输层安全。
55.17.4 SELinux 上下文
update_engine 运行在update_engineSELinux 域,权限集合:
- 读源分区权限
- 写入目标(非活跃 slot)分区权限
- 访问 Boot Control HAL
- 读写持久化目录
/data/misc/update_engine/ - 无用户数据、App 数据以及大部分系统服务访问权限
55.17.5 Verity 与 COW 交互
Virtual A/B 场景 dm‑verity 需要和快照层协同工作。
dm‑user (snapuserd) --> dm‑verity --> mounted filesystem
verity 哈希树与 FEC 前向纠错数据属于目标分区镜像,保存在 COW 中。snapuserd 同时提供元数据块与内容块,dm‑verity 对合并后视图做透明校验。
55.18 update_engine 服务配置
55.18.1 Init 服务定义
Android 中 update_engine 由 init 启动,是常驻服务。配置源自 ChromeOS Upstart 风格配置文件。 源码路径:system/update_engine/init/update‑engine.conf
description "System software update service"
start on starting system‑services
stop on stopping system‑services
respawn
respawn limit 10 20 # Max 10 restarts in 20 seconds
# Runs at low/idle IO priority to avoid impacting system responsiveness
exec ionice -c3 update_engine
在 Android 环境转换为 init rc 文件:system/update_engine/update_engine.rc
service update_engine /system/bin/update_engine --logtostderr --logtofile --foreground
class late_start
user root
group root system wakelock inet cache media_rw
task_profiles OtaProfiles
disabled
on property:ro.boot.slot_suffix=*
enable update_engine
关键服务特性:
- 以 root 运行,需要直接访问块设备
- 归属 system、wakelock、inet、cache、media_rw 用户组
- 默认 disabled;
ro.boot.slot_suffix属性出现才启用,不在非 A/B 设备运行 - 日志同时输出 stderr 和日志文件(
--logtostderr --logtofile) - 应用 OtaProfiles 任务配置,升级过程限制 CPU、IO 占用,避免抢占前台 UI
55.18.2 持久化配置
update_engine 状态保存在持久化目录:/data/misc/update_engine/prefs/
表格
| 配置文件 | 用途 |
|---|---|
| update‑state‑initialized | 标记状态是否初始化完成 |
| update‑state‑next‑operation | 断点续传:操作索引 |
| update‑state‑next‑data‑offset | 断点续传:数据偏移 |
| update‑state‑next‑data‑length | 预期数据长度 |
| update‑state‑payload‑index | 多 payload 场景当前序号 |
| update‑state‑manifest‑metadata‑size | 缓存清单元数据大小 |
| update‑state‑manifest‑signature‑size | 缓存清单签名大小 |
| update‑completed‑on‑boot‑id | 升级完成时 boot id |
| previous‑version | 升级前 build 指纹 |
| boot‑id | 当前 boot id,跟踪重启次数 |
| payload‑attempt‑number | 当前 payload 重试次数 |
| total‑bytes‑downloaded | 累计下载字节数 |
| dynamic‑partition‑metadata‑updated | 标记元数据是否已更新 |
55.18.3 CPU 节流
为避免升级造成设备过热、耗电过快,update_engine 实现 CPU 限流。 源码路径: system/update_engine/common/cpu_limiter.h system/update_engine/common/cpu_limiter.cc
CPULimiter监控系统负载;CPU 占用高时限制升级处理速度。对于 bsdiff、puffdiff、zucchini 这类差分运算密集阶段尤为重要。
55.19 错误码参考
55.19.1 完整原生错误码枚举
完整枚举定义:system/update_engine/common/error_code.h
enum class ErrorCode : int {
kSuccess = 0,
kError = 1,
kOmahaRequestError = 2,
kOmahaResponseHandlerError = 3,
kFilesystemCopierError = 4,
kPostinstallRunnerError = 5,
kPayloadMismatchedType = 6,
kInstallDeviceOpenError = 7,
kKernelDeviceOpenError = 8,
kDownloadTransferError = 9,
kPayloadHashMismatchError = 10,
kPayloadSizeMismatchError = 11,
kDownloadPayloadVerificationError = 12,
kDownloadNewPartitionInfoError = 13,
kDownloadWriteError = 14,
kNewRootfsVerificationError = 15,
kNewKernelVerificationError = 16,
kSignedDeltaPayloadExpectedError = 17,
kDownloadPayloadPubKeyVerificationError = 18,
kDownloadStateInitializationError = 20,
kDownloadInvalidMetadataMagicString = 21,
kDownloadSignatureMissingInManifest = 22,
kDownloadManifestParseError = 23,
kDownloadMetadataSignatureError = 24,
kDownloadMetadataSignatureVerificationError = 25,
kDownloadMetadataSignatureMismatch = 26,
kDownloadOperationHashVerificationError = 27,
kDownloadOperationExecutionError = 28,
kDownloadOperationHashMismatch = 29,
kDownloadInvalidMetadataSize = 32,
kDownloadInvalidMetadataSignature = 33,
kUnsupportedMajorPayloadVersion = 44,
kUnsupportedMinorPayloadVersion = 45,
kFilesystemVerifierError = 47,
kUserCanceled = 48,
kPayloadTimestampError = 51,
kUpdatedButNotActive = 52,
kNoUpdate = 53,
kRollbackNotPossible = 54,
kVerityCalculationError = 56,
kNotEnoughSpace = 60,
kDeviceCorrupted = 61,
kPostInstallMountError = 63,
kUpdateProcessing = 65,
kUpdateAlreadyInstalled = 66,
};
55.19.2 错误码按故障阶段分类
表格
| 阶段 | 错误码 | 说明 |
|---|---|---|
| Download 下载 | 9, 14, 57, 58 | 网络、写磁盘、curl 相关错误 |
| Metadata 元数据校验 | 21‑26, 32‑33, 44‑45 | 头部、清单解析校验失败 |
| Operations 执行操作 | 27‑29 | 单条操作哈希不匹配 |
| Verification 校验 | 10‑12, 15‑16, 47 | payload、分区哈希校验失败 |
| Device 设备访问 | 7, 8, 60, 61 | 存储设备打开、空间、介质损坏 |
| Policy 策略限制 | 48, 51, 52, 65, 66 | 用户取消、时间戳回滚、状态冲突 |
| Postinstall 后置脚本 | 5, 63 | 脚本执行、挂载分区失败 |
55.20 DownloadAction 详解
55.20.1 DownloadAction 初始化
DownloadAction 是流水线中最复杂的 Action,协调 HttpFetcher、DeltaPerformer、断点续传逻辑。 源码路径:system/update_engine/download_action.cc
void DownloadAction::PerformAction() {
http_fetcher_->set_delegate(this);
install_plan_ = GetInputObject(); // From InstallPlanAction
install_plan_.Dump(); // Log the plan
// Calculate total bytes across all payloads
bytes_total_ = 0;
for (const auto& payload : install_plan_.payloads)
bytes_total_ += payload.size;
// Handle resume: skip already‑applied payloads
if (install_plan_.is_resume) {
int64_t payload_index = 0;
if (prefs_->GetInt64(kPrefsUpdateStatePayloadIndex, &payload_index)) {
resume_payload_index_ = payload_index;
for (int i = 0; i < payload_index; i++)
install_plan_.payloads[i].already_applied = true;
}
}
// Mark target slot as unbootable during write
LOG(INFO) << "Marking new slot as unbootable";
boot_control_->MarkSlotUnbootable(install_plan_.target_slot);
StartDownloading();
}
关键设计点:
- 任何写入开始前,标记目标 slot 为不可启动,避免 Bootloader 启动半写损坏镜像
- MultiRangeHttpFetcher 封装原始 HttpFetcher,提供 Range 断点续传支持
55.20.2 进度上报
进度上报做节流,防止 Binder 回调风暴。
// update_attempter_android.cc
const double kBroadcastThresholdProgress = 0.01; // 1%
const int kBroadcastThresholdSeconds = 10;
UpdateAttempterAndroid::BytesReceived计算综合进度,由下载进度和操作执行进度加权组合。
// DeltaPerformer weights (from delta_performer.h)
static const unsigned kProgressDownloadWeight; // Download contribution
static const unsigned kProgressOperationsWeight; // Apply contribution
// These add up to 100
55.20.3 MultiRangeHttpFetcher
多 payload 更新场景,MultiRangeHttpFetcher 负责:
- 顺序下载多个 payload
- 对每个 payload 使用 Byte‑Range 请求,支持 payload 边界处断点续传
- 将收到字节分发给对应 DeltaPerformer 实例
55.21 文件系统校验
55.21.1 FilesystemVerifierAction
全部操作执行完毕后,FilesystemVerifierAction回读目标分区并计算哈希。
流程:
- FilesystemVerifierAction 启动
- 遍历 InstallPlan 中的每一个分区
- 打开目标分区块设备
- 顺序读取全部块,计算 SHA‑256 哈希
- 对比哈希是否与 InstallPlan 期望值匹配
- 不匹配:返回
kFilesystemVerifierError升级失败 - 匹配:处理下一个分区
- 不匹配:返回
- 全部分区校验通过,结束
该步骤捕获以下问题:
- 存储介质比特位损坏
- DeltaPerformer 内部逻辑 bug
- 断电导致的不完全写入(检查点之前)
55.21.2 Verity 哈希树生成
开启 dm‑verity 的分区,升级执行阶段同时生成 verity 哈希树与 FEC 前向纠错数据。
message PartitionUpdate {
uint64 hash_tree_data_offset = 19;
uint64 hash_tree_data_size = 20;
uint64 hash_tree_offset = 21;
uint64 hash_tree_size = 22;
string hash_tree_algorithm = 23; // "sha256"
bytes hash_tree_salt = 24;
uint64 fec_data_offset = 25;
uint64 fec_data_size = 26;
uint64 fec_offset = 27;
uint64 fec_size = 28;
uint32 fec_roots = 29; // Typically 2
}
InstallPlan 中write_verity为 true 时,在设备本地计算哈希树与 FEC 编码,而不是预先打包进 payload,大幅缩减 payload 体积。
55.22 InstallPlan 数据结构
InstallPlan 是贯穿整个 Action 流水线的核心数据结构,保存执行升级全部所需信息。 源码路径:system/update_engine/payload_consumer/install_plan.h
55.22.1 顶层字段
struct InstallPlan {
bool is_resume{false}; // 恢复上一次升级尝试
bool vabc_none{false}; // 关闭VABC
bool disable_vabc{false}; // 另一条关闭VABC路径
std::string download_url; // 下载URL
std::vector<Payload> payloads; // 一个或多个payload
Slot source_slot{kInvalidSlot}; // 当前运行slot
Slot target_slot{kInvalidSlot}; // 目标写入slot
std::vector<Partition> partitions; // 每个分区信息
bool hash_checks_mandatory{false}; // 强制哈希校验
bool powerwash_required{false}; // 重启后清除用户数据
bool spl_downgrade{false}; // 允许SPL降级OTA
bool switch_slot_on_reboot{true}; // 重启切换活动slot
bool run_post_install{true}; // 执行postinstall脚本
bool write_verity{true}; // 生成verity数据
std::vector<std::string> untouched_dynamic_partitions;
bool batched_writes = false; // COW批量写入
std::optional<bool> enable_threading; // 多线程压缩开关
};
55.22.2 单分区信息
每一条 Partition 记录源分区、目标分区元数据:
struct Partition {
std::string name; // e.g., "system"
std::string source_path; // e.g., "/dev/block/by‑name/system_a"
uint64_t source_size{0};
brillo::Blob source_hash; // SHA‑256 of source
std::string target_path; // e.g., "/dev/block/by‑name/system_b"
std::string readonly_target_path; // For mounting post‑install
uint64_t target_size{0};
brillo::Blob target_hash; // Expected SHA‑256 of target
uint32_t block_size{0}; // Usually 4096
bool run_postinstall{false};
std::string postinstall_path; // Script path within partition
std::string filesystem_type; // "ext4", "erofs"
bool postinstall_optional{false};
// Verity configuration
uint64_t hash_tree_data_offset{0};
uint64_t hash_tree_data_size{0};
uint64_t hash_tree_offset{0};
uint64_t hash_tree_size{0};
std::string hash_tree_algorithm;
brillo::Blob hash_tree_salt;
uint64_t fec_data_offset{0};
uint64_t fec_data_size{0};
uint64_t fec_offset{0};
uint64_t fec_size{0};
uint32_t fec_roots{0};
};
55.22.3 Payload 元数据
Plan 中每个 payload 保存 URL、大小、哈希信息:
struct Payload {
std::vector<std::string> payload_urls;
uint64_t size = 0;
uint64_t metadata_size = 0;
std::string metadata_signature; // Base64
brillo::Blob hash; // SHA‑256
InstallPayloadType type{kUnknown}; // kFull or kDelta
std::string fp; // Fingerprint
std::string app_id; // Application ID
bool already_applied = false; // For resume
};
55.23 PartitionWriter 工厂
工厂函数根据设备能力选择对应的 Writer 实现。 源码路径:system/update_engine/payload_consumer/partition_writer.h
namespace partition_writer {
std::unique_ptr<PartitionWriterInterface> CreatePartitionWriter(
const PartitionUpdate& partition_update,
const InstallPlan::Partition& install_part,
DynamicPartitionControlInterface* dynamic_control,
size_t block_size,
bool is_interactive,
bool is_dynamic_partition);
}
选择逻辑:
- Virtual A/B 压缩开启 + 动态分区 →
VABCPartitionWriter,通过 COW 完成写入 - 其他情况 →
PartitionWriter,直接 pwrite 写块设备
VABCPartitionWriter使用ICowWriter(来自 libsnapshot)输出 COW 操作;普通PartitionWriter直接打开目标块设备调用 pwrite。
55.23.1 PartitionWriter IO 路径
标准 A/B(非 VABC): DeltaPerformer → PartitionWriter → ExtentWriter → FileDescriptor → pwrite() → /dev/block/by‑name/system_b
55.23.2 VABCPartitionWriter IO 路径
开启 Virtual A/B Compression: DeltaPerformer → VABCPartitionWriter → ICowWriter → CowWriterV3 → COW file on /data
ICowWriter序列化操作为 COW 二进制格式;开机阶段由 snapuserd 读取 COW 文件。
55.23.3 XOR Map 处理
开启 XOR 压缩,VABCPartitionWriter维护ExtentMap,记录执行 XOR 合并的目标块。
ExtentMap<const CowMergeOperation*, ExtentLess> xor_map_;
XOR map 中的块,源拷贝操作生成COW_XOR条目,保存新旧块异或得到的差分数据,获得更好压缩率。
55.24 update_verifier
55.24.1 用途与执行时机
update_verifier是 OTA 升级完成后第一次开机运行的一次性服务。由 init 在系统完全就绪前触发。 源码路径:bootable/recovery/update_verifier/update_verifier.cpp
// update_verifier verifies the integrity of the partitions after an
// A/B OTA update. It gets invoked by init, and will only perform the
// verification if it's the first boot post an A/B OTA update.
55.24.2 校验流程
- update_verifier 启动
- 读取
/data/ota_package/下care_map.pb - 遍历
/sys/block/dm‑*查找 dm‑verity 块设备 - 匹配分区名称与 dm 设备
- 遍历 care_map 记录的块区间
- 通过 dm‑verity 设备读取每一个块区间
- 判断全部读取是否成功
- 全部成功:调用 Boot Control HAL 的
MarkBootSuccessful - 读取失败:设备重启,Bootloader 减少重试计数
- 全部成功:调用 Boot Control HAL 的
care_map 只包含真正存有文件系统数据的块,不包含空闲块,因此校验速度远大于读取整个分区。
55.24.3 dm‑verity 集成
update_verifier 本身不计算哈希;依赖内核 dm‑verity 在读块的时候实时校验。
- Enforcing 模式:dm‑verity 检测损坏直接重启设备
- EIO 模式:dm‑verity 返回 IO 错误,update_verifier 触发重启
- 其他模式不支持,update_verifier 直接重启
该设计让校验强度等同于设备 Verified Boot 信任链,不需要额外信任 verifier 二进制本身。
55.25 Sideload 模式:update_engine_sideload
55.25.1 Recovery 下 OTA 执行
A/B 设备 Recovery 模式执行 ADB 侧载,使用专门编译版本update_engine_sideload。 源码路径:system/update_engine/aosp/sideload_main.cc
该精简版本特性:
- 不需要完整 Android 系统运行
- 不依赖 Binder,没有框架服务
- 直接从 ADB 连接或者文件读取 payload
- 只负责执行升级操作,不做网络下载
55.25.2 Sideload 流程
主机 adb → USB 传输 OTA 包 → recovery 中 minadbd 接收 → 把文件交给 update_engine_sideload → 提取 payload.bin → 对目标 slot 执行写入操作 → 返回成功 / 失败状态 → Recovery 展示结果。
55.26 Android 17 OTA 变更
Android17 没有新增第四套更新方案;主要在 Virtual A/B 上做三处增强:全新 UBLK 用户态块设备后端用于快照服务;REPLACE 操作支持 zstd 压缩;生成、执行阶段大量删除旧逻辑与内存优化。
55.26.1 UBLK 快照后端
Android16 及更早版本,snapuserd 只使用 dm‑user 实现快照块设备:内核 IO 请求通过 dm‑user 字符设备转发到用户态 snapuserd 进程,合并 base+COW 数据后应答。
Android17 引入 UBLK(userspace‑block‑driver)后端:snapuserd 注册/dev/ublkb*块设备,通过 libublksrv 库与内核 ublk 驱动交互。
两套后端实现统一继承IBlockServer抽象接口;合并逻辑、COW 读取、工作线程代码不变,只改变内核与守护进程之间的通信传输层。
源码路径: system/fs/fs_mgr/libsnapshot/snapuserd/include/snapuserd/block_server.h system/fs/fs_mgr/libsnapshot/snapuserd/dm_user_block_server.cpp system/fs/fs_mgr/libsnapshot/snapuserd/ublk_block_server.cpp
后端选择逻辑:
-
snapuserd 在第一阶段 init 启动
-
判断是否显式传入
‑ublk/‑noublk参数,优先遵循参数 -
读取提示文件
/metadata/ota/snapuserd_mode -
自动检测
IsUblkEnabled():同时满足属性开关、aconfig 标志、内核版本≥6.6,启用 UBLK,否则回退 dm‑user// capabilities.cpp
bool IsUblkEnabled() {
// ... test‑only override elided ...
bool property_enabled =
android::base::GetBoolProperty("ro.virtual_ab.ublk.enabled", false);
bool flag_enabled = IsVabcWithUblkSupportEnabledByFlag(); // aconfig
bool kernel_support = KernelSupportsUblk(); // uname >= 6.6
return (property_enabled && flag_enabled && kernel_support);
}
KernelSupportsUblk()解析 uname,仅内核 6.6 及以上返回 true。 aconfig 标志com::android::libsnapshot::vabc_with_ublk_support作为功能开关;构建标志RELEASE_VABC_UBLK_ENABLE_FLAG控制它,Android17 主干版本置为 true。
选择的模式保存在提示文件/metadata/ota/snapuserd_mode。第一阶段 init 读取该文件,fork snapuserd 时带上‑ublk或者‑noublk,保证各个开机阶段后端选择一致。 源码路径: system/core/init/snapuserd_transition.cpp (LaunchFirstStageSnapuserd) system/core/init/first_stage_mount_android.cpp system/fs/fs_mgr/libsnapshot/snapshot.cpp (UpdateUsesUblk)
// first_stage_mount_android.cpp
bool use_ublk = sm‑>UpdateUsesUblk();
LOG(INFO) << "using snapuserd in " << (use_ublk ? "UBLK" : "dm‑user") << " mode";
LaunchFirstStageSnapuserd(use_ublk);
第一阶段 init 识别/dev/block/ublkb*与/dev/ublk*杂项设备,等待分区就绪。
55.26.2 每次更新强制使用 dm‑user:disable_ublk
即便设备已配置使用 UBLK,某一次特定 OTA 也可以强制回退至传统 dm‑user 后端。载荷清单在DynamicPartitionMetadata中携带disable_ublk开关。
源码:system/update_engine/update_metadata.proto(DynamicPartitionMetadata.disable_ublk)
message DynamicPartitionMetadata {
// ...
optional uint64 compression_factor = 7;
// 是否为OTA禁用UBLK。即使设备配置为基于UBLK的快照,
// 该字段也会强制将dm‑user作为OTA后端。
optional bool disable_ublk = 8;
}
ota_from_target_files提供了对应的选项,可从清单中设置该字段;当目标构建未声明支持 UBLK 时,它还会自动禁用 UBLK。如果某个内核或设备出现 UBLK 相关回归问题,OEM 可借助该兜底方案,无需重新构建设备配置。
55.26.3 REPLACE 操作的 zstd 压缩
早期版本中 REPLACE 数据采用 bzip2(REPLACE_BZ)或 XZ(REPLACE_XZ)进行压缩。Android 17 新增REPLACE_ZSTD(操作类型 14,次载荷版本 10),将 zstd 压缩块解压写入目标存储区段。zstd 的压缩比接近 XZ,但解压速度要快得多。这一点十分重要,因为 REPLACE 数据在完整载荷与增量更新的完整部分中占主要体积。
源码: system/update_engine/payload_consumer/zstd_extent_writer.cc system/update_engine/payload_consumer/install_operation_executor.cc system/update_engine/payload_generator/zstd_android.cc
在执行侧,REPLACE_ZSTD与其他 REPLACE 变体复用同一套PerformReplaceOperation执行路径;InstallOperationExecutor仅在目标写入器上层封装一层ZstdExtentWriter。 在生成侧,--enable_replace_zstd参数指示delta_generator输出REPLACE_ZSTD而非普通 REPLACE。 注意该选项与 VABC 的--vabc_compression_param=zstd,<level>并不等同:前者压缩载荷内的 REPLACE 数据块,后者压缩写入设备端的 COW 镜像。
55.26.4 移除项与内存优化
Android 17 对 OTA 组件进行裁剪,降低其内存峰值占用:
- 移除 squashfs OTA 支持 :Soong、init 以及发布工具链路径中删除了对 squashfs 镜像的构建与 OTA 支持,移除
libsquashfs_utils依赖。使用 squashfs 系统镜像的设备不再被 OTA 生成工具支持。 - 移除动态分区兼容升级逻辑 :删除用于通过更新动态升级为动态分区设备的旧兼容路径,简化
DynamicPartitionMetadata处理逻辑。 - 降低更新执行阶段内存峰值 :
update_engine解析完清单后不再保留原始清单字节;分区处理完成后立即释放该分区对应的清单内存。大型差异补丁现在写入临时文件,通过文件描述符进行应用,而非全部缓冲在内存中,对现代设备数 GB 级分区意义重大。 - 简化合并统计接口 :移除独立的
ISnapshotMergeStats/snapshot_stats.h统计累加器;合并指标从持久化的SnapshotMergeReport读取,该结构体新增ublk_used字段,用于记录本次合并所使用的后端。
源码: build/make/tools/releasetools/ota_from_target_files.py system/update_engine/aosp/cleanup_previous_update_action.cc system/fs/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
这些变更对 OTA 客户端完全透明:UpdateEngine Java API、载荷格式头部、动作流水线均未改动。一台升级 Android17 OTA 的设备只会发现快照由 UBLK 提供、REPLACE 数据采用 zstd 承载,更新请求与监控方式无任何变化。
55.27 动态系统更新(DSU)与 gsid
前面介绍的所有机制都会改写已安装系统:A/B OTA 切换槽位;Virtual A/B OTA 向真实分区写入 COW 快照。动态系统更新 DSU 则采用完全相反的思路:它启动下载得到的通用系统镜像 GSI,完全不改动原有已安装系统 。原有的 system、product 分区保持原样;GSI 镜像以及全新空的 userdata 存放在/data下镜像文件中,通过 device‑mapper 块设备对外暴露,设备可以从该环境启动一次或多次。关闭或擦除 DSU 后,下次重启直接回到原始未改动的系统。 因此 DSU 非常适合体验新版本平台构建、在 GSI 上跑 CTS、应用开发者基于纯净镜像做验证,全程不需要刷机,且操作可逆。
DSU 复用本章 Virtual A/B 已经介绍过的动态分区与镜像映射组件(libfiemap的ImageManager、liblp元数据、device‑mapper)。DSU 独有的部分是一个小型系统守护进程gsid(system/gsid/,约 3800 行 C++ 代码),负责将镜像准备到动态镜像文件中,并配置一次性启动标记。
55.27.1 gsid 守护进程与 IGsiService
gsid以gsiservice AIDL 服务运行。其 rc 脚本声明为 oneshot 且默认 disabled,不会开机自启,由 binder 按需拉起,运行身份为 root,附加 system、media_rw 用户组。
源码: system/gsid/gsid.rc system/gsid/daemon.cpp(main:注册服务、执行启动任务、校验镜像映射)
该守护进程对外暴露IGsiService Binder 接口,所有 DSU 客户端均调用该接口。接口方法直接对应完整安装生命周期:
表格
| IGsiService 方法 | 用途 |
|---|---|
| openInstall(installDir) | 在/data/gsi(或 SD 卡/mnt/media_rw)下开启一次 DSU 安装 |
| createPartition(name, size, readOnly) | 分配动态镜像(例如 system、userdata) |
| commitGsiChunkFromStream / commitGsiChunkFromAshmem | 将镜像字节流式写入分区 |
| closePartition / closeInstall | 完成单个分区 / 完成整体安装 |
| enableGsi(oneShot, dsuSlot) | 将已就绪的 DSU 标记为可启动(可选一次性启动) |
| getInstallProgress | 获取GsiProgress状态(STATUS_WORKING 直至 STATUS_COMPLETE) |
| isGsiInstalled / isGsiRunning / isGsiEnabled | 查询 DSU 状态 |
| disableGsi / removeGsi | 禁用(保留镜像)或彻底擦除(回收空间) |
| getActiveDsuSlot / getInstalledDsuSlots | 查询槽位(例如 dsu,或被.lock 锁定的 DSU) |
源码: system/gsid/aidl/android/gsi/IGsiService.aidl system/gsid/gsi_service.h(GsiService : public BnGsiService) system/gsid/gsi_service.cpp(EnableGsi,SetBootMode,RunStartupTasks)
面向框架层的入口类为android.os.image.DynamicSystemManager;命令行入口工具是gsi_tool(system/gsid/gsi_tool.cpp),其子命令 install、enable、disable、wipe、wipe‑data、status、cancel 都是对上述 binder 调用的薄封装。
55.27.2 将镜像准备到动态分区
gsi_tool install(真实下载场景下由DynamicSystemInstallationService执行)会按固定顺序调用IGsiService接口。默认 system 分区流程:先创建可写 userdata 镜像,再创建只读 system 镜像,随后将 GSI 字节流写入镜像。
源码:system/gsid/gsi_tool.cpp(Install:openInstall → createPartition("userdata") → createPartition("system") → commitGsiChunkFromStream → closeInstall → enableGsi)
createPartition底层由PartitionInstaller(system/gsid/partition_installer.h)实现,后端依赖libfiemap::ImageManager。 镜像数据默认存放路径:/data/gsi/dsu/(常量kDefaultDsuImageFolder = "/data/gsi/dsu/");liblp分区元数据与 DSU 记账信息存放在/metadata/gsi/dsu/(DSU_METADATA_PREFIX)。 当 DSU 后续启动时,这些镜像文件被映射为 device‑mapper 块设备,复用 Virtual A/B 完全一致的动态分区路径,内核看到普通的 system、userdata 块设备。
源码: system/gsid/file_paths.h(kDefaultDsuImageFolder,kDsuInstallStatusFile,kDsuOneShotBootFile) system/gsid/partition_installer.h(PartitionInstaller,libfiemap ImageManager) system/gsid/include/libgsi/libgsi.h(DSU_METADATA_PREFIX "/metadata/gsi/dsu/")
55.27.3 配置一次性启动标记
enableGsi(oneShot, dsuSlot)真正让已准备好的镜像具备可启动能力。GsiService::EnableGsi会在/metadata/gsi/dsu/下写入三项状态:
- 将激活槽位名称写入
kDsuActiveFile(active); - 通过
ResetBootAttemptCounter向安装状态文件kDsuInstallStatusFile写入启动尝试计数器(整型,或 ok /disabled/wipe); - 当
oneShot为 true 时,通过SetBootMode写入标记文件kDsuOneShotBootFile(one_shot_boot)。
源码: system/gsid/gsi_service.cpp(EnableGsi 1017 行,SetBootMode 558 行,ResetBootAttemptCounter 549 行) system/gsid/libgsi.cpp(CanBootIntoGsi,MarkSystemAsGsi,DisableGsi,UninstallGsi)
一次性启动逻辑实现在libgsi.cpp::CanBootIntoGsi,在启动早期被调用。最多允许kMaxBootAttempts(值为 1)次启动尝试;如果存在 one‑shot 标记,会预先在状态文件写入 disabled,本次进入 GSI 环境,下次重启自动回落至原系统。 gsid的 run‑startup‑tasks(gsid.rc 中的 exec_background,执行 RunStartupTasks)在 GSI 启动成功后将状态标记为 ok;如果有待处理擦除请求则回收镜像空间。该降级逻辑具备故障安全:一旦 GSI 启动失败就直接放弃,坏镜像不会导致设备变砖。
完整流程串联 gsid 各组件:
- 第一阶段启动
- 准备阶段(gsid + IGsiService)
- openInstall(/data/gsi)
- 通过 PartitionInstaller + ImageManager 创建分区 (system, userdata)
- commitGsiChunk*:将 GSI 字节流写入 /data/gsi/dsu 镜像文件
- closeInstall:在 /metadata/gsi/dsu 固化 liblp 元数据
- enableGsi (oneShot, dsuSlot):写入 active、状态、one_shot_boot 标记
- 重启
- libgsi CanBootIntoGsi ():检测 one_shot 标记且尝试次数未超限?
- 是:将 DSU 镜像映射为 dm 块设备(FirstStageMountAndroid,见第 4 章)→ 进入 GSI 系统,原有系统完全不改动
- 否:启动设备原有系统
55.27.4 本书其他章节涉及 DSU 的地方
DSU 接入本书另外两个子系统:
- 开发者选项(第 50 章) :设置应用开发者选项页面提供
SelectDSUPreferenceController,允许开发者选择并加载 DSU 镜像。该图形界面底层调用与gsi_tool完全相同的IGsiService接口。 - 第一阶段挂载(第 4 章) :启动早期
FirstStageMountAndroid(system/core/init/first_stage_mount_android.cpp)调用libgsi(CanBootIntoGsi、GetActiveDsu、MarkSystemAsGsi),将 DSU 镜像映射为 system/userdata 对应的 device‑mapper 设备,同时导出ro.gsid.image_running与 DSU‑slot 系统属性。DSU 复用普通动态分区、Virtual A/B 共用的第一阶段逻辑分区挂载路径。
简言之,gsid是一个专注镜像准备与启动标记的守护进程:它复用 OTA 的动态分区与镜像映射基础设施,把下载的系统镜像放置到/data,在/metadata/gsi/dsu写入少量标记文件,交由第一阶段 init 完成启动,实现可控、可逆的全新系统镜像体验。
55.28 动手实践:OTA 实验
55.28.1 查看载荷信息
# 编译OTA工具
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
m otatools
# 查看payload内容
python3 system/update_engine/scripts/payload_info.py payload.bin
# 输出包含:
# Payload version: 2
# Manifest length: ...
# Number of partitions: N
# For each partition:
# - Name, old/new size
# - Number of operations by type
# - Data blob size
55.28.2 生成完整 OTA 包
# 编译镜像后
m dist
# 从target‑files生成完整OTA
python3 build/make/tools/releasetools/ota_from_target_files.py \
out/dist/aosp_cf_x86_64_phone-target_files-*.zip \
full_ota.zip
# 查看输出包内容
unzip -l full_ota.zip
# payload.bin
# payload_properties.txt
# META‑INF/com/android/metadata
# META‑INF/com/android/metadata.pb
# care_map.pb
55.28.3 生成增量 OTA 包
# 编译源版本
m dist
cp out/dist/aosp_cf_x86_64_phone-target_files-*.zip source_tf.zip
# 修改代码,重新构建
m dist
# 生成增量OTA
python3 build/make/tools/releasetools/ota_from_target_files.py \
-i source_tf.zip \
out/dist/aosp_cf_x86_64_phone-target_files-*.zip \
incremental_ota.zip
55.28.4 通过 ADB 应用 OTA 更新
# 主机侧推送OTA包到设备
adb push full_ota.zip /data/ota_package/
# 使用设备端update_engine_client
adb shell update_engine_client \
--payload=file:///data/ota_package/payload.bin \
--offset=<offset_from_properties> \
--size=<size_from_properties> \
--headers="<key=value pairs from properties file>"
# 或者ADB sideload(非A/B设备需要进入recovery模式)
adb reboot sideload
adb sideload full_ota.zip
55.28.5 监控更新进度
# 查看update_engine日志
adb logcat -s update_engine
# 查看更新状态
adb shell update_engine_client --follow
# 查看启动槽位
adb shell bootctl get-current-slot
adb shell bootctl get-suffix 0 # _a
adb shell bootctl get-suffix 1 # _b
adb shell bootctl is-slot-bootable 0
adb shell bootctl is-slot-bootable 1
adb shell bootctl is-slot-marked-successful 0
adb shell bootctl is-slot-marked-successful 1
55.28.6 观察 Virtual A/B 合并过程
# 重启进入新槽位后,观察合并
adb logcat -s snapuserd
# 查看快照状态
adb shell snapshotctl dump
# 监控合并进度
adb shell snapshotctl map‑snapshots
55.28.7 在 Cuttlefish 模拟器模拟更新
# 启动Cuttlefish
launch_cvd
# 构建两套编译产物(源版本、目标版本)
# 通过updater示例应用或者update_engine_client刷入增量OTA
# Cuttlefish完整支持A/B与Virtual A/B,适合OTA测试
55.28.8 查看 Recovery 模式
# 进入recovery
adb reboot recovery
# recovery下可通过音量键操作:
# - View recovery logs
# - Apply update from ADB
# - Wipe data/factory reset
# 回到Android系统后拉取recovery日志
adb pull /cache/recovery/last_log
adb pull /cache/recovery/last_kmsg
55.28.9 使用 VABC 参数构建自定义 OTA
# 携带VABC参数生成OTA
python3 build/make/tools/releasetools/ota_from_target_files.py \
--vabc_compression_param=zstd,9 \
--enable_vabc_xor \
--enable_zucchini \
--enable_lz4diff \
--compression_factor=64k \
--max_threads=8 \
-i source_tf.zip \
target_tf.zip \
optimized_ota.zip
55.28.10 载荷校验
# 校验payload完整性
brillo_update_payload check \
--payload payload.bin \
--target_image target.img \
--source_image source.img
# 提取payload属性
brillo_update_payload properties \
--payload payload.bin \
--properties_file -
55.28.11 检查快照后端(Android 17)
# 设备是否启用UBLK快照
adb shell getprop ro.virtual_ab.ublk.enabled
# 内核版本(UBLK要求6.6及以上)
adb shell uname -r
# OTA完成后,查看第一阶段init选择的模式
adb logcat -b all | grep -i "snapuserd in" # "UBLK mode" or "dm‑user mode"
# 后端激活时会出现UBLK块设备
adb shell ls -la /dev/block/ublkb* /dev/ublk* 2>/dev/null
# 记录本次更新持久化的后端模式
adb shell cat /metadata/ota/snapuserd_mode # "ublk" or "dm‑user"
55.29 本章小结
OTA 更新
- 更新方案
- 传统非 A/B:Recovery 模式、原地打补丁、有变砖风险
- A/B 无缝更新:双物理槽位、后台写入、自动回滚
- Virtual A/B:COW 快照、snapuserd、重启后合并、压缩与 XOR;Android17 增强
- update_engine
- 动作流水线:DownloadAction、DeltaPerformer、FilesystemVerifier、PostinstallRunner
- Binder 服务:applyPayload、suspend/resume/cancel
- BootControl:槽位管理、HAL 对接
- 载荷格式
- CrAU 头部、Protobuf 清单、各类操作(REPLACE 系列、SOURCE_COPY)、差分算法、签名
- 生成工具
- ota_from_target_files、brillo_update_payload、delta_generator
- Recovery
- BCB 协议、ADB sideload、非 A/B 安装器
- 框架层
- UpdateEngine API、错误码、状态回调
OTA 子系统是 Android 最关键但对用户透明的基础设施之一。一套健壮的 OTA 流水线可以让设备在极少人工干预下保持安全与版本更新。从非 A/B 演进到 A/B 再到 Virtual A/B,体现工程上持续追求:可靠性(避免变砖)、用户体验(无停机)、存储效率(不浪费存储空间)。
进一步阅读的核心源码路径:
表格
| 组件 | 路径 |
|---|---|
| update_engine 守护进程 | system/update_engine/ |
| Android 平台适配层 | system/update_engine/aosp/ |
| 载荷消费(更新执行) | system/update_engine/payload_consumer/ |
| 载荷生成 | system/update_engine/payload_generator/ |
| OTA 发布脚本 | build/make/tools/releasetools/ |
| Recovery 模式 | bootable/recovery/ |
| 更新校验器 | bootable/recovery/update_verifier/ |
| 快照管理器 | system/fs/fs_mgr/libsnapshot/ |
| UBLK 能力判断 | system/fs/fs_mgr/libsnapshot/capabilities.cpp |
| snapuserd 守护进程 | system/fs/fs_mgr/libsnapshot/snapuserd/ |
| UBLK 块服务 | system/fs/fs_mgr/libsnapshot/snapuserd/ublk_block_server.cpp |
| COW 格式实现 | system/fs/fs_mgr/libsnapshot/libsnapshot_cow/ |
| zstd REPLACE 写入器 | system/update_engine/payload_consumer/zstd_extent_writer.cc |
| 框架 API | frameworks/base/core/java/android/os/UpdateEngine.java |