動態分割是透過 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 顯示轉換為動態分區前後的範例分區表。

支援的動態分區如下:
- 系統
- 供應商
- 產品
- 系統分機號碼
- 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
Jamboard 設定變更
您必須設定 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 - overhead -
對於非 A/B 裝置和改裝 A/B 裝置,所有群組的最大大小總和必須為:
BOARD_SUPER_PARTITION_SIZE
- overhead - 在建構期間,更新群組中每個分割區的圖片大小總和,不得超過群組大小上限。
- 計算時必須納入經常用量,才能將中繼資料、對齊等因素納入考量。合理的額外負荷為 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 的未使用空間。
系統即為根目錄的變更
搭載 Android 10 的裝置不得使用系統即為根目錄。
搭載動態分區的裝置 (無論是推出時就搭載,還是改裝成搭載動態分區),都不得使用以系統為根目錄的設定。Linux 核心無法解讀 super
分割區,因此無法掛接 system
本身。system
現在由第一階段 init
裝載,後者位於 ramdisk 中。
請勿設定 BOARD_BUILD_SYSTEM_ROOT_IMAGE
。在 Android 10 中,BOARD_BUILD_SYSTEM_ROOT_IMAGE
標記只用於區分系統是由核心掛接,還是由 ramdisk 中的第一階段 init
掛接。
將 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 時,如果裝置未使用鏈結分割區描述元,則不必進行任何變更。不過,如果使用鏈結式分割區,且其中一個已驗證的分割區是動態分割區,則必須進行變更。
以下是裝置的範例設定,該裝置會將 vbmeta
鏈結至 system
和 vendor
分割區。
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: Only query partition GUIDs when the cmdline needs them."
- 5abd6bc2578968d24406d834471adfd995a0c2e9 — "Allow system partition to be absent"
- 9ba3b6613b4e5130fa01a11d984c6b5f0eb3af05 — "Fix AvbSlotVerifyData->cmdline might be NULL"
如果使用鏈結分割區,請加入額外修補程式:
- 49936b4c0109411fdd38bd4ba3a32a01c40439a9 — "libavb: Support vbmeta blobs in beginning of partition."
核心指令列變更
您必須在核心指令列中新增 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 項目。使用將成為 RAM 磁碟一部分的 fstab 檔案。
您必須變更邏輯分割區的 fstab 檔案:
-
fs_mgr 標記欄位必須包含
logical
標記和first_stage_mount
標記 (Android 10 中導入),表示要在第一階段掛接分割區。 -
分割區可以指定
avb=vbmeta partition name
做為fs_mgr
旗標,然後指定的vbmeta
分割區會由第一階段init
初始化,再嘗試掛接任何裝置。 -
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
fastbootd
開機載入程式 (或任何非使用者空間的刷機工具) 無法辨識動態分割區,因此無法刷機。為解決這個問題,裝置必須使用快速啟動通訊協定的使用者空間實作,也就是 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
Jamboard 設定變更
您必須設定下列開發板變數:
- 將
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
。詳情請參閱「新裝置的開發板設定變更」。
舉例來說,如果裝置已有系統和供應商磁區,且您想在更新期間將這些磁區轉換為動態磁區,並新增產品磁區,請設定以下主機板設定:
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;
如需其他設定,請參閱「在新裝置上實作動態分割區」。
如要進一步瞭解改造更新,請參閱「OTA for A/B Devices without Dynamic Partitions」。
原廠映像檔
如果裝置支援動態分區,請避免使用使用者空間 Fastboot 刷入原廠映像檔,因為啟動至使用者空間的速度比其他刷入方法慢。
為解決這個問題,make dist
現在會建構額外的 super.img
圖片,可直接刷入超級分割區。它會自動將邏輯分割區的內容組合在一起,也就是說,除了 super
分割區中繼資料之外,還包含 system.img
、vendor.img
等。這個映像檔可以直接刷入 super
分割區,不需要任何額外工具或使用 fastbootd。建構完成後,super.img
會放置在 ${ANDROID_PRODUCT_OUT}
中。
對於搭載動態分區的 A/B 裝置,A 插槽中包含映像檔。super.img
直接刷入超級映像檔後,請將 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
。您可能會預期在 early-fs
(各種精靈和設施啟動時) init.rc
指令碼可以覆寫之前,核心預設 read_ahead_kb
就已足夠。init.rc
因此,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}