动态分区是使用 Linux 内核中的 dm-linear device-mapper 模块实现的。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 Ext
- ODM
对于搭载 Android 10 的设备,内核命令行选项 androidboot.super_partition
必须为空,以使命令 sysprop ro.boot.super_partition
为空。
分区对齐
如果 super
分区未正确对齐,device-mapper 模块的运行效率可能会降低。super
分区必须与最小 I/O 请求大小保持一致,该大小由块层决定。 默认情况下,构建系统(通过生成 super
分区映像的 lpmake
)假设每个动态分区有 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
下面的设备示例将系统和产品服务放入 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
-
对于虚拟 A/B 启动设备,所有组的大小上限总和不得超过:
BOARD_SUPER_PARTITION_SIZE
- 开销
请参阅实现虚拟 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
标记仅用于区分系统是由内核装载还是在第一阶段装载init
(在 ramdisk 中)。
如果将 BOARD_BUILD_SYSTEM_ROOT_IMAGE
设置为 true
,则在 PRODUCT_USE_DYNAMIC_PARTITIONS
也为 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 -“Fix AvbSlotVerifyData->cmdline might be NULL”
如果使用链式分区,请包含一个额外的补丁程序:
- 49936b4c0109411fdd38bd4ba3a32a01c40439a9 -“libavb:支持在分区开头存放 vbmeta blob。”
内核命令行更改
必须在内核命令行中添加新参数 androidboot.boot_devices
。init
使用它来启用 /dev/block/by-name
符号链接。该参数应该是由 ueventd
创建的底层 by-name 符号链接(即 /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您可以在 BoardConfig.mk 文件中添加 bootconfig 参数,如下所示:
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 build 的开发者,adb remount
对快速迭代非常有用。动态分区给 adb remount
造成了问题,因为每个文件系统中都不再有空闲空间。为解决此问题,设备可以启用 overlayfs。只要超级分区中有空闲空间,adb remount
就会自动创建临时的动态分区,并使用 overlayfs 进行写入。该临时分区的名称为 scratch
,因此请勿将该名称用于其他分区。
要详细了解如何启用 overlayfs,请参阅 AOSP 中的 overlayfs 自述文件。
升级 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
分区,并且您希望将它们用作块存储设备来存储动态分区的 extent,并且其按名称符号链接标记为 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
映像,该映像可以直接刷写到 super 分区。除了 super
分区元数据之外,它还会自动捆绑逻辑分区的内容,这意味着它包含 system.img
、vendor.img
等。此映像可以直接刷写到 super
分区,而无需任何其他工具或使用 fastbootd。构建之后,super.img
会放置在 ${ANDROID_PRODUCT_OUT}
中。
对于搭载动态分区的 A/B 设备,super.img
包含 A 槽位中的映像。直接刷写 super 映像后,在重启设备之前将槽位 A 标记为可启动。
对于改造设备,make dist
会构建一组可以直接刷写到相应物理分区的 super_*.img
映像。例如,当 BOARD_SUPER_PARTITION_BLOCK_DEVICES
是系统供应商时,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
会变为活跃状态,值会开始更新。但是,由于属性触发器在 init
后才会进入活动状态,不能在初始启动阶段使用它们来处理 root
、system
或vendor
。您可能希望内核默认 read_ahead_kb
足够大,直到 init.rc
脚本可以替换 early-fs
(当多个守护进程和设施启动时)。因此,Google 建议您将 on property
功能与 sys.read_ahead_kb
等 init.rc
控制的属性搭配使用,以处理操作并防止出现竞态条件,如以下示例所示:
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}