動態分區是使用Linux核心中的dm-線性設備映射器模組實現的。 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顯示了轉換為動態分割之前和之後的範例分割表。
支援的動態分區有:
- 系統
- 小販
- 產品
- 系統分機
- 原始設計製造商
對於使用 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
和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 的未使用空間。
系統作為 root 的更改
使用 Android 10 啟動的裝置不得使用 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
標誌和 Android 10 中引入的first_stage_mount
標誌,該標誌表示分區將在第一階段掛載。 - 分割區可以指定
avb= vbmeta partition name
為fs_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_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;
如需其他配置,請參閱在新裝置上實作動態分割區。
有關改造更新的更多信息,請參閱不帶動態分區的 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>
其中dev.mnt.blk. <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
功能,再加上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}