動態分區是使用 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顯示了轉換為動態分區之前和之後的示例分區表。
![分區表佈局](https://source.android.google.cn/static/devices/tech/ota/images/dynamic_partitions_table.png?authuser=19&hl=zh-tw)
支持的動態分區有:
- 系統
- 小販
- 產品
- 系統分機
- 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
,並將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_ 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時,如果設備未使用鍊式分區描述符,則無需更改。但是,如果使用鍊式分區,並且已驗證的分區之一是動態的,則需要進行更改。
這是為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:僅在命令行需要時查詢分區 GUID。”
- 5abd6bc2578968d24406d834471adfd995a0c2e9 —“允許系統分區不存在”
- 9ba3b6613b4e5130fa01a11d984c6b5f0eb3af05 —“修復 AvbSlotVerifyData->cmdline 可能為 NULL”
如果使用鍊式分區,請包含一個附加補丁:
- 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_GROUPS
、BOARD_ 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
進行標記。例如,如果設備已經有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;
有關其他配置,請參閱在新設備上實現動態分區。
有關改造更新的更多信息,請參閱不帶動態分區的 A/B 設備的 OTA 。
工廠圖片
對於支持動態分區啟動的設備,請避免使用用戶空間快速啟動來刷新出廠映像,因為啟動到用戶空間比其他刷新方法慢。
為了解決這個問題, make dist
現在構建了一個額外的super.img
鏡像,可以直接刷入超級分區。它自動捆綁邏輯分區的內容,這意味著除了super
分區元數據之外,它還包含system.img
、 vendor.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.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}