實現動態分區

動態分區是使用 Linux 內核中的 dm-linear device-mapper 模塊實現的。 super分區包含列出super中每個動態分區的名稱和塊範圍的元數據。在第一階段init期間,解析和驗證此元數據,並創建虛擬塊設備來表示每個動態分區。

應用 OTA 時,會根據需要自動創建、調整大小或刪除動態分區。對於 A/B 設備,元數據有兩個副本,並且更改僅應用於表示目標插槽的副本。

因為動態分區是在用戶空間中實現的,所以引導加載程序需要的分區不能變成動態的。例如, bootdtbovbmeta由引導加載程序讀取,因此必須保留為物理分區。

每個動態分區都可以屬於一個更新組。這些組限制了該組中的分區可以使用的最大空間。例如, systemvendor可以屬於限制systemvendor總大小的組。

在新設備上實現動態分區

本節詳細介紹如何在搭載 Android 10 及更高版本的新設備上實現動態分區。要更新現有設備,請參閱升級 Android 設備

分區更改

對於搭載 Android 10 的設備,請創建一個名為super的分區。 super分區在內部處理 A/B 插槽,因此 A/B 設備不需要單獨super_asuper_b分區。引導加載程序不使用的所有隻讀 AOSP 分區必須是動態的,並且必須從 GUID 分區表 (GPT) 中刪除。供應商特定的分區不必是動態的,可以放在 GPT 中。

要估計super的大小,請添加從 GPT 中刪除的分區的大小。對於 A/B 設備,這應該包括兩個插槽的大小。圖 1顯示了轉換為動態分區之前和之後的示例分區表。

分區表佈局
圖 1.轉換為動態分區時的新物理分區表佈局

支持的動態分區有:

  • 系統
  • 小販
  • 產品
  • 系統分機
  • ODM

對於搭載 Android 10 的設備,內核命令行選項androidboot.super_partition必須為空,以便命令 sysprop 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 _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 ,並將vendorproductodm放入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_ partition IMAGE_PARTITION_RESERVED_SIZE指定最小可用空間量,或者您可以指定BOARD_ partition IMAGE_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 Verified Boot 2.0時,如果設備未使用鍊式分區描述符,則無需更改。但是,如果使用鍊式分區,並且已驗證的分區之一是動態的,則需要進行更改。

這是為systemvendor分區鏈接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

使用此配置,引導加載程序希望在systemvendor分區的末尾找到一個vbmeta 頁腳。因為這些分區不再對引導加載程序可見(它們駐留在super中),所以需要進行兩項更改。

  • vbmeta_systemvbmeta_vendor分區添加到設備的分區表中。對於 A/B 設備,添加vbmeta_system_avbmeta_system_bvbmeta_vendor_avbmeta_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 ,請包含以下補丁:

如果使用鍊式分區,請包含一個附加補丁:

  • 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
您可以在 BoardConfig.mk 文件中添加 bootconfig 參數,如下所示:
BOARD_BOOTCONFIG += androidboot.boot_devices=soc/100000.ufshc

fstab 更改

設備樹和設備樹覆蓋不得包含 fstab 條目。使用將成為 ramdisk 一部分的 fstab 文件。

必須對邏輯分區的 fstab 文件進行更改:

  • fs_mgr flags 字段必須包含logical標誌和first_stage_mount標誌,在 Android 10 中引入,表示要在第一階段掛載一個分區。
  • 分區可以指定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

快速啟動

引導加載程序(或任何非用戶空間刷新工具)不了解動態分區,因此無法刷新它們。為了解決這個問題,設備必須使用 fastboot 協議的用戶空間實現,稱為 fastbootd。

有關如何實現 fastbootd 的更多信息,請參閱將 Fastboot 移至用戶空間

adb 重新掛載

對於使用 eng 或 userdebug 構建的開發人員, adb remount對於快速迭代非常有用。動態分區給adb remount帶來了問題,因為每個文件系統中不再有可用空間。為了解決這個問題,設備可以啟用 overlayfs。只要超級分區內有空閒空間, adb remount就會自動創建一個臨時動態分區並使用 overlayfs 進行寫入。臨時分區名為scratch ,因此不要將此名稱用於其他分區。

有關如何啟用 overlayfs 的更多信息,請參閱 AOSP 中的overlayfs 自述文件

升級安卓設備

如果您將設備升級到 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_ partition IMAGE_PARTITION_SIZE
  • 為 BOARD_SUPER_PARTITION_BLOCK_DEVICES 中的所有分區取消設置現有的BOARD_ partition IMAGE_PARTITION_SIZE BOARD_SUPER_PARTITION_BLOCK_DEVICES
  • 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 _SIZE _SIZE 和BOARD_ group _PARTITION_LIST 。有關詳細信息,請參閱新設備上的板配置更改

例如,如果設備已經有系統和供應商分區,並且您想將它們轉換為動態分區並在更新期間添加新的產品分區,請設置此板配置:

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進行標記。例如,如果設備已經有systemvendor分區,您希望將它們用作塊設備來存儲動態分區的範圍,並且它們的按名稱符號鏈接標記為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

工廠圖片

對於支持動態分區啟動的設備,請避免使用用戶空間快速啟動來刷新出廠映像,因為啟動到用戶空間比其他刷新方法慢。

為了解決這個問題, make dist現在構建了一個額外的super.img鏡像,可以直接刷入超級分區。它自動捆綁邏輯分區的內容,這意味著除了super分區元數據之外,它還包含system.imgvendor.img等。該鏡像可以直接刷入super分區,無需任何額外工具或使用 fastbootd。構建完成後, super.img被放置在${ANDROID_PRODUCT_OUT}中。

對於使用動態分區啟動的 A/B 設備, super.img包含 A 插槽中的圖像。直接刷超級鏡像後,在重啟設備前將插槽A標記為可啟動。

對於改造設備, make dist構建了一組super_*.img鏡像,可以直接刷到對應的物理分區。例如,當BOARD_SUPER_PARTITION_BLOCK_DEVICES是系統供應商時, make dist構建super_system.imgsuper_vendor.img 。這些圖像放置在target_files.zip的 OTA 文件夾中。

設備映射器存儲設備調優

動態分區可容納許多不確定的設備映射器對象。這些可能無法按預期全部實例化,因此您必須跟踪所有掛載,並使用其底層存儲設備更新所有關聯分區的 Android 屬性。

init內部的一種機制跟踪掛載並異步更新 Android 屬性。這需要的時間不能保證在特定時期內,因此您必須提供足夠的時間讓所有on property觸發器做出反應。屬性是dev.mnt.blk. <partition>例如,其中<partition>rootsystemdatavendor 。每個屬性都與基本存儲設備名稱相關聯,如以下示例所示:

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才處於活動狀態,因此它們不能在初始引導階段用於處理rootsystemvendor 。您可能希望內核默認的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}