前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 动态分区概念了解

Android 动态分区概念了解

作者头像
wizzie
发布2022-09-28 08:26:28
2K1
发布2022-09-28 08:26:28
举报
文章被收录于专栏:Android Framework
My Table

动态分区是Android的用户空间分区系统,从Android Q开始引入这个动态分区super的概念。使用此分区系统,可以在无线下载 (OTA) 更新期间创建、销毁分区或者调整分区大小。借助动态分区,供应商无需担心各个分区(例如 system、vendor 和 product)的大小。取而代之的是,设备分配一个super分区,其中的子分区可动态地调整大小。单个分区镜像不再需要为将来的 OTA 预留空间。相反,super 中剩余的可用空间还可用于所有动态分区。

1. 概述

从Android Q引入动态分区,到Android R/S在动态分区之上增加虚拟分区管理, OTA升级时需要对分区变更进行处理

动态分区管理的本质就是对分区数据的增删改查操作,操作的对象就是动态分区描述数据metadata。


2. 实现动态分区

动态分区是使用Linux内核中的dm-linear device-mapper模块实现的。 super分区包含列出了super中每个动态分区的名称和块范围的元数据。

在第一阶段init期间,系统会解析并验证此元数据,并创建虚拟块设备来表示每个动态分区。

执行OTA时,系统会根据需要自动创建/删除动态分区,或者调整动态分区的大小。

若是A/B设备,将存在两个元数据副本,而更改仅会应用到表示目标槽位的副本。

由于动态分区是在用户空间中实现的,因此引导加载程序所需的分区不能是动态的。 例如,引导加载程序会读取 boot、dtbo 和 vbmeta,因此这些分区必须仍保持为物理分区。

每个动态分区都可以属于一个“更新组”。 这些组会限制组内的分区可以使用的最大空间

例如,system和vendor可以属于一个限制了system和vendor总大小的组


2.1. Linux device mapper驱动

Device mapper 内核中各对象的层次关系:

  • 虚拟设备Mapped Device基于驱动Target Driver和内部的映射表Mapping Table来实现
  • 一个虚拟设备Mapped Device可以由一个或多个Target Device映射组成,参与映射的Target Device本身也可能是虚拟设备

所以,一个设备可能是真实的,也可能是虚拟的。对虚拟设备的访问会被Target Driver拦截,然后通过Mapping Table转发给另外一个设备。

例如,在Android Q上,对system_a或vendor_a分区的访问被驱动拦截并转发为对super分区某个区域的访问。而这里的拦截以及转发对用户是透明的,用户根本不知道,也不需要知道他最终访问的到底是哪个设备。


2.2. 分区布局更改

对于搭载Android 10及之后版本的设备,创建名为super的分区。super分区会在内部处理A/B槽位,因此A/B设备不需要单独的super_a和super_b分区。

引导加载程序未使用的所有只读AOSP分区都必须是动态的,并且必须从GUID分区表 (GPT) 中移除。 供应商专用分区则可以不是动态的,并且可以放在GPT中

注意:应避免将 userdata 或任何其他永久性读写分区放在super中

如需估算super的大小,请加总要从GPT中删除的分区的大小。对于A/B设备,这应包括两个槽位的大小。

下图显示了转换为动态分区前后的分区表示例:

新的super分区内部包含了多个原来的分区,而识别这些内部分区的方法就是:引入数据来描述super分区的布局。

在super分区开头存储用于描述分区布局的metadata,和磁盘开头存储用于描述磁盘分区布局的gpt数据的道理一样。系统加载动态分区时读取metadata,对其进行解析,在内存中建立super分区布局描述的LpMetadata结构体,就知道内部的各个分区都位于哪个地方。

换句话说,metadata就是LpMetadata结构在super分区上的物理存储数据,而LpMetadatametadata在内存中的数据结构。LpMetadata中的信息会被转换成device mapper中的映射表Mappting Table,基于这个映射表,super分区对应设备/dev/block/by-name/super的不同部分被映射成多个虚拟设备,如/dev/block/mapper/system_a,/dev/block/mapper/vendor_a


2.2.1. 支持的动态分区

支持的动态分区包括:

  • 系统
  • Vendor
  • Product
  • System Ext
  • ODM

对于搭载 Android 10 的设备,内核命令行选项androidboot.super_partition必须为空,以使命令sysprop ro.boot.super_partition为空。


2.2.2. metadata数据

宏观上看下物理存储的metadata:

对metadata,可以分层3个层次,见图:

  1. 第1层,super分区(宏观)
  • super分区头部存放描述分区内部布局的metadata数据
  • metadata数据之后1MB对齐开始的地方依次存放两组槽位(slot)的分区数据
  1. 第2层,metadata数据
  • metadata 数据开始前预留了4K的空间,所以metadata数据从4K的偏移位置开始(所以metadata位于super分区的4KB~1MB范围内)
  • metadata 数据由 Geometry 和 Metadata 两部分组成
    • Geometry大小为4K,数据除自身外,还有个一个备份,紧挨着Geometry存放
    • Metadata大小64K,按槽位(slot)存放,每个槽位有一份Metadata数据,所有槽位的Metadata数据结束后开始存放其备份数据
    • 分区加载时,根据分区对应槽位,读取相应槽位的Metadata和其备份数据
  1. 第 3 层,Geometry和Metadata(微观)
  • Geometry内部是大小为58字节的LpMetadataGeometry结构数据,填充到4K大小
  • Metadata内部包含LpMetadataHeader,LpMetadataPartition,LpMetadataExtent,LpMetadataPartitionGroup,LpMetadataBlockDevice等数据结构,填充到64K大小
2.2.2.1. metadata数据小结
  • metadata包括Geometry和Metadata两个部分,每个部分都有自己的Primary和Bakcup两组相同的数据,存放顺序是Geometry(Primary), Geometry(Backup), Metadata(Primary), Metadata(Backup)
  • 对于Metadata,其内部又有3份数据,分别对应于Slot 0, Slot 1, Slot 2。当 Slot 0启动时,读取Slot 0对应的Metadata, 当Slot 1启动时,读取Slot 1对应的Metadata
  • OTA升级时,如果当前在Slot 0(或者说slot A)上运行,要更新Slot 1分区,则在更新之前Slot 1前会更新Slot 1对应的Metadata以反应Slot 1的实际布局,反之亦然
2.2.2.2. 源码定义

动态分区的核心数据结构主要定义在以下文件中:

  • system/core/fs_mgr/liblp/include/liblp/metadata_format.h
  • system/core/fs_mgr/liblp/include/liblp/liblp.h

2.3. 分区对齐

如果super分区未正确对齐,device-mapper模块的运行效率可能会降低。super分区必须与最小I/O请求大小保持一致,该大小由块层决定。

默认情况下,构建系统(通过生成super分区镜像的lpmake)假设每个动态分区有1 MiB的对齐程度即已足够。

不过,供应商应确保super分区正确对齐。

  1. 可以通过检查sysfs来确定块设备的最小请求大小。例如:
代码语言:javascript
复制
# 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
  1. 可以通过类似的方式验证 super 分区的对齐:(对齐偏移必须为0
代码语言:javascript
复制
# cat /sys/block/sda/sda17/alignment_offset

2.4. 设备配置更改(启动动态分区)

在device.mk添加标记:

PRODUCT_USE_DYNAMIC_PARTITIONS := true

例如在Android Q源码:

代码语言:javascript
复制
//android/device/google/cuttlefish/shared/phone/device.mk
TARGET_USE_DYNAMIC_PARTITIONS ?= true
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

2.5. 板级配置更改(配置super分区大小)

在BoardConfig.mk添加修改,设置super分区的大小:(在A/B设备上,如果动态分区镜像的总大小超过super分区大小的一半,构建系统就会发生错误)

BOARD_SUPER_PARTITION_SIZE := <size-in-bytes>

可以按以下方式配置动态分区列表。 对于使用更新组的设备,在BOARD_SUPER_PARTITION_GROUPS变量中列出这些组。

然后,每个组名称都有一个BOARD_group_SIZEBOARD_group_PARTITION_LIST变量。 对于A/B设备,组的大小上限应仅包含一个槽位,因为组名在内部以槽位为后缀。

以下示例设备将所有分区放入名为example_dynamic_partitions的组中:

代码语言:javascript
复制
//例如1:将所有分区放入名为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

//例如2:将系统和产品服务放入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

例如在Android Q源码:

代码语言:javascript
复制
//android/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
  # No dynamic partitions support; we must specify maximum sizes
  BOARD_SYSTEMIMAGE_PARTITION_SIZE := 4294967296 # 4 GB
  BOARD_VENDORIMAGE_PARTITION_SIZE := 536870912 # 512MB
  BOARD_PRODUCTIMAGE_PARTITION_SIZE := 1610612736 # 1.5GB
  TARGET_NO_RECOVERY ?= true
endif

2.5.1. 注意点

  1. 请勿选择可能会与构建变量冲突的名称,例如super和super_partition。为了便于查看,请选择诸如foo_dp、foo_dynamic_partitions、foo_dp_group、super_foo或group_foo这样的名称,其中foo表示更新指定组的操作方的名称)。例如,group_OEM或group_VENDOR
  2. 一个设备上可能有一个或多个更新组
  3. 对于虚拟A/B启动设备,所有组的最大大小总和不得超过:BOARD_SUPER_PARTITION_SIZE - 开销
  4. 对于A/B启动设备,所有组的最大大小总和必须为:BOARD_SUPER_PARTITION_SIZE / 2 - 开销
  5. 对于非A/B设备和改造的A/B设备,所有组的大小上限总和必须为:BOARD_SUPER_PARTITION_SIZE - 开销
  6. 在构建时,更新组中每个分区的镜像大小总和不得超过组的大小上限
  7. 在计算时需要扣除开销,因为要考虑元数据、对齐等。合理的开销是4MiB,但可以根据设备的需要选择更大的开销

2.6. 调整动态分区的大小

在采用动态分区之前,会为分区分配富余的空间,以确保它们有足够的空间满足将来的更新。分区会按分配的大小占用实际空间,大多数只读分区的文件系统中都会有一些空闲空间。

在动态分区中,这些空闲空间不可用,并且可以用于在OTA期间增大分区。 需要确保分区没有浪费空间并且尽可能将其分配给最小大小。

对于只读的ext4镜像,如果未指定硬编码分区大小,则构建系统会自动分配最小的空间。构建系统会适配镜像,以尽可能减少文件系统中的未使用空间。这样可以确保设备不会浪费可用于OTA的空间。

此外,通过启用块级重复信息删除,可以进一步压缩ext4镜像。 要启用此功能,请使用以下配置:

BOARD_EXT4_SHARE_DUP_BLOCKS := true

如果不希望自动分配最小分区大小,则可以通过两种方法来控制分区大小。

可以使用BOARD_partitionIMAGE_PARTITION_RESERVED_SIZE指定最小可用空间,

也可以指定BOARD_partitionIMAGE_PARTITION_SIZE以强制将动态分区设为特定大小。除非必要,这两种方法都不建议使用。

例如,这样会强制product.img的文件系统使用50MB的未使用空间:

BOARD_PRODUCTIMAGE_PARTITION_RESERVED_SIZE := 52428800 //50MB

注意:构建系统只能最佳适配ext4文件系统镜像,且只能适配构建过程中生成的镜像。若是预构建的镜像或使用其他文件系统的镜像,则必须手动确保文件系统没有浪费磁盘空间。


2.7. 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_IMAG=true会导致PRODUCT_USES_DYNAMIC_PARTITIONS=true

BOARD_USES_RECOVERY_AS_BOOT设置为true时,recovery.img将被构建为boot.img,其中包含恢复的ramdisk。 以前,引导加载程序使用skip_initramfs内核命令行参数来决定启动到哪种模式。

对于搭载Android 10的设备,引导加载程序不得向内核命令行传递skip_initramfs。

引导加载程序应传递androidboot.force_normal_boot=1来跳过恢复并正常启动 Android。

发布时搭载Android 12或更高版本的设备必须使用bootconfig传递androidboot.force_normal_boot=1


2.8. AVB配置更改

警告:动态分区需要使用Android启动时验证 (AVB),但不能与启动时验证1.0搭配使用

使用 Android 启动时验证 2.0 时,如果设备未使用链式分区描述符,则不需要进行更改。但如果使用了链式分区,并且其中一个已验证分区是动态分区,则需要进行更改。

下面的设备配置示例链接system和vendor分区所对应的vbmeta。

代码语言:javascript
复制
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_来重命名配置标记,并指定链接扩展到的分区:

代码语言:javascript
复制
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

2.9. 内核命令行更改

必须在内核命令行中添加新参数androidboot.boot_devices。init使用它来启用/dev/block/by-name符号链接。

该参数应该是由ueventd创建的底层by-name符号链接(即/dev/block/platform/device-path/by-name/partition-name)的设备路径组件。发布时搭载Android 12或更高版本的设备必须使用bootconfigandroidboot.boot_devices传递给init。

例如,如果super按分区的符号链接为/dev/block/platform/soc/100000.ufshc/by-name/super

  1. 可以在BoardConfig.mk文件中添加命令行参数,如下所示:

BOARD_KERNEL_CMDLINE += androidboot.boot_devices=soc/100000.ufshc

  1. 可以在 BoardConfig.mk 文件中添加 bootconfig 参数,如下所示:

BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc


2.10. fatab更改

设备树和设备树叠加层不得包含 fstab 条目。 使用将成为 ramdisk 一部分的 fstab 文件。

必须对逻辑分区的 fstab 文件进行以下更改:

fs_mgr标志字段必须包含logical标志和first_stage_mountAndroid 10 中引入的 Android 标记标志,用于指示在第一阶段装载分区。

分区可以将 avb=vbmeta partition name 指定为 fs_mgr 标志,然后指定的 vbmeta 分区先由第一阶段 init 初始化,然后再尝试装载任何磁盘设备。

dev字段必须是分区名称。

以下fstab条目按照上述规则设置system、vendor和product逻辑分区。

将fstab文件复制到第一阶段ramdisk。

代码语言:javascript
复制
#<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

注意:对于非A/B设备,请勿包含slotselect


2.11. selinux更改

super分区块设备必须使用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


2.12. fastbootd

引导加载程序(或任何非用户空间刷写工具)无法理解动态分区,因此无法对其进行刷写。为解决此问题,设备必须使用fastboot协议的用户空间实现,称为fastbootd。


2.13. adb remount

对于使用eng或userdebug build的开发者,adb remount对快速迭代非常有用。

动态分区给adb remount造成了问题,因为每个文件系统中都不再有空闲空间。为解决此问题,设备可以启用overlayfs。

只要super分区中有空闲空间,adb remount就会自动创建临时的动态分区,并使用overlayfs进行写入。该临时分区的名称为scratch,因此请勿将该名称用于其他分区。


3. 升级Android设备

如果您想将设备升级到 Android 10,并且希望在 OTA 中包含动态分区支持,则不需要更改内置分区表。需要进行一些额外的配置。

3.1. device.mk更改

代码语言:javascript
复制
//device.mk
PRODUCT_USE_DYNAMIC_PARTITIONS := true
PRODUCT_RETROFIT_DYNAMIC_PARTITIONS := true

3.2. BoardConfig.mk

  • 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_GROUPSBOARD_group_SIZEBOARD_group_PARTITION_LIST

例如,如果设备已经有system和vendor分区,并且希望在更新期间将它们转换为动态分区并添加新的product分区,请设置以下板级配置:

代码语言:javascript
复制
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

3.3. selinux更改

必须使用super_block_device_type中下载Google健身应用。

例如,如果设备已经system和vendor分区,希望将它们用作块设备来存储动态分区的范围,并且其按名称符号链接标记为system_block_device

代码语言:javascript
复制
/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;


3.4. 出厂镜像

对于搭载动态分区支持的设备,请勿使用用户空间 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。这些j镜像放置在target_files.zip的OTA文件夹中。


3.5. 设备映射器(基于存储设备)

动态分区具有许多不确定性的设备映射器对象。这些内容可能无法按预期方式实例化,因此,您必须跟踪所有装载操作,并使用其底层存储设备更新所有关联分区的Android属性。

init内的机制会跟踪装载并异步更新Android属性。我们不保证在特定时间段内花费的时间,因此您必须为所有on property触发器提供足够的时间以作出回应。

特性为dev.mnt.blk.<partition>,例如<partition>为root、system、data或vendor。每个属性都与基本存储设备名称相关联,如以下示例所示:

代码语言:javascript
复制
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属性,并且可以根据需要通过平台调整存储设备:

代码语言:javascript
复制
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控制的属性搭配使用,以处理操作并防止出现竞态条件,如以下示例所示:

代码语言:javascript
复制
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}

4. 具有动态分区的A/B设备的OTA

Android 10支持动态分区,这是一种可以在无线下载 (OTA)更新期间创建和销毁分区以及调整分区大小的用户空间分区系统。

设备上有一个super分区。此分区没有槽后缀。块设备必须与fstab中/miscblk_device条目一起存在。

例如,如果fstab文件列出了以下内容:

/dev/block/bootdevice/by-name/misc /misc # Other fields

然后,禁止的设备super必须位于/dev/block/bootdevice/by-name/super但super该分区不需要列在fstab文件。

super分区中有两个元数据插槽,编号分别为0和1,对应于分区的A/B插槽。

分区属于更新组。

下面的示例显示了设备上的元数据。

  • 元数据0
    • 群组foo_a
    • 分区system_a
    • 分区product_services_a
    • 其他由Foo更新的分区
    • 群组bar_a
    • 分区vendor_a
    • 分区product_a
    • 其他由Bar更新的分区
    • “foo_b”组(从上一次升级中剩余)
    • “bar_b”组(从上一次升级中剩余)
  • 元数据 1
    • “foo_a”组(从上一次升级中剩余)
    • “bar_a”组(从上一次升级中剩余)
    • 群组foo_b
    • 分区system_b
    • 分区product_services_b
    • 其他由Foo更新的分区
    • 群组bar_b
    • 分区vendor_b
    • 分区product_b
    • 其他由Bar更新的分区

4.1. lpdump工具

可以使用lpdump工具(system/extras/partition_tools下的源代码)转储设备上的元数据。例如:

代码语言:javascript
复制
lpdump --slot 0 /dev/block/bootdevice/by-name/super
lpdump --slot 1 /dev/block/bootdevice/by-name/super

4.2. 更新流程

4.2.1. 初始化super分区元数据

在本文档中,我们将元数据槽位称为元数据S源)和元数据T目标)。类似地,将分区命名为system_s、vendor_t等。

在升级之前,元数据S包含正在使用的动态分区(通常是system_s、vendor_s、product_s等)的信息。系统会在更新期间读取这些分区的范围,因此它们无法删除。

  1. 从元数据S加载来源动态分区的范围。假设M是加载的元数据。
  2. 从M中移除目标组和分区(例如 foo_t、bar_t),以便M仅包含带有_s后缀的分区和组。
  3. 根据更新清单中的dynamic_partition_metadata字段添加目标组和分区。可在new_partition_info中找到每个分区的大小。
  4. 将元数据M写入元数据T
  5. 将设备映射器上添加的分区映射为可写入

4.2.2. 在块设备上应用更新

  1. 根据需要在设备映射器上将源分区映射为只读分区。这对于旁加载来说是必要的,因为在更新之前未映射来源分区。
  2. 将完整或增量更新应用到目标插槽上的所有块设备。
  3. 装载分区以运行安装后脚本,然后再卸载分区。

4.2.3. 取消映射目标分区

注意:来源和目标动态分区的范围跨越整个super分区。 因此,必须加载来源元数据,以便这些范围不会用作任何目标分区的一部分

在更新之前和之后,以下系统属性应具有相应的值:

代码语言:javascript
复制
ro.boot.dynamic_partitions=true
ro.boot.dynamic_partitions_retrofit=true

4.3. 将组和分区添加到更新清单

在具有动态分区的A/B设备或要添加对动态分区的支持的A/B设备上执行OTA更新时,需要将组和分区添加到更新清单中。

以下代码段显示更新清单以支持动态分区的其他信息。

代码语言:javascript
复制
message DeltaArchiveManifest {
    optional DynamicPartitionMetadata dynamic_partition_metadata;
}

message DynamicPartitionMetadata {
    repeated DynamicPartitionGroup groups;
}

message DynamicPartitionGroup {
    required string name;
    optional uint64 size; // maximum size of group
    repeated string partition_names;
}


5. 参考

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 概述
  • 2. 实现动态分区
    • 2.1. Linux device mapper驱动
      • 2.2. 分区布局更改
        • 2.2.1. 支持的动态分区
        • 2.2.2. metadata数据
      • 2.3. 分区对齐
        • 2.4. 设备配置更改(启动动态分区)
          • 2.5. 板级配置更改(配置super分区大小)
            • 2.5.1. 注意点
          • 2.6. 调整动态分区的大小
            • 2.7. System-as-root更改
              • 2.8. AVB配置更改
                • 2.9. 内核命令行更改
                  • 2.10. fatab更改
                    • 2.11. selinux更改
                      • 2.12. fastbootd
                        • 2.13. adb remount
                        • 3. 升级Android设备
                          • 3.1. device.mk更改
                            • 3.2. BoardConfig.mk
                              • 3.3. selinux更改
                                • 3.4. 出厂镜像
                                  • 3.5. 设备映射器(基于存储设备)
                                  • 4. 具有动态分区的A/B设备的OTA
                                    • 4.1. lpdump工具
                                      • 4.2. 更新流程
                                        • 4.2.1. 初始化super分区元数据
                                        • 4.2.2. 在块设备上应用更新
                                        • 4.2.3. 取消映射目标分区
                                      • 4.3. 将组和分区添加到更新清单
                                      • 5. 参考
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档