實施動態分割區

動態分區是使用Linux核心中的dm-線性設備映射器模組實現的。 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.轉換為動態分割時的新實體分區表佈局

支援的動態分區有:

  • 系統
  • 小販
  • 產品
  • 系統分機
  • 原始設計製造商

對於使用 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 _SIZEBOARD_ 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 的未使用空間。

系統作為 root 的更改

使用 Android 10 啟動的裝置不得使用 system-as-root。

具有動態分區的設備(無論是使用動態分區啟動還是改裝動態分區)不得使用系統作為根。 Linux 核心無法解釋super分割區,因此無法自行掛載systemsystem現在由駐留在 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標誌和 Android 10 中引入的first_stage_mount標誌,該標誌表示分區將在第一階段掛載。
  • 分割區可以指定avb= vbmeta partition namefs_mgr標誌,然後在嘗試掛載任何裝置之前,第一階段init會初始化指定的vbmeta分割區。
  • dev欄位必須是分割區名稱。

以下 fstab 條目將系統、供應商和產品設定為遵循上述規則的邏輯分區。

#<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 移至用戶空間

亞銀重新安裝

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

有關如何啟用overlayfs的更多信息,請參閱AOSP中的overlayfs README

升級安卓設備

如果您將設備升級到 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_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 。有關詳細信息,請參閱新設備上的主機板配置變更

例如,如果設備已有系統分區和供應商分區,並且您希望將它們轉換為動態分區並在更新期間新增新的產品分區,請設定以下板配置:

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>其中dev.mnt.blk. <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功能,再加上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}