第 55 章:OTA 更新

空中下载(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/corelibsnapshotsnapuserd以及 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取值可为NONERETROFITLAUNCH,用于区分设备是后期升级获得该特性,还是出厂原生搭载该特性。

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 服务:

  1. android.os.UpdateEngineService ------ 主接口
  2. stable AIDL 变体 ------ 用于跨版本兼容性

55.2.2 Action 流水线

update_engine采用 Action 流水线模式。更新的每一步都是Action的子类,由ActionProcessor串联执行。Action 之间依靠类型安全的ActionPipe传递数据。

典型 Action

源码:system/update_engine/common/action.hsystem/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.ccsystem/update_engine/payload_consumer/payload_metadata.cc

载荷结构:

  1. 载荷头部(24 字节)
    • Magic:CrAU(4 字节)
    • Major Version(8 字节 uint64)
    • Manifest Size(8 字节 uint64)
    • Metadata Sig Size(4 字节 uint32)
  2. DeltaArchiveManifest(protobuf):分区列表、操作、块大小、时间戳
  3. 元数据签名(长度由头部指定,可选)
  4. 二进制数据块:各个操作对应的压缩 / 原始数据
  5. 载荷签名:文件末尾追加

头部解析逻辑位于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 压缩的 bsdiff
  • PUFFDIFF:感知 deflate 格式差分
  • ZUCCHINI:可执行文件专用二进制差分
  • LZ4DIFF_*:EROFS 的 LZ4 感知差分

全量操作(不依赖源分区)

  • REPLACE:写入原始数据
  • REPLACE_BZ:bzip2 解压后写入
  • REPLACE_XZ:XZ 解压后写入
  • REPLACE_ZSTD:zstd 解压后写入(Android17)
  • ZERO:填充 0
  • DISCARD:执行 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_COPYSOURCE_BSDIFFPUFFDIFFZUCCHINI等引用源块的操作。体积小很多(通常 50‑200MB,对比全量 2‑4GB),但设备必须运行指定的源版本构建。

  • 差分载荷:源分区 → 差分引擎 → 补丁 + 复制块 → 目标分区
  • 全量载荷:载荷数据块 → 目标分区

55.3.6 载荷签名与校验

载荷使用密码学签名防止篡改:

  1. 元数据签名:对头部 + manifest 签名;解析 manifest 之前完成校验,防止 protobuf 解析漏洞被利用
  2. 载荷签名:对整个载荷(签名段除外)签名;全部操作执行完成后校验

源码:system/update_engine/payload_consumer/payload_verifier.h

设备信任证书存放于/system/etc/security/otacerts.zip(或kUpdateCertificatesPath指定路径)。校验阶段PayloadVerifier从证书取出公钥,校验 RSA/EC 签名。

校验流程:

  1. update_engine 拿到载荷(头部 + manifest + 数据 + 签名)
  2. 校验元数据签名:读取 otacerts.zip 证书,校验头部 + manifest 的 RSA/EC 签名;元数据校验 OK
  3. 执行更新操作
  4. 对除去签名段的完整载荷计算哈希,与签名内哈希比对,校验载荷签名;载荷校验 OK

55.4 DeltaPerformer

DeltaPerformer是实际向目标分区执行载荷操作的核心工作类。实现FileWriter接口,在下载过程中增量接收载荷字节流。

55.4.1 流式执行

源码:system/update_engine/payload_consumer/delta_performer.hsystem/update_engine/payload_consumer/delta_performer.cc

DeltaPerformer::Write()会在网络收到载荷分片时被反复调用。内部维护状态,记录解析与执行进度。

处理流程:

  1. 通过Write接收字节
  2. 判断头部是否解析完成;未完成则向缓冲区累积数据;数据不足返回等待更多数据;数据足够解析魔数、版本、manifest 大小、签名大小
  3. 判断 manifest 是否接收完整;完整则解析 protobuf manifest,执行 manifest 校验,准备更新分区
  4. 判断是否全部操作处理完毕;未完成则等待当前操作所需数据充足,执行操作,推进到下一个操作
  5. 全部完成后提取并校验签名

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根据操作类型,在基础写入器外层包装BzipExtentWriterXzExtentWriter,Android17 新增ZstdExtentWriter,再将解压后字节写入目标区域。

源码:system/update_engine/payload_consumer/install_operation_executor.ccsystem/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_asystem_bboot_aboot_b以此类推。

55.5.3 A/B 更新完整生命周期

  1. OTA 客户端调用applyPayload(url, headers)
  2. update_engine 获取当前插槽为 A,下载并将载荷写入插槽 B
  3. 调用SetActiveBootSlot(B);状态变为UPDATED_NEED_REBOOT,提示用户重启
  4. 设备重启,从新激活的插槽 B 启动
  5. Bootloader 维护重试计数器
  6. update_verifier读取 care_map,校验 dm‑verity 块完整性
  7. 校验成功执行MarkBootSuccessful()
  8. GetOTAUpdateResult()得到OTA_SUCCESSFUL
  9. 执行CleanupPreviousUpdate(Virtual A/B 执行合并)

55.5.4 Bootloader 交互

Bootloader 维护每一个插槽元数据:

表格

字段 说明
bootable 该插槽是否可启动
successful 是否已经校验启动成功
active 下次开机要启动的插槽
retry_count 标记为不可启动之前剩余的启动重试次数

Bootloader 启动逻辑:

  1. Bootloader 启动
  2. 判断 active 插槽是否可启动;不可启动切换另一插槽
  3. retry_count > 0:计数器自减,尝试启动该插槽
  4. 插槽被标记successful:正常启动 Android
  5. 插槽多次启动失败:标记该插槽不可启动,切换到另一个插槽
  6. Android 系统启动后运行update_verifier
  7. 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 数据的合并视图。

阶段示意:

  1. 更新过程中 :系统运行system_aupdate_engine把变更块写入 COW 设备(位于 /data 或者 super 分区);dm‑verity 保护源分区。
  2. 重启后(合并前)snapuserd读取基础system_b镜像 + COW 数据,通过 dm‑user 设备输出合并视图给内核,dm‑verity 校验合并后视图。
  3. 合并完成后 :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.cppsystem/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 文件布局:

  1. 头部:版本、块大小、操作计数
  2. 操作表:连续一组 CowOperation 条目
  3. 数据段:REPLACE 操作对应的压缩数据块

55.6.5 snapuserd

snapuserd是用户态守护进程,负责对外提供快照块设备。它在开机极早期(init 第一阶段)启动,通过用户态块设备向内核输出基础分区 + COW 数据的合并视图。块设备抽象为IBlockServer接口;历史上后端只有 dm‑user;Android17 新增 UBLK 后端,守护进程启动阶段选择使用哪一个。接口头文件snapuserd/include/snapuserd/block_server.h;两个实现分别为dm_user_block_server.cppublk_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
};

完整流程:

  1. CleanupPreviousUpdateAction执行;等待BootCompleted事件
  2. 确认插槽已经标记启动成功
  3. 调用InitiateMerge()启动合并 worker
  4. ReadAhead 预读 COW 块;MergeWorker 把块写入基础分区
  5. 更新合并状态;全部完成执行CommitMerge(num_ops)
  6. 清理快照资源;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 数据可以存放于:

  1. super 分区空闲空间:super 分区还有剩余容量
  2. 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.txt
  • META/dynamic_partitions_info.txt
  • META/misc_info.txt
  • META/ab_partitions.txt

55.7.2 生成流程

输入:target‑files.zip(全部镜像、元数据、密钥);增量更新额外需要source‑files.zip

  1. 解析META/misc_info.txt
  2. 判断是否 A/B 设备
    • A/B 设备:生成 A/B 载荷
    • 非 A/B 设备:生成传统 OTA 包
  3. 从 target‑files 解压镜像
  4. 调用PayloadGenerator,内部调用delta_generator生成payload.bin
  5. 对 payload 签名
  6. 生成 properties 属性文件
  7. 打包为 OTA zip 压缩包,对 zip 整体签名
  8. 输出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.ccsystem/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执行:

  1. 读取源镜像与目标镜像
  2. 识别文件系统类型(ext4、EROFS 等)
  3. 按文件对块分组,优化差分效果
  4. 为每一段块范围选择最优差分算法:
    • 完全相同块:SOURCE_COPY
    • 全零块:ZERO
    • 根据内容选择 BSDIFF、PUFFDIFF、ZUCCHINI、LZ4DIFF
    • 都不适合时使用带压缩的 REPLACE 作为兜底
  5. 将操作与数据块序列化为 payload 格式

55.7.5 差分算法选择逻辑

  1. 判断块范围是否与源完全相同 → SOURCE_COPY
  2. 判断是否全部为零块 → ZERO
  3. 是否为 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‑scriptupdate‑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 可以增量处理有效载荷:

  1. 从首个数据块解析 24 字节头部
  2. 持续接收字节,直到完整清单数据就绪
  3. 解析清单并完成分区预处理
  4. 后续每一项操作,等待对应数据块到达后立刻执行

写入采用流水线机制:当前操作的数据正在写入目标设备的同时,下一份操作数据正在后台下载。 设备不需要预留等同于完整有效载荷大小的空闲空间。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.cppbootable/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 工作协议:

  1. 主系统向 command 字段写入boot‑recovery,将 recovery 参数写入 recovery 字段
  2. Bootloader 读取 command,启动进入 Recovery 模式
  3. Recovery 从 BCB 读取启动参数
  4. 升级完成后,Recovery 清空 BCB,设备正常开机

55.9.3 Recovery 命令

源码路径:bootable/recovery/recovery.cppstart_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.cppInstallPackage()函数负责:

  • 使用/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 = 10
  • CLEANUP_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

执行逻辑:

  1. PostinstallRunnerAction 启动
  2. 遍历所有标记run_postinstall的分区
  3. 以只读方式挂载目标分区
  4. fork 并执行postinstall_path脚本
  5. 判断退出码是否为 0
    • 成功:卸载分区,处理下一个分区
    • 失败:判断postinstall_optional
      • true:输出警告,继续后续流程
      • false:整个 OTA 升级失败
  6. 全部处理完成结束

脚本运行在受限环境:

  • 目标分区挂载到临时路径
  • 进程继承 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 设备行为:

  1. 每个 slot 拥有独立 vbmeta 分区,保存 Android Verified Boot 元数据
  2. Bootloader 启动槽位之前校验信任链
  3. 运行时 dm‑verity 保护分区完整性
  4. 首次开机 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 块需要完整替换新内容。

  1. 构建服务器阶段:delta_generator 对比源、目标镜像;生成 InstallOperation:type=REPLACE dst_extents=block42 data_offset=X data_length=4096;4096 字节写入 payload 数据段。
  2. 设备下载阶段:HTTP 字节流交给 LibcurlHttpFetcher → DownloadAction::ReceivedBytes → DeltaPerformer::Write → 在 buffer_缓存;判断是否收到完整当前操作数据。
  3. 设备解析执行阶段:执行 PerformReplaceOperation;判断是否 Virtual A/B
    • 普通 A/B:PartitionWriter 调用 pwrite 写入/dev/block/...system_b
    • Virtual A/B:VABCPartitionWriter 通过 ICowWriter 写入 COW_REPLACE 操作到 /data 下 COW 设备
  4. 重启之后 (Virtual A/B):snapuserd ReadWorker 处理 IO;dm‑user 对外提供合并后的 block42;dm‑verity 完成校验;文件系统读取更新后的块。
  5. 合并完成 (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 条目:

  1. 读取源分区旧块
  2. 和 payload 中新块做异或运算,得到 XOR 差分数据,大部分为 0
  3. 使用 lz4/zstd 压缩,保存为 COW_XOR 写入 COW 设备
  4. 读取的时候:解压 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 迭代

InstallPlanuntouched_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 分区更大:

  1. 从 manifest 的dynamic_partition_metadata读取目标分区布局

  2. 更新 super 分区内逻辑分区元数据

  3. 创建适配新布局大小的 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 密钥签名。签名链路:

  1. 构建系统使用 release 密钥对 payload 签名
  2. 设备在otacerts.zip存放对应证书
  3. 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();
}

关键设计点:

  1. 任何写入开始前,标记目标 slot 为不可启动,避免 Bootloader 启动半写损坏镜像
  2. 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回读目标分区并计算哈希。

流程:

  1. FilesystemVerifierAction 启动
  2. 遍历 InstallPlan 中的每一个分区
  3. 打开目标分区块设备
  4. 顺序读取全部块,计算 SHA‑256 哈希
  5. 对比哈希是否与 InstallPlan 期望值匹配
    • 不匹配:返回kFilesystemVerifierError升级失败
    • 匹配:处理下一个分区
  6. 全部分区校验通过,结束

该步骤捕获以下问题:

  • 存储介质比特位损坏
  • 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 校验流程

  1. update_verifier 启动
  2. 读取/data/ota_package/care_map.pb
  3. 遍历/sys/block/dm‑*查找 dm‑verity 块设备
  4. 匹配分区名称与 dm 设备
  5. 遍历 care_map 记录的块区间
  6. 通过 dm‑verity 设备读取每一个块区间
  7. 判断全部读取是否成功
    • 全部成功:调用 Boot Control HAL 的MarkBootSuccessful
    • 读取失败:设备重启,Bootloader 减少重试计数

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

后端选择逻辑:

  1. snapuserd 在第一阶段 init 启动

  2. 判断是否显式传入‑ublk / ‑noublk参数,优先遵循参数

  3. 读取提示文件/metadata/ota/snapuserd_mode

  4. 自动检测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 已经介绍过的动态分区与镜像映射组件(libfiemapImageManagerliblp元数据、device‑mapper)。DSU 独有的部分是一个小型系统守护进程gsidsystem/gsid/,约 3800 行 C++ 代码),负责将镜像准备到动态镜像文件中,并配置一次性启动标记。

55.27.1 gsid 守护进程与 IGsiService

gsidgsiservice 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_toolsystem/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底层由PartitionInstallersystem/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/下写入三项状态:

  1. 将激活槽位名称写入kDsuActiveFile(active);
  2. 通过ResetBootAttemptCounter向安装状态文件kDsuInstallStatusFile写入启动尝试计数器(整型,或 ok /disabled/wipe);
  3. 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 各组件:

  1. 第一阶段启动
  2. 准备阶段(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 标记
  3. 重启
  4. libgsi CanBootIntoGsi ():检测 one_shot 标记且尝试次数未超限?
    • 是:将 DSU 镜像映射为 dm 块设备(FirstStageMountAndroid,见第 4 章)→ 进入 GSI 系统,原有系统完全不改动
    • 否:启动设备原有系统

55.27.4 本书其他章节涉及 DSU 的地方

DSU 接入本书另外两个子系统:

  1. 开发者选项(第 50 章) :设置应用开发者选项页面提供SelectDSUPreferenceController,允许开发者选择并加载 DSU 镜像。该图形界面底层调用与gsi_tool完全相同的IGsiService接口。
  2. 第一阶段挂载(第 4 章) :启动早期FirstStageMountAndroidsystem/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
相关推荐
消失的旧时光-19431 天前
Android 系统层扫盲 03:第一次看 AOSP 源码目录,frameworks、system、packages、hardware 都是干什么的?
aosp
齊家治國平天下1 个月前
AAOS 电源管理深度解析:休眠/唤醒/功耗优化
android·车载系统·aaos·aosp·电源管理·休眠唤醒·carpowermanager
浪客川2 个月前
AOSP源码隐藏状态栏
android·aosp
故渊at3 个月前
第一板块:Android 系统基石与运行原理 | 第一篇:Android 系统架构分层与 AOSP 规范
android·系统架构·android系统·aosp
不会Android的潘潘3 个月前
【AOSP 应用集成全方案】
android·aosp
千里马学框架3 个月前
安卓车载手机原生多屏闪黑问题分析及修复成果展示
android·智能手机·性能·多屏·系统开发·aosp·framework工程师
韩曙亮4 个月前
【Android】Android 源码查看 ( Android 源码在线查看 2026-03-30 | Android 源码下载 | Android 源码查看工具 )
android·安卓·安卓源码·aosp·android 源码·android源码查看工具·android 源码工具
andr_gale4 个月前
04_rc文件语法规则
android·framework·aosp
andr_gale4 个月前
05_aosp12中init进程解析rc文件流程分析
android·aosp·framwork