Android 动态分区详解(三) 动态分区配置及super.img的生成

36 篇文章 124 订阅 ¥69.90 ¥99.00

Android 动态分区详解(三) 动态分区配置及super.img的生成

1. 导读

关于 Android 的各种特性,永远建议首先参考官方文档,包括两类:

  • 一类是代码中自带的文档,包括模块中 README.md, HIDL 和 头文件中的注释等

  • 另一类是 Android 官方网站: https://source.android.com/

这里重点说下第二个,现在官方网站上的文档是越来越丰富了。我刚开始分析 A/B 系统的时候,官方文档屈指可数,也没有中文版。现在文档很多,感觉似乎各个模块都有,而且还配有多语言版本,很大程度上可以解决 “my english is poor” 问题。

如果发现有神秘力量导致无法访问官方网站,现在也可以访问无障碍版本: https://source.android.google.cn/

当我真正开始写关于 Android 动态分区配置介绍的时候,又认真看了一遍官方介绍动态分区的页面,只能一声感叹,“卧槽~”。我想说的,官方网站早就说了,以至于我都不知道还能写些什么。

Android 官方介绍动态分区的链接:

《实现动态分区》(https://source.android.com/devices/tech/ota/dynamic_partitions/implement)

官方页面从比较高的角度综述了 Android 动态分区,包括基本原理,配置说明,注意事项等,目的还是给大家介绍如何使用动态分区,重点不在于强调原理。

本文根据设备是否存在 super 分区,将包含 super 分区的称为原生动态分区,对应于 Android 的默认动态分区配置;将从低版本升级上来不包含 super 分区的称为改造动态分区,对应于 retrofit 的配置。

本文重点介绍 Android 动态分区的这编译配置,这些配置是如何生效,super.img 又是如何生成的。

如果您之前没有阅读过上面说的 《实现动态分区》,在开始本文之前,我再建议您先仔细阅读下(官方这篇值得反复阅读),以及本动态分区详解系列的第一篇《Android 动态分区详解(一)》,然后再开始本文。

本文基于 android-10.0.0_r47 代码进行分析

  • 如果只想知道原生动态分区需要如何配置,请参考 2.1.1 节;
  • 如果只想知道升级改造动态分区需要如何配置,请参考 2.1.2 节;
  • 第 2.2 节分析了几个 AOSP 中动态分区配置的示例;
  • 第 2.3 节汇总了一些动态分区参数的设置原则;
  • 第 3.1 节介绍了原生动态分区和改造动态分区的两组参数最终合二为一;
  • 第 3.2 节介绍了动态分区参数最终被输出到 misc_info.txt 文件中用于生成动态分区文件;
  • 第 4 节介绍了动态分区文件 super.imgsuper_empty.img 是如何生成的,解释了我第一篇中 lpmake 为什么会被调用 3 次的疑问。

2. Android 动态分区配置

2.1 动态分区配置选项

根据《实现动态分区》里讲述的,将动态分区需要设置的项列举如下。

2.1.1 原生动态分区配置

对于原生动态分区,需要以下设置:

# 动态分区总开关
PRODUCT_USE_DYNAMIC_PARTITIONS := true

# 设置 super 分区大小
BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>

# 设置分区组, 可以设置多个组,对于 A/B 设备,每组最终会有 _a 和 _b 两个 slot
# 这里以分区组 group_foo 为例,会生成 group_foo_a 和 group_foo_b 两个组
BOARD_SUPER_PARTITION_GROUPS := group_foo

# 设置分区组包含的分区, 这里包含 system, vendor 和 product 等 3 个分区
BOARD_GROUP_FOO_PARTITION_LIST := system vendor product

# 设置分区组总大小, 总大小需要能够放下分区组里面的所有分区
BOARD_GROUP_FOO_SIZE := <size-in-bytes>

# 启用块级重复信息删除,可以进一步压缩 ext4 映像
BOARD_EXT4_SHARE_DUP_BLOCKS := true
2.1.2 改造动态分区配置

对于改造动态分区(retrofit), 需要以下设置:

# 改造(retrofit)动态分区总开关, 这里多了一个 retrofit,标明是升级改造设备
PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

# 设置为所有动态分区内子分区大小的总和
BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>

# 设置动态分区子分区, 这里包含 system, vendor 和 product 等 3 个分区
BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor product

# 逐个设置每一个子分区大小, 这设置 system, vendor 分区大小 
# BOARD_SUPER_PARTITION_$(partition)_DEVICE_SIZE
BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := <size-in-bytes>
BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := <size-in-bytes>

# 设置分区组, 可以设置多个组,每组最终会有 _a 和 _b 两个 slot
BOARD_SUPER_PARTITION_GROUPS := group_foo

# 设置分区组包含的分区, 这里包含 system, vendor 和 product 等 3 个分区
BOARD_GROUP_FOO_PARTITION_LIST := system vendor product

# 设置分区组总大小, 总大小需要能够放下分区组里面的所有分区
BOARD_GROUP_FOO_SIZE := <size-in-bytes>

# 指定 metadata 数据存放的设备,这里设置为 system 分区,也可以是单独的分区
BOARD_SUPER_PARTITION_METADATA_DEVICE := system

# 启用块级重复信息删除,可以进一步压缩 ext4 映像
BOARD_EXT4_SHARE_DUP_BLOCKS := true

这里特别说明的是,改造动态分区时,需要通过 BOARD_SUPER_PARTITION_METADATA_DEVICE 指定 metadata 存放的分区。因此,不仅可以将 metadata 数据和某个分区放到一起,例如原生动态分区中就是将 metadata 和 super 分区放到一起;也可以将 metadata 数据单独放到某个分区中,例如 cuttlefish 模拟器中就是将 metadata 单独存放在名为 vda 的分区中 。

2.1.3 动态分区配置注意事项
  1. 在动态分区配置中,不再需要以下分区大小设置了,例如:
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 4294967296 # 4 GB
BOARD_VENDORIMAGE_PARTITION_SIZE := 536870912 # 512MB
BOARD_PRODUCTIMAGE_PARTITION_SIZE := 1610612736 # 1.5GB
  1. 原生动态分区中,super 分区内的 system, vendor, product 等需要从 GPT 分区表中移除
  2. 应避免将 userdata, cache 或任何其他永久性读写分区放在 super 分区中

2.2 动态分区配置示例

关于动态分区配置,这里再以三个 AOSP 自带的 google 设备动态分区配置为例说明,包括原生动态分区和改造动态分区(retrofit),这部分配置位于 device/google 目录之下。

2.2.1 crosshatch 设备(Pixel 3 XL)配置示例

crosshatch 设备(Pixel 3 XL) 支持原生动态分区,也支持改造动态分区,配置如下:

# device/google/crosshatch/BoardConfig-common.mk
ifneq ($(PRODUCT_USE_DYNAMIC_PARTITIONS), true)
  # ...
else
  BOARD_EXT4_SHARE_DUP_BLOCKS := true
endif

ifeq ($(PRODUCT_USE_DYNAMIC_PARTITIONS), true)
BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := \
    system \
    vendor \
    product

ifeq ($(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS), true)
# Normal Pixel 3 must retrofit dynamic partitions.
BOARD_SUPER_PARTITION_SIZE := 4072669184
BOARD_SUPER_PARTITION_METADATA_DEVICE := system
BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor product
BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := 2952790016
BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := 805306368
BOARD_SUPER_PARTITION_PRODUCT_DEVICE_SIZE := 314572800
# Assume 4MB metadata size.
# TODO(b/117997386): Use correct metadata size.
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 4069523456
else
# Mainline Pixel 3 has an actual super partition.

# TODO (b/136154856) product_services partition is removed.
# Instead, we will add system_ext once it is ready.
# BOARD_PRODUCT_SERVICESIMAGE_FILE_SYSTEM_TYPE := ext4
# TARGET_COPY_OUT_PRODUCT_SERVICES := product_services

BOARD_SUPER_PARTITION_SIZE := 12884901888
# Assume 1MB metadata size.
# TODO(b/117997386): Use correct metadata size.
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 6441402368

# TODO (b/136154856) product_services partition removed.
# Instead, we will add system_ext once it is ready.
# BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST += \
#    product_services \

endif # PRODUCT_RETROFIT_DYNAMIC_PARTITIONS
endif # PRODUCT_USE_DYNAMIC_PARTITIONS

crosshatch 动态分区总体上,设备定义了 1 个动态分区组 google_dynamic_partitions, 包含分区 system vendor product

对于原生动态分区,有:

# 启用块级重复信息删除,可以进一步压缩 ext4 映像
BOARD_EXT4_SHARE_DUP_BLOCKS := true

# 总开关
PRODUCT_USE_DYNAMIC_PARTITIONS := true
# 分区组和子分区
BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
# super 分区和分区组大小
BOARD_SUPER_PARTITION_SIZE := 12884901888
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 6441402368

对于改造动态分区,有:

# 启用块级重复信息删除,可以进一步压缩 ext4 映像
BOARD_EXT4_SHARE_DUP_BLOCKS := true

# 总开关
PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true
# 分区组和子分区
BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
# super 分区大小
BOARD_SUPER_PARTITION_SIZE := 4072669184
# metadata 存放的设备
BOARD_SUPER_PARTITION_METADATA_DEVICE := system
# 动态分区内的子分区
BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor product
# 每个子分区大小
BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := 2952790016  # 2816M
BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := 805306368   # 768M
BOARD_SUPER_PARTITION_PRODUCT_DEVICE_SIZE := 314572800  # 300M
# 分区组大小
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 4069523456
2.2.2 bonito 设备(Pixel 3a XL)配置示例

bonito 设备(Pixel 3a XL)只支持改造动态分区,配置如下:

# device/google/bonito/device-common.mk
# Enable retrofit dynamic partitions for all bonito
# and sargo targets
PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

# device/google/bonito/BoardConfig-common.mk
BOARD_EXT4_SHARE_DUP_BLOCKS := true
BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := \
    system \
    vendor \
    product

BOARD_SUPER_PARTITION_SIZE := 4072669184
BOARD_SUPER_PARTITION_METADATA_DEVICE := system
BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor
BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := 3267362816
BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := 805306368
# Assume 4MB metadata size.
# TODO(b/117997386): Use correct metadata size.
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 4068474880

从这里的配置看,和 crosshatch 设备(Pixel 3 XL)对改造动态分区的配置是一样的,只是少了一个 product 分区。

2.2.3 模拟器 cuttlefish 配置示例

模拟器 cuttlefish 的动态分区配置位于文件: device/google/cuttlefish/shared/BoardConfig.mk,如下:

# device/google/cuttlefish/shared/BoardConfig.mk
ifeq ($(TARGET_USE_DYNAMIC_PARTITIONS),true)
  BOARD_SUPER_PARTITION_SIZE := 6442450944
  BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
  BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
  BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 6442450944
  BOARD_SUPER_PARTITION_METADATA_DEVICE := vda
  BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT := true
  BOARD_SUPER_IMAGE_IN_UPDATE_PACKAGE := true
  TARGET_RELEASETOOLS_EXTENSIONS := device/google/cuttlefish/shared
else
  # ...
endif

这里是模拟 cuttlefish 原生动态分区的配置,重点如下:

  • 不带 PRODUCT_RETROFIT_DYNAMIC_PARTITIONS, 原生动态分区
  • super 分区大小为 6442450944
  • 定义了一个动态分区组 google_dynamic_partitions, 大小为 6442450944, 包含三个子分区 system vendor product
  • 指定了 metadata 数据存放的分区 vda

另外:

  • BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT := true 指定了 super.img 由 $(PRODUCT_OUT) 目录下的文件创建,并输出到 $(PRODUCT_OUT)/super.img

注意: 上面的 cuttlefish 的例子中,

  1. 使用自定义的 TARGET_USE_DYNAMIC_PARTITIONS 作为开关,而不是 PRODUCT_USE_DYNAMIC_PARTITIONS, 不过后者会根据前者设置为 true
  2. BOARD_SUPER_PARTITION_SIZEBOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE 一样,都是 6442450944,所以 cuttlefish 模拟器应该不是 A/B 设备。A/B 设备下要求动态分区组大小为:(super 分区大小 - 开销) / 2

2.3 动态分区参数检查

设置了动态分区参数以后,Android 在编译时会对参数进行检查,检查的内容包括两类:

  • 开关参数检查,检查动态分区的配置开关是否冲突

  • 分区大小参数的检查,检查分区大小设置是否符合要求

我本来对这几段 Makefile 代码做了注释,但因为很长,贴上的话整篇文章就更啰嗦了,所以这里直接贴上结论把。

2.3.1 开关参数检查

文件 build/make/core/config.mk 的 811~878 行,对动态分区的开关参数进行检查。

参考链接: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/config.mk#811

这段代码检查的重点有:

  • 改造动态分区开关和动态分区总开关必须同时设置

    # 总开关
    PRODUCT_USE_DYNAMIC_PARTITIONS := true
    # 改造(retrofit)动态分区开关
    PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true
    
  • 打开了动态分区之后,列表(system, vendor, odm, product, product_services)对应分区的以下 SIZE 配置不能同时设置

    # (system, vendor, odm, product, product_services)
    BOARD_$(device)IMAGE_PARTITION_SIZE
    BOARD_$(device)IMAGE_PARTITION_RESERVED_SIZE
    
  • 对每一个分组 group,需要同时设置 PARTITION_LISTSIZE 参数

    BOARD_$(group)_PARTITION_LIST
    BOARD_$(group)_SIZE
    
  • 如果分组没有设置 BOARD_$(group)_PARTITION_LIST, 则默认分组内没有分区

  • 分组名 BOARD_SUPER_PARTITION_GROUPS 不能设置为列表(system vendor product product_services odm)中的名字

  • 打开动态分区后,不需要再设置 BOARD_BUILD_SYSTEM_ROOT_IMAGE = true

2.3.2 分区大小限制

文件 build/make/core/Makefile 的 3375~3485 行,定义了多个宏对动态分区以及子分区的大小进行检查。

参考链接: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#3375

详细的代码比较繁琐,主要是各分区或分组大小数值的计算和比较。

这里原文引用一下《实现动态分区》中说的动态分区大小限制:

  • 对于虚拟 A/B 启动设备,所有组的最大大小总和不得超过:
    BOARD_SUPER_PARTITION_SIZE - 开销
  • 对于 A/B 启动设备,所有组的最大大小总和必须为:
    BOARD_SUPER_PARTITION_SIZE/ 2 - 开销
  • 对于非 A/B 设备和改造的 A/B 设备,所有组的大小上限总和必须为:
    BOARD_SUPER_PARTITION_SIZE - 开销
  • 在构建时,更新组中每个分区的映像大小总和不得超过组的大小上限。
  • 在计算时需要扣除开销,因为要考虑元数据、对齐等。合理的开销是 4 MiB,但您可以根据设备的需要选择更大的开销。

这里说下上面提到的 4M 总开销的来源,主要有两类:

  • 元数据(metadata)开销,元数据位于分区开始的 4KB~1MB 范围内
  • 分区对齐开销,默认分区按照 1MB 对齐

如果动态分区中定义了一个分区组,包含三个分区(system, vendor, product),对于 A/B 系统,分区组会有两个槽位,因此一共有 6 个子分区。按中值计算,平均每个子分区对齐开销为 0.5M,这样 6 个分区对齐,一共需要 0.5M x 6 = 3M 的总对齐开销。再加上元数据(metadata) 1M 的开销,所以预估 4M = 1M + 0.5M x 6 的总开销是合理的。

3. Android 动态分区参数的处理

3.1 原生和改造动态分区两套参数的合并

前面第 2 节提到,对于原生动态分区和改造动态分区,需要进行不同的参数设置。

文件 build/make/core/config.mk 的 923~994行,代码将原生动态分区和改造动态分区的两组参数合并成一组参数进行了处理。

参考链接: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/config.mk#923

2.2.1 crosshatch 设备(Pixel 3 XL)配置示例 节提到的原生动态分区和改造动态分区参数为例,经过处理,最终得到以下的动态分区参数。

对于原生动态分区为:

PRODUCT_USE_DYNAMIC_PARTITIONS := true

BOARD_EXT4_SHARE_DUP_BLOCKS := true

BOARD_SUPER_PARTITION_SIZE := 4072669184
BOARD_SUPER_PARTITION_METADATA_DEVICE := super

BOARD_SUPER_PARTITION_BLOCK_DEVICES := super
BOARD_SUPER_PARTITION_SUPER_DEVICE_SIZE := 4072669184

BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 4069523456

BOARD_BUILD_RETROFIT_DYNAMIC_PARTITIONS_OTA_PACKAGE :=

INTERNAL_KERNEL_CMDLINE += \
		androidboot.super_partition=$(BOARD_SUPER_PARTITION_METADATA_DEVICE)

对于改造动态分区为:

PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

BOARD_EXT4_SHARE_DUP_BLOCKS := true

BOARD_SUPER_PARTITION_SIZE := 4072669184
BOARD_SUPER_PARTITION_METADATA_DEVICE := system

BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor product
BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := 2952790016
BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := 805306368
BOARD_SUPER_PARTITION_PRODUCT_DEVICE_SIZE := 314572800

BOARD_SUPER_PARTITION_GROUPS := google_dynamic_partitions
BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
BOARD_GOOGLE_DYNAMIC_PARTITIONS_SIZE := 4069523456

BOARD_BUILD_RETROFIT_DYNAMIC_PARTITIONS_OTA_PACKAGE := true

INTERNAL_KERNEL_CMDLINE += \
		androidboot.super_partition=$(BOARD_SUPER_PARTITION_METADATA_DEVICE)

在这里,这两套参数最终合并成了一套参数。

3.2 动态分区参数最终去了哪里?

上一节提到原生动态分区和改造动态分区的不同设置最终会合并成对同一组参数的设置。

那转换成这同一组参数后,后续是如何处理的呢?

build/make/core/Makefile 中定义了一个宏函数 dump-dynamic-partitions-info,用于将原生动态分区相关信息输出到指定的文件中,如下:

# $(1): file
define dump-dynamic-partitions-info
  $(if $(filter true,$(PRODUCT_USE_DYNAMIC_PARTITIONS)), \
    echo "use_dynamic_partitions=true" >> $(1))
  $(if $(filter true,$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS)), \
    echo "dynamic_partition_retrofit=true" >> $(1))
  echo "lpmake=$(notdir $(LPMAKE))" >> $(1)
  $(if $(filter true,$(PRODUCT_BUILD_SUPER_PARTITION)), $(if $(BOARD_SUPER_PARTITION_SIZE), \
    echo "build_super_partition=true" >> $(1)))
  $(if $(filter true,$(BOARD_BUILD_RETROFIT_DYNAMIC_PARTITIONS_OTA_PACKAGE)), \
    echo "build_retrofit_dynamic_partitions_ota_package=true" >> $(1))
  echo "super_metadata_device=$(BOARD_SUPER_PARTITION_METADATA_DEVICE)" >> $(1)
  $(if $(BOARD_SUPER_PARTITION_BLOCK_DEVICES), \
    echo "super_block_devices=$(BOARD_SUPER_PARTITION_BLOCK_DEVICES)" >> $(1))
  $(foreach device,$(BOARD_SUPER_PARTITION_BLOCK_DEVICES), \
    echo "super_$(device)_device_size=$(BOARD_SUPER_PARTITION_$(call to-upper,$(device))_DEVICE_SIZE)" >> $(1);)
  $(if $(BOARD_SUPER_PARTITION_PARTITION_LIST), \
    echo "dynamic_partition_list=$(BOARD_SUPER_PARTITION_PARTITION_LIST)" >> $(1))
  $(if $(BOARD_SUPER_PARTITION_GROUPS),
    echo "super_partition_groups=$(BOARD_SUPER_PARTITION_GROUPS)" >> $(1))
  $(foreach group,$(BOARD_SUPER_PARTITION_GROUPS), \
    echo "super_$(group)_group_size=$(BOARD_$(call to-upper,$(group))_SIZE)" >> $(1); \
    $(if $(BOARD_$(call to-upper,$(group))_PARTITION_LIST), \
      echo "super_$(group)_partition_list=$(BOARD_$(call to-upper,$(group))_PARTITION_LIST)" >> $(1);))
  $(if $(filter true,$(TARGET_USERIMAGES_SPARSE_EXT_DISABLED)), \
    echo "build_non_sparse_super_partition=true" >> $(1))
  $(if $(filter true,$(BOARD_SUPER_IMAGE_IN_UPDATE_PACKAGE)), \
    echo "super_image_in_update_package=true" >> $(1))
endef

调用 dump-dynamic-partitions-info 主要有以下 3 个地方:

  1. 生成 BUILT_TARGET_FILES_PACKAGE 目标时,将动态分区信息追加到 $(zip_root)/META/misc_info.txt 文件中
  2. 被宏 dump-super-image-info 内部调用,在编译 super.img 或 super_empty.img 时,将动态分区信息输出到各自对应的 misc_info.txt

例如,下面是一个 Broadcom 某平台上 super.img 的 misc_info.txt 内容:

$ cat out/target/product/inuvik/obj/PACKAGING/superimage_debug_intermediates/misc_info.txt
use_dynamic_partitions=true
lpmake=lpmake
build_super_partition=true
super_metadata_device=super
super_block_devices=super
super_super_device_size=3028287488
dynamic_partition_list= system vendor
super_partition_groups=bcm_ref
super_bcm_ref_group_size=1509949440
super_bcm_ref_partition_list=system vendor
ab_update=true
system_image=out/target/product/inuvik/system.img
vendor_image=out/target/product/inuvik/vendor.img

下面是谷歌 crosshatch 设备 super_empty.img 文件 misc_info.txt 的内容:

$ cat out/target/product/crosshatch/obj/PACKAGING/super_empty_intermediates/misc_info.txt
use_dynamic_partitions=true
dynamic_partition_retrofit=true
lpmake=lpmake
build_super_partition=true
build_retrofit_dynamic_partitions_ota_package=true
super_metadata_device=system
super_block_devices=system vendor product
super_system_device_size=2952790016
super_vendor_device_size=805306368
super_product_device_size=314572800
dynamic_partition_list= system vendor product
super_partition_groups=google_dynamic_partitions
super_google_dynamic_partitions_group_size=4069523456
super_google_dynamic_partitions_partition_list=system vendor product
ab_update=true

至于动态分区参数输出到 misc_info.txt 文件中以后,会怎么处理,将在 super.img 文件如何生成的一节展开。

4. 原生动态分区 super.img 的生成

还记得我在《Android 动态分区详解(一)》中提到,在 make 的 log 中搜索 lpmake 时发现有 3 个地方调用 lpmake 吗?

仔细阅读 build/make/core/Makefile,就会发现有两个地方去生成 super.img, 一个地方生成 super_empty.img, 在生成这些文件时通过脚本 build_super_image.py 调用 lpmake 去生成 metadata,所以总共调用了 3 次。

所幸的是,这三个地方都有注释,比较好找,分别是:

  • 目标: superimage_dist,注释: super partition image (dist)

    代码: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#4423

  • 目标: superimage, 注释: super partition image for development

    代码: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#4460

  • 目标: superimage_empty, 注释: super empty image

    代码: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#4514

4.1 dist 模式的 super.img

dist 模式下基于 target_files (例如: inuvik-target_files-eng.rg935739.zip) 的内容生成 super.img,其生成的文件位于:

out/target/product/inuvik/obj/PACKAGING/super.img_intermediates/super.img

主要由 superimage_dist 目标构成依赖关系路径: dist --> dist_files --> superimage_dist --> super.img

源码如下:

# -----------------------------------------------------------------
# super partition image (dist)

ifeq (true,$(PRODUCT_BUILD_SUPER_PARTITION))

# BOARD_SUPER_PARTITION_SIZE must be defined to build super image.
ifneq ($(BOARD_SUPER_PARTITION_SIZE),)

# Dump variables used by build_super_image.py.
define dump-super-image-info
  $(call dump-dynamic-partitions-info,$(1))
  $(if $(filter true,$(AB_OTA_UPDATER)), \
    echo "ab_update=true" >> $(1))
endef

ifneq (true,$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS))

# For real devices and for dist builds, build super image from target files to an intermediate directory.
INTERNAL_SUPERIMAGE_DIST_TARGET := $(call intermediates-dir-for,PACKAGING,super.img)/super.img
$(INTERNAL_SUPERIMAGE_DIST_TARGET): extracted_input_target_files := $(patsubst %.zip,%,$(BUILT_TARGET_FILES_PACKAGE))
$(INTERNAL_SUPERIMAGE_DIST_TARGET): $(LPMAKE) $(BUILT_TARGET_FILES_PACKAGE) $(BUILD_SUPER_IMAGE)
	$(call pretty,"Target super fs image from target files: $@")
	PATH=$(dir $(LPMAKE)):$$PATH \
	    $(BUILD_SUPER_IMAGE) -v $(extracted_input_target_files) $@

# Skip packing it in dist package because it is in update package.
ifneq (true,$(BOARD_SUPER_IMAGE_IN_UPDATE_PACKAGE))
$(call dist-for-goals,dist_files,$(INTERNAL_SUPERIMAGE_DIST_TARGET))
endif

.PHONY: superimage_dist
superimage_dist: $(INTERNAL_SUPERIMAGE_DIST_TARGET)

endif # PRODUCT_RETROFIT_DYNAMIC_PARTITIONS != "true"
endif # BOARD_SUPER_PARTITION_SIZE != ""
endif # PRODUCT_BUILD_SUPER_PARTITION == "true"

4.2 debug 模式的 super.img

dist 模式下基于 misc_info.txt的内容生成 super.img,其生成的文件位于:

out/target/product/inuvik/super.img

主要由 superimage 目标构成依赖关系路径: droid --> droidcore --> superimage --> super.img

示例 misc_info.txt 如下:

# misc_info.txt
$ cat out/target/product/inuvik/obj/PACKAGING/superimage_debug_intermediates/misc_info.txt
use_dynamic_partitions=true
lpmake=lpmake
build_super_partition=true
super_metadata_device=super
super_block_devices=super
super_super_device_size=3028287488
dynamic_partition_list= system vendor
super_partition_groups=bcm_ref
super_bcm_ref_group_size=1509949440
super_bcm_ref_partition_list=system vendor
ab_update=true
system_image=out/target/product/inuvik/system.img
vendor_image=out/target/product/inuvik/vendor.img

这里说成是 debug 模式是不准确的,主要是说想对于 release 而言,这里编译出来的镜像是开发时使用的,而通过 make dist得到的镜像,是 release 使用的。

生成 super.img 的源码如下:

# -----------------------------------------------------------------
# super partition image for development

ifeq (true,$(PRODUCT_BUILD_SUPER_PARTITION))
ifneq ($(BOARD_SUPER_PARTITION_SIZE),)
ifneq (true,$(PRODUCT_RETROFIT_DYNAMIC_PARTITIONS))

# Build super.img by using $(INSTALLED_*IMAGE_TARGET) to $(1)
# $(1): built image path
# $(2): misc_info.txt path; its contents should match expectation of build_super_image.py
define build-superimage-target
  mkdir -p $(dir $(2))
  rm -rf $(2)
  $(call dump-super-image-info,$(2))
  $(foreach p,$(BOARD_SUPER_PARTITION_PARTITION_LIST), \
    echo "$(p)_image=$(INSTALLED_$(call to-upper,$(p))IMAGE_TARGET)" >> $(2);)
  mkdir -p $(dir $(1))
  PATH=$(dir $(LPMAKE)):$$PATH \
    $(BUILD_SUPER_IMAGE) -v $(2) $(1)
endef

# out/target/product/inuvik/super.img
INSTALLED_SUPERIMAGE_TARGET := $(PRODUCT_OUT)/super.img
INSTALLED_SUPERIMAGE_DEPENDENCIES := $(LPMAKE) $(BUILD_SUPER_IMAGE) \
    $(foreach p, $(BOARD_SUPER_PARTITION_PARTITION_LIST), $(INSTALLED_$(call to-upper,$(p))IMAGE_TARGET))

# If BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT is set, super.img is built from images in the
# $(PRODUCT_OUT) directory, and is built to $(PRODUCT_OUT)/super.img. Also, it will
# be built for non-dist builds. This is useful for devices that uses super.img directly, e.g.
# virtual devices.
ifeq (true,$(BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT))
$(INSTALLED_SUPERIMAGE_TARGET): $(INSTALLED_SUPERIMAGE_DEPENDENCIES)
	$(call pretty,"Target super fs image for debug: $@")
	$(call build-superimage-target,$(INSTALLED_SUPERIMAGE_TARGET),\
	  $(call intermediates-dir-for,PACKAGING,superimage_debug)/misc_info.txt)

droidcore: $(INSTALLED_SUPERIMAGE_TARGET)

# For devices that uses super image directly, the superimage target points to the file in $(PRODUCT_OUT).
.PHONY: superimage
superimage: $(INSTALLED_SUPERIMAGE_TARGET)
endif # BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT

# Build $(PRODUCT_OUT)/super.img without dependencies.
.PHONY: superimage-nodeps supernod
superimage-nodeps supernod: intermediates :=
superimage-nodeps supernod: | $(INSTALLED_SUPERIMAGE_DEPENDENCIES)
	$(call pretty,"make $(INSTALLED_SUPERIMAGE_TARGET): ignoring dependencies")
	$(call build-superimage-target,$(INSTALLED_SUPERIMAGE_TARGET),\
	  $(call intermediates-dir-for,PACKAGING,superimage-nodeps)/misc_info.txt)

endif # PRODUCT_RETROFIT_DYNAMIC_PARTITIONS != "true"
endif # BOARD_SUPER_PARTITION_SIZE != ""
endif # PRODUCT_BUILD_SUPER_PARTITION == "true"

4.3 super_empty.img

基于 misc_info.txt 文件生成的 super_empty.img,其生成的文件位于:

out/target/product/inuvik/super_empty.img

主要通过 main.mk 中的 superimage_empty 目标形成依赖关系路径: dist --> dist_files --> superimage_dist --> super_empty.img

生成 super_empty.img 的源码如下:

# -----------------------------------------------------------------
# super empty image

ifeq (true,$(PRODUCT_BUILD_SUPER_PARTITION))
ifneq ($(BOARD_SUPER_PARTITION_SIZE),)

INSTALLED_SUPERIMAGE_EMPTY_TARGET := $(PRODUCT_OUT)/super_empty.img
$(INSTALLED_SUPERIMAGE_EMPTY_TARGET): intermediates := $(call intermediates-dir-for,PACKAGING,super_empty)
$(INSTALLED_SUPERIMAGE_EMPTY_TARGET): $(LPMAKE) $(BUILD_SUPER_IMAGE)
	$(call pretty,"Target empty super fs image: $@")
	mkdir -p $(intermediates)
	rm -rf $(intermediates)/misc_info.txt
	$(call dump-super-image-info,$(intermediates)/misc_info.txt)
	PATH=$(dir $(LPMAKE)):$$PATH \
	    $(BUILD_SUPER_IMAGE) -v $(intermediates)/misc_info.txt $@

$(call dist-for-goals,dist_files,$(INSTALLED_SUPERIMAGE_EMPTY_TARGET))

endif # BOARD_SUPER_PARTITION_SIZE != ""
endif # PRODUCT_BUILD_SUPER_PARTITION == "true"

4.4 build_super_image.py 脚本

仔细观察上面生成 super.imgsuper_empty.img 的代码,分别通过以下的命令实现:

  • dist 模式的 super.img

    $(call pretty,"Target super fs image from target files: $@")
    PATH=$(dir $(LPMAKE)):$$PATH \
    	    $(BUILD_SUPER_IMAGE) -v $(extracted_input_target_files) $@
    
  • debug 模式的 super.img

    # Build super.img by using $(INSTALLED_*IMAGE_TARGET) to $(1)
    # $(1): built image path
    # $(2): misc_info.txt path; its contents should match expectation of build_super_image.py
    define build-superimage-target
      mkdir -p $(dir $(2))
      rm -rf $(2)
      $(call dump-super-image-info,$(2))
      $(foreach p,$(BOARD_SUPER_PARTITION_PARTITION_LIST), \
        echo "$(p)_image=$(INSTALLED_$(call to-upper,$(p))IMAGE_TARGET)" >> $(2);)
      mkdir -p $(dir $(1))
      PATH=$(dir $(LPMAKE)):$$PATH \
        $(BUILD_SUPER_IMAGE) -v $(2) $(1)
    endef
    
    $(call pretty,"Target super fs image for debug: $@")
    $(call build-superimage-target,$(INSTALLED_SUPERIMAGE_TARGET),\
    	  $(call intermediates-dir-for,PACKAGING,superimage_debug)/misc_info.txt)
    
  • super_empty.img

    $(call pretty,"Target empty super fs image: $@")
    mkdir -p $(intermediates)
    rm -rf $(intermediates)/misc_info.txt
    $(call dump-super-image-info,$(intermediates)/misc_info.txt)
    PATH=$(dir $(LPMAKE)):$$PATH \
    	    $(BUILD_SUPER_IMAGE) -v $(intermediates)/misc_info.txt $@
    

其中,在 build/make/core/config.mk 中设置 BUILD_SUPER_IMAGE:

BUILD_SUPER_IMAGE := build/make/tools/releasetools/build_super_image.py

总体说来,脚本 build_super_image.py 接收两个参数,第一个是输入文件或目录,第二个是输出 super.img 的路径。

对于第一个参数,有三种情况:

  • 如果输入是 misc_info.txt 文件路径,则直接提取其中动态分区相关内容,传递给 lpmake 工具生成动态分区文件
  • 如果输入是目录,则查找其目录下的 META/misc_info.txt 文件,提取动态分区配置传递给 lpmake
  • 如果输入是 zip 文件,则先解压缩打临时目录总,再按照目录的情况进行处理

归根到底,build_super_image.py 会查找 misc_info.txt 文件(如果是目录,就查找目录下的文件;如果是 zip 文件,就解压缩后提取文件),并把其中动态分区参数传递给 lpmake 用来生成动态分区文件 super.img

如果动态分区中,某个分区还带有镜像文件的路径,就会将镜像文件放到 super.img 中;如果分区没有相应的文件,则最终的 super.img 就不会包含该分区的 image 内容。

默认编译生成的 super.img 只包含了 slot a 的镜像,另外一个 slot b 为空。可以使用 lpdump 分析 metadata,或者使用 lpunpack 解包查看。

5. 总结

动态分区参数有两类设置,一类是原生动态分区配置,一类是改造动态分区配置。具体需要设置的参数请查看本文 2.1 节。

动态分区虽然有两套参数,但最终这两套参数会合二为一成为同一套参数,并将这些参数设置输出到 misc_info.txt 中。两套参数的处理细节请参考本文的第 3 节。

编译系统调用 build_super_image.py 脚本读取 misc_info.txt中的动态分区配置参数,传递给 lpmake 工具。lpmake 根据动态分区参数中各分区的大小以及 image 路径,生成最终的 super.img(包括 metadata 和各分区 image)。

默认生成的 super.img 只包含了 slot a 的镜像,另外一个 slot b 为空,可以使用 lpdump 分析 metadata,或者使用 lpunpack 解包查看。

关于改造动态分区的 image 如何生成,值得另外开篇详细说明。

6. 其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

收钱码

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

公众号

  • 5
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Android动态分区是一种在存储设备上为应用程序和数据进行分区管理的技术。它允许用户将存储设备的空间灵活地划分为多个分区,并将不同的应用程序和数据存储在不同的分区中。 首先,Android动态分区可以提供更灵活的存储管理。通常情况下,存储设备被划分为内部存储和外部存储。内部存储用于安装应用程序和存储数据,而外部存储则用于存储媒体文件等。通过动态分区,用户可以根据自己的需求,自由地划分存储设备的空间,使得内部存储和外部存储可以互相调整大小,从而更好地满足用户的存储需求。 其次,动态分区可以提供更好的隔离保护。每个应用程序和数据都被存储在独立的分区中,这样可以避免不同应用程序之间的干扰和冲突。当一个应用程序出现问题时,只需要清除或重置相关的分区,而不会影响其他应用程序的正常运行。这种隔离性可以增加系统的稳定性和安全性。 另外,Android动态分区还可以优化存储空间的使用。系统会将一些常用的应用程序和数据放置在内部存储的快速区域,这样可以提高应用程序和数据的访问速度。同时,一些很少使用的应用程序和数据可以被移动到外部存储的缓慢区域,从而释放内部存储空间,让更常用的应用程序和数据能够更好地运行。 总的来说,Android动态分区是一种可以提供灵活存储管理、更好的隔离保护和优化存储空间使用的技术。它可以为用户提供更好的使用体验,并提高系统的稳定性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛奇看世界

一分也是爱~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值