4 minute read

动态分区分为原生动态分区和改造动态分区两种配置方式,其中包含开关配置和参数配置,以Android Q源码给出的原生示例为参考。

1. 动态分区配置

1.1. 原生动态分区配置

#文件device.mk
# 动态分区总开关
PRODUCT_USE_DYNAMIC_PARTITIONS := true

#文件BoardConfig.mk
# 设置 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

1.2. 改造动态分区配置

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

  • 注意:改造动态分区时,需要通过BOARD_SUPER_PARTITION_METADATA_DEVICE指定metadata存放的分区。因此,不仅可以将metadata数据和某个分区放到一起,例如原生动态分区中就是将metadata和super分区放到一起;也可以将 metadata数据单独放到某个分区中
#文件device.mk
# 改造(retrofit)动态分区总开关, 这里多了一个 retrofit,标明是升级改造设备
PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

#文件BoardConfig.mk
# 设置为所有动态分区内子分区大小的总和
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

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. 动态分区配置示例

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

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. bonito设备(Pixel 3a XL)配置示例(改造动态分区)

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

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

# 文件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

2.3. 模拟器cuttlefish配置示例(原生动态分区)

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

# device/google/cuttlefish/shared/BoardConfig.mk

# 使用自定义的TARGET_USE_DYNAMIC_PARTITIONS 作为开关,而不是PRODUCT_USE_DYNAMIC_PARTITIONS, 不过后者会根据前者设置为true
# ============= 参考device.mk配置
ifeq ($(TARGET_USE_DYNAMIC_PARTITIONS),true)
  PRODUCT_USE_DYNAMIC_PARTITIONS := true
  TARGET_BUILD_SYSTEM_ROOT_IMAGE := false
else
  TARGET_BUILD_SYSTEM_ROOT_IMAGE ?= true
endif
# ============================
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,即6G
  • 定义了一个动态分区组google_dynamic_partitions, 大小为6442450944, 包含三个子分区system vendor product
  • 指定了metadata数据存放的分区vda
  • BOARD_BUILD_SUPER_IMAGE_BY_DEFAULT := true指定了super.img由$(PRODUCT_OUT)目录下的文件创建,并输出到$(PRODUCT_OUT)/super.img

3. 动态分区参数检查

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

  • 开关参数检查,检查动态分区的配置开关是否冲突
  • 分区大小参数的检查,检查分区大小设置是否符合要求

3.1. 开关参数检查

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

检查重点:

  1. 改造动态分区开关和动态分区总开关必须同时设置
# 总开关
PRODUCT_USE_DYNAMIC_PARTITIONS := true
# 改造(retrofit)动态分区开关
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true
  1. 打开了动态分区之后,列表(system, vendor, odm, product, product_services)对应分区的以下SIZE配置不能同时设置
# (system, vendor, odm, product, product_services)
BOARD_$(device)IMAGE_PARTITION_SIZE
BOARD_$(device)IMAGE_PARTITION_RESERVED_SIZE
  1. 对每一个分组group,需要同时设置PARTITION_LIST和SIZE参数
BOARD_$(group)_PARTITION_LIST
BOARD_$(group)_SIZE
  1. 如果分组没有设置BOARD_$(group)_PARTITION_LIST, 则默认分组内没有分区
  2. 分组名BOARD_SUPER_PARTITION_GROUPS不能设置为列表(system vendor product product_services odm)中的名字
  3. 打开动态分区后,不需要再设置BOARD_BUILD_SYSTEM_ROOT_IMAGE = true

3.2. 分区大小限制

文件build/make/core/Makefile的3375~3485行,定义了多个宏对动态分区以及子分区的大小进行检查,主要是各分区或分组大小数值的计算和比较

Google官网的说明:

对于虚拟 A/B 启动设备,所有组的最大大小总和不得超过:
BOARD_SUPER_PARTITION_SIZE - 开销

对于 A/B 启动设备,所有组的最大大小总和必须为:
BOARD_SUPER_PARTITION_SIZE/ 2 - 开销

对于非 A/B 设备和改造的 A/B 设备,所有组的大小上限总和必须为:
BOARD_SUPER_PARTITION_SIZE - 开销

在构建时,更新组中每个分区的映像大小总和不得超过组的大小上限。
在计算时需要扣除开销,因为要考虑元数据、对齐等。合理的开销是 4 MiB,但您可以根据设备的需要选择更大的开销。

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

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

如果动态分区中定义了一个分区组,包含三个分区(system, vendor, product),对于A/B系统,分区组会有两个槽位,因此一共有6个子分区。

按中值计算,平均每个子分区对齐开销为0.5M,这样6个分区对齐,一共需要0.5M x 6 = 3M的总对齐开销。再加上元数据(metadata) 1M的开销,所以预估4M = 1M + 0.5M x 6的总开销是合理的


4. 动态分区参数结果查看

在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.imgsuper_empty.img时,将动态分区信息输出到各自对应的两处misc_info.txt

(1)例如,下面是一个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

(2)下面是谷歌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

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

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

  1. 目标: superimage_dist,注释: super partition image(dist)(代码: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#4423)

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

  1. 目标: superimage, 注释: super partition image for development(代码: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#4460)

debug模式下基于misc_info.txt的内容生成super.img,其生成的文件位于:out/target/product/inuvik/super.img

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

注意:相对于release而言,这里编译出来的镜像是开发时使用的,而通过make dist得到的镜像,是release使用的

  1. 目标: superimage_empty, 注释: super empty image(代码: http://aospxref.com/android-10.0.0_r47/xref/build/make/core/Makefile#4514)

基于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


6. 小结

  1. 动态分区参数有两类设置,一类是原生动态分区配置,一类是改造动态分区配置
  2. 动态分区虽然有两套参数,但最终这两套参数会合二为一成为同一套参数,并将这些参数设置输出到misc_info.txt中。两套参数的处理细节请参考文件Android Q源码build/make/core/config.mk的923~994行
  3. 编译系统调用build_super_image.py脚本读取misc_info.txt中的动态分区配置参数,传递给lpmake工具。lpmake根据动态分区参数中各分区的大小以及image路径,生成最终的super.img(包括metadata和各分区image)
  4. 默认生成的super.img只包含了slot a的镜像,另外一个slot b为空,可以使用lpdump分析metadata,或者使用lpunpack解包查看

7. 参考