动态分区是使用 Linux 内核中的 dm-linear 设备映射器模块实现的。super
分区包含元数据,其中列出了 super
中每个动态分区的名称和块范围。在第一阶段 init
期间,系统会对该元数据进行解析和验证,并创建虚拟块设备来表示每个动态分区。
应用 OTA 时,系统会自动根据需要创建、调整大小或删除动态分区。对于 A/B 设备,元数据有两份副本,更改仅应用于代表目标槽位的副本。
由于动态分区是在用户空间中实现的,因此引导加载程序所需的分区无法设为动态分区。例如,引导加载程序会读取 boot
、dtbo
和 vbmeta
,因此这些分区必须保留为物理分区。
每个动态分区都可以属于一个更新组。这些组会限制该组中的分区可以占用的最大空间。例如,system
和 vendor
可以属于一个组,该组会限制 system
和 vendor
的总大小。
在新设备上实现动态分区
本部分详细介绍了如何在搭载 Android 10 及更高版本的新设备上实现动态分区。要更新现有设备,请参阅升级 Android 设备。
分区变更
对于搭载 Android 10 的设备,请创建一个名为 super
的分区。super
分区在内部处理 A/B 槽位,因此 A/B 设备不需要单独的 super_a
和 super_b
分区。所有未被引导加载程序使用的只读 AOSP 分区都必须是动态分区,并且必须从 GUID 分区表 (GPT) 中移除。供应商专用分区不必是动态分区,可以放置在 GPT 中。
要估算 super
的大小,请将要从 GPT 中删除的分区的大小相加。对于 A/B 设备,这应包括两个槽位的大小。图 1 显示了转换为动态分区之前和之后的示例分区表。

受支持的动态分区包括:
- System
- Vendor
- Product
- System Ext
- ODM
对于搭载 Android 10 的设备,内核命令行选项 androidboot.super_partition
必须为空,以便命令系统属性 ro.boot.super_partition
为空。
分区对齐
如果 super
分区未正确对齐,则设备映射器模块的运行效率可能会降低。super
分区必须与块层确定的最小 I/O 请求大小对齐。默认情况下,构建系统(通过 lpmake
生成 super
分区映像)假定 1 MiB 对齐足以满足每个动态分区的需求。但是,供应商应确保 super
分区已正确对齐。
您可以通过检查 sysfs
来确定块设备的最小请求大小。例如:
# ls -l /dev/block/by-name/super lrwxrwxrwx 1 root root 16 1970-04-05 01:41 /dev/block/by-name/super -> /dev/block/sda17 # cat /sys/block/sda/queue/minimum_io_size 786432
您可以采用类似的方式验证 super
分区的对齐方式:
# cat /sys/block/sda/sda17/alignment_offset
对齐偏移量必须为 0。
设备配置变更
要启用动态分区,请在 device.mk
中添加以下标志:
PRODUCT_USE_DYNAMIC_PARTITIONS := true
主板配置变更
您需要设置 super
分区的大小:
BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>
在 A/B 设备上,如果动态分区映像的总大小超过 super
分区大小的一半,构建系统会抛出错误。
您可以按如下方式配置动态分区列表。对于使用更新组的设备,请在 BOARD_SUPER_PARTITION_GROUPS
变量中列出这些组。然后,每个组名称都具有 BOARD_group_SIZE
和 BOARD_group_PARTITION_LIST
变量。对于 A/B 设备,组的最大大小应仅涵盖一个槽位,因为组名称在内部带有槽位后缀。
以下示例设备将所有分区放入名为 example_dynamic_partitions
的组中:
BOARD_SUPER_PARTITION_GROUPS := example_dynamic_partitions BOARD_EXAMPLE_DYNAMIC_PARTITIONS_SIZE := 6442450944 BOARD_EXAMPLE_DYNAMIC_PARTITIONS_PARTITION_LIST := system vendor product
以下示例设备将 system 和 product 服务放入 group_foo
,并将 vendor
、product
和 odm
放入 group_bar
:
BOARD_SUPER_PARTITION_GROUPS := group_foo group_bar BOARD_GROUP_FOO_SIZE := 4831838208 BOARD_GROUP_FOO_PARTITION_LIST := system product_services BOARD_GROUP_BAR_SIZE := 1610612736 BOARD_GROUP_BAR_PARTITION_LIST := vendor product odm
- 对于 Virtual A/B 启动设备,所有组的最大大小之和必须至多为:
BOARD_SUPER_PARTITION_SIZE
- 开销
请参阅实现 Virtual A/B。 - 对于 A/B 启动设备,所有组的最大大小之和必须为:
BOARD_SUPER_PARTITION_SIZE
/ 2 - 开销 - 对于非 A/B 设备和改造 A/B 设备,所有组的最大大小之和必须为:
BOARD_SUPER_PARTITION_SIZE
- 开销 - 在构建时,更新组中每个分区的映像大小之和不得超过该组的最大大小。
- 开销是计算中所需的,用于考虑元数据、对齐方式等。合理的开销为 4 MiB,但您可以根据设备需要选择更大的开销。
调整动态分区大小
在动态分区之前,分区大小被过度分配,以确保它们有足够的空间用于未来的更新。实际大小按原样采用,大多数只读分区的文件系统中都有一些可用空间。在动态分区中,该可用空间不可用,可用于在 OTA 期间增大分区。务必确保分区未浪费空间,并且分配的大小尽可能小。
对于只读 ext4 映像,如果未指定硬编码分区大小,则构建系统会自动分配最小大小。构建系统会调整映像的大小,使文件系统的未使用空间尽可能少。这可确保设备不会浪费可用于 OTA 的空间。
此外,可以通过启用块级重复数据删除来进一步压缩 ext4 映像。要启用此功能,请使用以下配置:
BOARD_EXT4_SHARE_DUP_BLOCKS := true
如果不需要自动分配分区最小大小,则有两种方法可以控制分区大小。您可以指定最小可用空间,使用 BOARD_partitionIMAGE_PARTITION_RESERVED_SIZE
;或者您可以指定 BOARD_partitionIMAGE_PARTITION_SIZE
以强制动态分区达到特定大小。除非必要,否则不建议使用这两种方法。
例如:
BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800
这会强制 product.img
中的文件系统具有 50 MiB 的未使用空间。
System-as-root 变更
搭载 Android 10 的设备不得使用 system-as-root。
具有动态分区(无论是启动时还是改造动态分区)的设备不得使用 system-as-root。Linux 内核无法解释 super
分区,因此无法自行挂载 system
。system
现在由位于 ramdisk 中的第一阶段 init
挂载。
请勿设置 BOARD_BUILD_SYSTEM_ROOT_IMAGE
。在 Android 10 中,BOARD_BUILD_SYSTEM_ROOT_IMAGE
标志仅用于区分系统是由内核挂载还是由 ramdisk 中的第一阶段 init
挂载。
当 PRODUCT_USE_DYNAMIC_PARTITIONS
也为 true
时,将 BOARD_BUILD_SYSTEM_ROOT_IMAGE
设置为 true
会导致构建错误。
当 BOARD_USES_RECOVERY_AS_BOOT
设置为 true 时,恢复映像构建为 boot.img,其中包含恢复 ramdisk。以前,引导加载程序使用 skip_initramfs
内核命令行参数来决定要启动进入哪种模式。对于 Android 10 设备,引导加载程序不得将 skip_initramfs
传递到内核命令行。相反,引导加载程序应传递 androidboot.force_normal_boot=1
以跳过恢复并启动正常的 Android。搭载 Android 12 或更高版本的设备必须使用 bootconfig 来传递 androidboot.force_normal_boot=1
。
AVB 配置变更
使用 Android 验证启动 2.0 时,如果设备未使用链接分区描述符,则无需进行任何更改。但是,如果使用链接分区,并且其中一个经过验证的分区是动态分区,则需要进行更改。
以下是设备链接 system
和 vendor
分区的 vbmeta
的示例配置。
BOARD_AVB_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_SYSTEM_ALGORITHM := SHA256_RSA2048 BOARD_AVB_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_SYSTEM_ROLLBACK_INDEX_LOCATION := 1 BOARD_AVB_VENDOR_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_VENDOR_ALGORITHM := SHA256_RSA2048 BOARD_AVB_VENDOR_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_VENDOR_ROLLBACK_INDEX_LOCATION := 1
使用此配置,引导加载程序希望在 system
和 vendor
分区的末尾找到 vbmeta 页脚。由于这些分区对引导加载程序不再可见(它们位于 super
中),因此需要进行两项更改。
- 将
vbmeta_system
和vbmeta_vendor
分区添加到设备的分区表。对于 A/B 设备,添加vbmeta_system_a
、vbmeta_system_b
、vbmeta_vendor_a
和vbmeta_vendor_b
。如果添加这些分区中的一个或多个,它们的大小应与vbmeta
分区的大小相同。 - 通过添加
VBMETA_
重命名配置标志,并指定链接扩展到哪些分区:BOARD_AVB_VBMETA_SYSTEM := system BOARD_AVB_VBMETA_SYSTEM_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_VBMETA_SYSTEM_ALGORITHM := SHA256_RSA2048 BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_VBMETA_SYSTEM_ROLLBACK_INDEX_LOCATION := 1 BOARD_AVB_VBMETA_VENDOR := vendor BOARD_AVB_VBMETA_VENDOR_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem BOARD_AVB_VBMETA_VENDOR_ALGORITHM := SHA256_RSA2048 BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX := $(PLATFORM_SECURITY_PATCH_TIMESTAMP) BOARD_AVB_VBMETA_VENDOR_ROLLBACK_INDEX_LOCATION := 1
设备可以使用这些分区中的一个、两个或全部都不使用。仅当链接到逻辑分区时才需要进行更改。
AVB 引导加载程序变更
如果引导加载程序嵌入了 libavb,请包含以下补丁:
- 818cf56740775446285466eda984acedd4baeac0 — “libavb:仅在 cmdline 需要时查询分区 GUID。”
- 5abd6bc2578968d24406d834471adfd995a0c2e9 — “允许 system 分区不存在”
- 9ba3b6613b4e5130fa01a11d984c6b5f0eb3af05 — “修复 AvbSlotVerifyData->cmdline 可能为 NULL”
如果使用链接分区,请包含其他补丁:
- 49936b4c0109411fdd38bd4ba3a32a01c40439a9 — “libavb:支持分区开头的 vbmeta Blob。”
内核命令行变更
必须将新参数 androidboot.boot_devices
添加到内核命令行。init
使用此参数来启用 /dev/block/by-name
符号链接。它应该是 ueventd
创建的底层按名称符号链接的设备路径组件,即 /dev/block/platform/device-path/by-name/partition-name
。搭载 Android 12 或更高版本的设备必须使用 bootconfig 将 androidboot.boot_devices
传递给 init
。
例如,如果按名称的超级分区符号链接是 /dev/block/platform/soc/100000.ufshc/by-name/super
,您可以在 BoardConfig.mk 文件中添加命令行参数,如下所示:
BOARD_KERNEL_CMDLINE += androidboot.boot_devices=soc/100000.ufshc
BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc
fstab 变更
设备树和设备树叠加层不得包含 fstab 条目。请使用将成为 ramdisk 一部分的 fstab 文件。
必须对逻辑分区的 fstab 文件进行更改:
- fs_mgr 标志字段必须包含
logical
标志和 Android 10 中引入的first_stage_mount
标志,后者表示要在第一阶段挂载分区。 - 分区可以将
avb=vbmeta partition name
指定为fs_mgr
标志,然后指定的vbmeta
分区由第一阶段init
初始化,然后再尝试挂载任何设备。 dev
字段必须是分区名称。
以下 fstab 条目根据上述规则将 system、vendor 和 product 设置为逻辑分区。
#<dev> <mnt_point> <type> <mnt_flags options> <fs_mgr_flags> system /system ext4 ro,barrier=1 wait,slotselect,avb=vbmeta,logical,first_stage_mount vendor /vendor ext4 ro,barrier=1 wait,slotselect,avb,logical,first_stage_mount product /product ext4 ro,barrier=1 wait,slotselect,avb,logical,first_stage_mount
将 fstab 文件复制到第一阶段 ramdisk 中。
SELinux 变更
超级分区块设备必须标记为 super_block_device
标签。例如,如果按名称的超级分区符号链接是 /dev/block/platform/soc/100000.ufshc/by-name/super
,请将以下行添加到 file_contexts
:
/dev/block/platform/soc/10000\.ufshc/by-name/super u:object_r:super_block_device:s0
fastbootd
引导加载程序(或任何非用户空间刷写工具)不理解动态分区,因此无法刷写它们。为了解决这个问题,设备必须使用 fastboot 协议的用户空间实现,称为 fastbootd。
如需详细了解如何实现 fastbootd,请参阅将 Fastboot 移至用户空间。
adb remount
对于使用 eng 或 userdebug 构建版本的开发者,adb remount
对于快速迭代非常有用。动态分区给 adb remount
带来了一个问题,因为每个文件系统内不再有可用空间。为了解决这个问题,设备可以启用 overlayfs。只要超级分区内有可用空间,adb remount
就会自动创建一个临时动态分区,并使用 overlayfs 进行写入。临时分区名为 scratch
,因此请勿将此名称用于其他分区。
如需详细了解如何启用 overlayfs,请参阅 AOSP 中的 overlayfs README。
升级 Android 设备
如果您将设备升级到 Android 10,并且想要在 OTA 中包含动态分区支持,则无需更改内置分区表。需要进行一些额外的配置。
设备配置变更
要改造动态分区,请在 device.mk
中添加以下标志:
PRODUCT_USE_DYNAMIC_PARTITIONS := true PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true
主板配置变更
您需要设置以下主板变量:
- 将
BOARD_SUPER_PARTITION_BLOCK_DEVICES
设置为用于存储动态分区区段的块设备列表。这是设备上现有物理分区的名称列表。 - 将
BOARD_SUPER_PARTITION_partition_DEVICE_SIZE
分别设置为BOARD_SUPER_PARTITION_BLOCK_DEVICES
中每个块设备的大小。这是设备上现有物理分区的大小列表。这通常是现有主板配置中的BOARD_partitionIMAGE_PARTITION_SIZE
。 - 取消设置
BOARD_SUPER_PARTITION_BLOCK_DEVICES
中所有分区的现有BOARD_partitionIMAGE_PARTITION_SIZE
。 - 将
BOARD_SUPER_PARTITION_SIZE
设置为BOARD_SUPER_PARTITION_partition_DEVICE_SIZE
的总和。 - 将
BOARD_SUPER_PARTITION_METADATA_DEVICE
设置为存储动态分区元数据的块设备。它必须是BOARD_SUPER_PARTITION_BLOCK_DEVICES
之一。通常,这设置为system
。 - 分别设置
BOARD_SUPER_PARTITION_GROUPS
、BOARD_group_SIZE
和BOARD_group_PARTITION_LIST
。有关详细信息,请参阅新设备上的主板配置变更。
例如,如果设备已经具有 system 和 vendor 分区,并且您想要将它们转换为动态分区并在更新期间添加新的 product 分区,请设置此主板配置:
BOARD_SUPER_PARTITION_BLOCK_DEVICES := system vendor BOARD_SUPER_PARTITION_METADATA_DEVICE := system # Rename BOARD_SYSTEMIMAGE_PARTITION_SIZE to BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE. BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE := <size-in-bytes> # Rename BOARD_VENDORIMAGE_PARTITION_SIZE to BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE := <size-in-bytes> # This is BOARD_SUPER_PARTITION_SYSTEM_DEVICE_SIZE + BOARD_SUPER_PARTITION_VENDOR_DEVICE_SIZE BOARD_SUPER_PARTITION_SIZE := <size-in-bytes> # Configuration for dynamic partitions. For example: BOARD_SUPER_PARTITION_GROUPS := group_foo BOARD_GROUP_FOO_SIZE := <size-in-bytes> BOARD_GROUP_FOO_PARTITION_LIST := system vendor product
SELinux 变更
超级分区块设备必须标记有属性 super_block_device_type
。例如,如果设备已经具有 system
和 vendor
分区,您想要将它们用作块设备来存储动态分区的区段,并且它们的按名称符号链接标记为 system_block_device
:
/dev/block/platform/soc/10000\.ufshc/by-name/system u:object_r:system_block_device:s0 /dev/block/platform/soc/10000\.ufshc/by-name/vendor u:object_r:system_block_device:s0
然后,将以下行添加到 device.te
:
typeattribute system_block_device super_block_device_type;
对于其他配置,请参阅在新设备上实现动态分区。
有关改造更新的更多信息,请参阅没有动态分区的 A/B 设备的 OTA。
工厂映像
对于启动时支持动态分区的设备,请避免使用用户空间 fastboot 来刷写工厂映像,因为启动到用户空间比其他刷写方法慢。
为了解决这个问题,make dist
现在构建了一个额外的 super.img
映像,可以直接刷写到超级分区。它会自动捆绑逻辑分区的内容,这意味着它包含 system.img
、vendor.img
等,以及 super
分区元数据。此映像可以直接刷写到 super
分区,而无需任何其他工具或使用 fastbootd。构建完成后,super.img
将放置在 ${ANDROID_PRODUCT_OUT}
中。
对于启动时具有动态分区的 A/B 设备,super.img
包含 A 槽位中的映像。直接刷写超级映像后,请在重启设备之前将槽位 A 标记为可启动。
对于改造设备,当 BOARD_SUPER_PARTITION_BLOCK_DEVICES
是 system vendor 时,make dist
构建一组 super_*.img
映像,可以直接刷写到相应的物理分区。例如,make dist
构建 super_system.img
和 super_vendor.img
。这些映像放置在 target_files.zip
的 OTA 文件夹中。
设备映射器存储设备调整
动态分区容纳许多不确定的设备映射器对象。这些对象可能并非都按预期实例化,因此您必须跟踪所有挂载,并使用其底层存储设备更新所有关联分区的 Android 属性。
init
内的机制跟踪挂载并异步更新 Android 属性。完成此操作所需的时间无法保证在特定时间段内,因此您必须为所有 on property
触发器提供足够的反应时间。属性为 dev.mnt.blk.<partition>
,其中 <partition>
为 root
、system
、data
或 vendor
等。每个属性都与基本存储设备名称相关联,如以下示例所示:
taimen:/ % getprop | grep dev.mnt.blk [dev.mnt.blk.data]: [sda] [dev.mnt.blk.firmware]: [sde] [dev.mnt.blk.metadata]: [sde] [dev.mnt.blk.persist]: [sda] [dev.mnt.blk.root]: [dm-0] [dev.mnt.blk.vendor]: [dm-1] blueline:/ $ getprop | grep dev.mnt.blk [dev.mnt.blk.data]: [dm-4] [dev.mnt.blk.metadata]: [sda] [dev.mnt.blk.mnt.scratch]: [sda] [dev.mnt.blk.mnt.vendor.persist]: [sdf] [dev.mnt.blk.product]: [dm-2] [dev.mnt.blk.root]: [dm-0] [dev.mnt.blk.system_ext]: [dm-3] [dev.mnt.blk.vendor]: [dm-1] [dev.mnt.blk.vendor.firmware_mnt]: [sda]
init.rc
语言允许将 Android 属性扩展为规则的一部分,并且平台可以根据需要使用如下命令调整存储设备:
write /sys/block/${dev.mnt.blk.root}/queue/read_ahead_kb 128 write /sys/block/${dev.mnt.blk.data}/queue/read_ahead_kb 128
一旦命令处理在第二阶段 init
中开始,epoll loop
就会变为活动状态,并且值开始更新。但是,由于属性触发器在 late-init
之前处于非活动状态,因此它们无法在初始启动阶段用于处理 root
、system
或 vendor
。您可能希望内核默认 read_ahead_kb
足以满足需求,直到 init.rc
脚本可以在 early-fs
中覆盖(当各种守护程序和工具开始运行时)。因此,Google 建议您使用 on property
功能,并结合 init.rc
控制的属性(如 sys.read_ahead_kb
)来处理操作的计时并防止竞争情况,如以下示例所示:
on property:dev.mnt.blk.root=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.root}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.system=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.system}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.vendor=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.vendor}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.product=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.system_ext}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.oem=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.oem}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on property:dev.mnt.blk.data=* && property:sys.read_ahead_kb=* write /sys/block/${dev.mnt.blk.data}/queue/read_ahead_kb ${sys.read_ahead_kb:-2048} on early-fs: setprop sys.read_ahead_kb ${ro.read_ahead_kb.boot:-2048} on property:sys.boot_completed=1 setprop sys.read_ahead_kb ${ro.read_ahead_kb.bootcomplete:-128}