動態系統更新 (DSU) 可讓您製作 Android 系統映像檔,使用者可以從網際網路下載並試用,不必擔心現有系統映像檔損毀。本文說明如何支援 DSU。
核心需求
如需核心需求,請參閱「實作動態分割區」。
此外,DSU 依賴裝置對應程式驗證 (dm-verity) 核心功能,驗證 Android 系統映像檔。因此您必須啟用下列核心設定:
CONFIG_DM_VERITY=y
CONFIG_DM_VERITY_FEC=y
分區需求
自 Android 11 起,DSU 必須使用 F2FS 或 ext4 檔案系統的 /data
分區。F2FS 的效能較佳,因此建議使用,但差異應該不大。
以下列舉幾個 Pixel 裝置的動態系統更新時間範例:
- 使用 F2FS:
- 109 秒、8G 使用者、867M 系統、檔案系統類型:F2FS: encryption=aes-256-xts:aes-256-cts
- 104s、8G 使用者、867M 系統、檔案系統類型:F2FS:encryption=ice
- 使用 ext4:
- 135 秒、8G 使用者、867M 系統、檔案系統類型:ext4: encryption=aes-256-xts:aes-256-cts
如果平台上的時間長度遠遠超過這個值,請檢查掛接標記是否包含任何會進行「同步」寫入的標記,或明確指定「非同步」標記,以提升效能。
metadata
分割區 (16 MB 以上) 必須用於儲存與已安裝映像檔相關的資料。必須在第一階段掛接期間掛接。
userdata
分區必須使用 F2FS 或 ext4 檔案系統。使用 F2FS 時,請納入 Android 通用核心中所有與 F2FS 相關的修補程式。
DSU 是使用核心/通用 4.9 開發及測試,建議使用核心 4.9 以上版本,才能使用這項功能。
供應商 HAL 行為
Weaver HAL
weaver HAL 提供固定數量的儲存空間,可存放使用者金鑰。DSU 會耗用兩個額外的金鑰位置。如果 OEM 具有織布器 HAL,則必須有足夠的插槽,可容納一般系統映像檔 (GSI) 和主機映像檔。
Gatekeeper HAL
Gatekeeper HAL 必須支援大型 USER_ID
值,因為 GSI 會將 UID 偏移至 HAL,偏移量為 +1000000。
驗證開機程序
如要在鎖定狀態下支援啟動開發人員 GSI 映像檔,且不需停用驗證啟動,請加入開發人員 GSI 金鑰,方法是在 device/<device_name>/device.mk
檔案中新增下列程式碼:
$(call inherit-product, $(SRC_TARGET_DIR)/product/developer_gsi_keys.mk)
復原保護措施
使用 DSU 時,下載的 Android 系統映像檔必須比裝置上的目前系統映像檔新。方法是比較兩個系統映像檔的 Android 驗證開機程序 (AVB) AVB 屬性描述元:Prop: com.android.build.system.security_patch ->
'2019-04-05'
中的安全性修補程式等級。
如果裝置未使用 AVB,請使用開機載入程式,將目前系統映像檔的安全性修補程式等級放入核心 cmdline 或 bootconfig:androidboot.system.security_patch=2019-04-05
。
硬體需求
啟動 DSU 執行個體時,系統會分配兩個暫存檔:
- 儲存
GSI.img
的邏輯分區 (1 至 1.5 GB) - 8 GB 的空白
/data
分割區,做為執行 GSI 的沙箱
建議您在啟動 DSU 執行個體前,保留至少 10 GB 的可用空間。DSU 也支援從 SD 卡配置。如果裝置有 SD 卡,系統會優先將空間分配給 SD 卡。對於內部儲存空間可能不足的低功耗裝置而言,SD 卡支援功能至關重要。如果手機有 SD 卡,請確認 SD 卡未設為內部儲存空間。DSU 不支援已採用 SD 卡。
可用的前端
您可以使用 adb
、OEM 應用程式或一鍵 DSU 載入器 (適用於 Android 11 以上版本) 啟動 DSU。
使用 adb 啟動 DSU
如要使用 adb 啟動 DSU,請輸入下列指令:
$ simg2img out/target/product/.../system.img system.raw
$ gzip -c system.raw > system.raw.gz
$ adb push system.raw.gz /storage/emulated/0/Download
$ adb shell am start-activity \
-n com.android.dynsystem/com.android.dynsystem.VerificationActivity \
-a android.os.image.action.START_INSTALL \
-d file:///storage/emulated/0/Download/system.raw.gz \
--el KEY_SYSTEM_SIZE $(du -b system.raw|cut -f1) \
--el KEY_USERDATA_SIZE 8589934592
使用應用程式啟動 DSU
DSU 的主要進入點是 android.os.image.DynamicSystemClient.java
API:
public class DynamicSystemClient {
...
...
/**
* Start installing DynamicSystem from URL with default userdata size.
*
* @param systemUrl A network URL or a file URL to system image.
* @param systemSize size of system image.
*/
public void start(String systemUrl, long systemSize) {
start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
}
您必須在裝置上預先安裝/組合這個應用程式。由於 DynamicSystemClient
是系統 API,您無法使用一般 SDK API 建構應用程式,也無法在 Google Play 發布。這款應用程式的用途如下:
- 使用供應商定義的架構擷取圖片清單和對應的網址。
- 比對清單中的圖片與裝置,並顯示相容的圖片供使用者選取。
請按照下列方式叫用
DynamicSystemClient.start
:DynamicSystemClient aot = new DynamicSystemClient(...) aot.start( ...URL of the selected image..., ...uncompressed size of the selected image...);
這個網址指向經過 gzip 壓縮的非稀疏系統映像檔,您可以使用下列指令製作該檔案:
$ simg2img ${OUT}/system.img ${OUT}/system.raw
$ gzip ${OUT}/system.raw
$ ls ${OUT}/system.raw.gz
檔案名稱應符合以下格式:
<android version>.<lunch name>.<user defined title>.raw.gz
例如:
o.aosp_taimen-userdebug.2018dev.raw.gz
p.aosp_taimen-userdebug.2018dev.raw.gz
一鍵載入 DSU
Android 11 推出一鍵 DSU 載入器,這是開發人員設定中的前端。
圖 1. 啟動 DSU 載入器
開發人員點選「DSU Loader」按鈕後,系統會從網頁擷取預先設定的 DSU JSON 描述元,並在浮動選單中顯示所有適用的映像檔。選取映像檔即可開始安裝 DSU,通知列會顯示安裝進度。
圖 2. DSU 映像檔安裝進度
根據預設,DSU 載入器會載入包含 GSI 映像檔的 JSON 描述元。以下各節將示範如何製作 OEM 簽署的 DSU 套件,並從 DSU 載入器載入這些套件。
功能旗標
DSU 功能位於 settings_dynamic_android
功能旗標下方。使用 DSU 前,請務必啟用對應的功能旗標。
圖 3. 啟用功能旗標
如果裝置執行的是使用者版本,可能無法使用功能旗標使用者介面。在這種情況下,請改用 adb
指令:
$ adb shell setprop persist.sys.fflag.override.settings_dynamic_system 1
GCE 上的供應商主機系統映像檔 (選用)
系統映像檔的可能儲存位置之一是 Google Compute Engine (GCE) 儲存空間。發布管理員使用 GCP 儲存空間控制台新增/刪除/變更發布的系統映像檔。
圖片必須可公開存取,如下所示:
圖 4. GCE 中的公開存取權
如要瞭解如何將項目設為公開,請參閱 Google Cloud 說明文件。
ZIP 檔案中的多重分割 DSU
自 Android 11 起,DSU 可以有多個分割區。舉例來說,除了 system.img
之外,它還可包含 product.img
。裝置啟動時,第一階段 init
會偵測已安裝的 DSU 分區,並在啟用已安裝的 DSU 時,暫時取代裝置上的分區。DSU 套件可能包含裝置上沒有對應磁碟分割區的磁碟分割區。
圖 5. DSU 程序 (多個分割區)
OEM 簽署的 DSU
為確保裝置上執行的所有映像檔都經過裝置製造商授權,DSU 套件中的所有映像檔都必須經過簽署。舉例來說,假設 DSU 套件包含兩個分區映像檔,如下所示:
dsu.zip {
- system.img
- product.img
}
system.img
和 product.img
都必須先以 OEM 金鑰簽署,才能放入 ZIP 檔案。一般做法是使用非對稱演算法 (例如 RSA),以私密金鑰簽署套件,並以公開金鑰驗證套件。第一階段的 RAM 磁碟必須包含配對公開金鑰,例如 /avb/*.avbpubkey
。如果裝置已採用 AVB,現有的簽署程序就足夠。以下各節說明簽署程序,並重點介紹用於驗證 DSU 套件中映像檔的 AVB 公開金鑰位置。
DSU JSON 描述元
DSU JSON 描述元會說明 DSU 套件。支援兩種基本型別。
首先,include
基本體包含額外的 JSON 描述元,或將 DSU 載入器重新導向至新位置。例如:
{
"include": ["https://.../gsi-release/gsi-src.json"]
}
其次,image
基本型別用於描述已發布的 DSU 套件。圖片基本體內有數個屬性:
name
和details
屬性是顯示在對話方塊中的字串,供使用者選取。cpu_api
、vndk
和os_version
屬性用於相容性檢查,下一節將說明這項檢查。選用的
pubkey
屬性會說明與私密金鑰配對的公開金鑰,該私密金鑰用於簽署 DSU 套件。指定後,DSU 服務就能檢查裝置是否具備用於驗證 DSU 套件的金鑰。這樣可避免安裝無法辨識的 DSU 套件,例如將 OEM-A 簽署的 DSU 安裝到 OEM-B 製造的裝置。選用的
tos
屬性會指向說明對應 DSU 套件服務條款的文字檔案。開發人員選取指定服務條款屬性的 DSU 套件時,系統會開啟圖 6 所示的對話方塊,要求開發人員先接受服務條款,再安裝 DSU 套件。圖 6. 服務條款對話方塊
如需參考,以下是 GSI 的 DSU JSON 描述元:
{
"images":[
{
"name":"GSI+GMS x86",
"os_version":"10",
"cpu_abi": "x86",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"tos": "https://dl.google.com/developers/android/gsi/gsi-tos.txt",
"uri":"https://.../gsi/gsi_gms_x86-exp-QP1A.190711.020.C4-5928301.zip"
},
{
"name":"GSI+GMS ARM64",
"os_version":"10",
"cpu_abi": "arm64-v8a",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"tos": "https://dl.google.com/developers/android/gsi/gsi-tos.txt",
"uri":"https://.../gsi/gsi_gms_arm64-exp-QP1A.190711.020.C4-5928301.zip"
},
{
"name":"GSI ARM64",
"os_version":"10",
"cpu_abi": "arm64-v8a",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"uri":"https://.../gsi/aosp_arm64-exp-QP1A.190711.020.C4-5928301.zip"
},
{
"name":"GSI x86_64",
"os_version":"10",
"cpu_abi": "x86_64",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"uri":"https://.../gsi/aosp_x86_64-exp-QP1A.190711.020.C4-5928301.zip"
}
]
}
相容性管理
系統會使用多項屬性,指定 DSU 套件與本機裝置之間的相容性:
cpu_api
是描述裝置架構的字串。這項屬性為必填,且會與ro.product.cpu.abi
系統屬性進行比較。值必須完全相符。os_version
是選用整數,用於指定 Android 版本。舉例來說,Android 10 的os_version
是10
,Android 11 的os_version
則是11
。指定這個屬性時,其值必須大於或等於ro.system.build.version.release
系統屬性。這項檢查可防止在 Android 11 供應商裝置上啟動 Android 10 GSI 映像檔,因為目前不支援這項操作。您可以在 Android 10 裝置上啟動 Android 11 GSI 映像檔。vndk
是選用陣列,用於指定 DSU 套件中包含的所有 VNDK。指定後,DSU 載入器會檢查是否包含從ro.vndk.version
系統屬性擷取的號碼。
撤銷 DSU 金鑰以確保安全性
在極少數情況下,如果用於簽署 DSU 映像檔的 RSA 金鑰組遭到入侵,請盡快更新 RAM 磁碟,移除遭入侵的金鑰。除了更新啟動分區,您也可以使用 HTTPS URL 中的 DSU 金鑰撤銷清單 (金鑰黑名單),封鎖遭入侵的金鑰。
DSU 金鑰撤銷清單包含已撤銷的 AVB 公開金鑰清單。 在 DSU 安裝期間,系統會使用撤銷清單驗證 DSU 映像檔內的公開金鑰。如果系統發現映像檔含有已撤銷的公開金鑰,就會停止 DSU 安裝程序。
金鑰撤銷清單網址應為 HTTPS 網址,以確保安全性,並以資源字串指定:
frameworks/base/packages/DynamicSystemInstallationService/res/values/strings.xml@key_revocation_list_url
字串的值為 https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json
,這是 Google 發布的 GSI 金鑰的撤銷清單。這個資源字串可以疊加及自訂,因此採用 DSU 功能的原始設備製造商可以提供及維護自己的金鑰黑名單。這樣一來,原始設備製造商就能封鎖特定公開金鑰,而不必更新裝置的 RAM 磁碟映像檔。
撤銷清單的格式如下:
{
"entries":[
{
"public_key":"bf14e439d1acf231095c4109f94f00fc473148e6",
"status":"REVOKED",
"reason":"Key revocation test key"
},
{
"public_key":"d199b2f29f3dc224cca778a7544ea89470cbef46",
"status":"REVOKED",
"reason":"Key revocation test key"
}
]
}
public_key
是已撤銷金鑰的 SHA-1 摘要,格式如「產生 AVB 公開金鑰」一節所述。status
表示金鑰的撤銷狀態。目前唯一支援的值是REVOKED
。reason
:(選用) 說明撤銷原因的字串。
DSU 程序
本節說明如何執行幾項 DSU 設定程序。
產生新的金鑰組
使用 openssl
指令,以 .pem
格式產生 RSA 私密/公開金鑰組 (例如大小為 2048 位元):
$ openssl genrsa -out oem_cert_pri.pem 2048
$ openssl rsa -in oem_cert_pri.pem -pubout -out oem_cert_pub.pem
私密金鑰可能無法存取,且只會保留在硬體安全性模組 (HSM) 中。 在這種情況下,金鑰產生後可能會提供 x509 公開金鑰憑證。如需從 x509 憑證產生 AVB 公開金鑰的操作說明,請參閱「將配對公開金鑰新增至 ramdisk」一節。
如要將 x509 憑證轉換為 PEM 格式,請按照下列步驟操作:
$ openssl x509 -pubkey -noout -in oem_cert_pub.x509.pem > oem_cert_pub.pem
如果憑證已是 PEM 檔案,請略過這個步驟。
將配對公鑰新增至 RAM 磁碟
oem_cert.avbpubkey
必須放在 /avb/*.avbpubkey
底下,才能驗證已簽署的 DSU 套件。首先,將 PEM 格式的公開金鑰轉換為 AVB 公開金鑰格式:
$ avbtool extract_public_key --key oem_cert_pub.pem --output oem_cert.avbpubkey
然後按照下列步驟,在第一階段 RAM 磁碟中加入公開金鑰。
新增預先建構的模組,即可複製
avbpubkey
。舉例來說,請新增device/<company>/<board>/oem_cert.avbpubkey
和device/<company>/<board>/avb/Android.mk
,並加入下列內容:include $(CLEAR_VARS) LOCAL_MODULE := oem_cert.avbpubkey LOCAL_MODULE_CLASS := ETC LOCAL_SRC_FILES := $(LOCAL_MODULE) ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb else LOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)/avb endif include $(BUILD_PREBUILT)
讓 droidcore 目標依附於新增的
oem_cert.avbpubkey
:droidcore: oem_cert.avbpubkey
在 JSON 描述元中產生 AVB 公開金鑰屬性
oem_cert.avbpubkey
採用 AVB 公開金鑰二進位格式。使用 SHA-1 讓 JSON 描述元可讀:
$ sha1sum oem_cert.avbpubkey | cut -f1 -d ' '
3e62f2be9d9d813ef5........866ac72a51fd20
這會是 JSON 描述元的 pubkey
屬性內容。
"images":[
{
...
"pubkey":"3e62f2be9d9d813ef5........866ac72a51fd20",
...
},
簽署 DSU 套件
請使用下列其中一種方法簽署 DSU 套件:
方法 1:重複使用原始 AVB 簽署程序製作的構件,製作 DSU 套件。另一種做法是從發布套件中擷取已簽署的圖片,並使用擷取的圖片直接製作 ZIP 檔案。
方法 2:如果私鑰可用,請使用下列指令簽署 DSU 分區。DSU 套件 (ZIP 檔案) 中的每個
img
都會個別簽署:$ key_len=$(openssl rsa -in oem_cert_pri.pem -text | grep Private-Key | sed -e 's/.*(\(.*\) bit.*/\1/') $ for partition in system product; do avbtool add_hashtree_footer \ --image ${OUT}/${partition}.img \ --partition_name ${partition} \ --algorithm SHA256_RSA${key_len} \ --key oem_cert_pri.pem done
如要進一步瞭解如何使用 avbtool
新增 add_hashtree_footer
,請參閱「使用 avbtool」一節。
在本機驗證 DSU 套件
建議使用下列指令,根據配對公開金鑰驗證所有本機映像檔:
for partition in system product; do
avbtool verify_image --image ${OUT}/${partition}.img --key oem_cert_pub.pem
done
預期的輸出內容如下:
Verifying image dsu/system.img using key at oem_cert_pub.pem
vbmeta: Successfully verified footer and SHA256_RSA2048 vbmeta struct in dsu/system.img
: Successfully verified sha1 hashtree of dsu/system.img for image of 898494464 bytes
Verifying image dsu/product.img using key at oem_cert_pub.pem
vbmeta: Successfully verified footer and SHA256_RSA2048 vbmeta struct in dsu/product.img
: Successfully verified sha1 hashtree of dsu/product.img for image of 905830400 bytes
製作 DSU 套件
以下範例會建立包含 system.img
和 product.img
的 DSU 套件:
dsu.zip {
- system.img
- product.img
}
簽署兩個映像檔後,請使用下列指令建立 ZIP 檔案:
$ mkdir -p dsu
$ cp ${OUT}/system.img dsu
$ cp ${OUT}/product.img dsu
$ cd dsu && zip ../dsu.zip *.img && cd -
自訂一鍵 DSU
根據預設,DSU 載入器會指向 GSI 映像檔的中繼資料,即 https://...google.com/.../gsi-src.json
。
OEM 可以定義指向自家 JSON 描述元的 persist.sys.fflag.override.settings_dynamic_system.list
屬性,覆寫清單。舉例來說,OEM 可能會提供包含 GSI 和 OEM 專屬圖片的 JSON 中繼資料,如下所示:
{
"include": ["https://dl.google.com/.../gsi-src.JSON"]
"images":[
{
"name":"OEM image",
"os_version":"10",
"cpu_abi": "arm64-v8a",
"details":"...",
"vndk":[
27,
28,
29
],
"spl":"...",
"pubkey":"",
"uri":"https://.../....zip"
},
}
如圖 7 所示,OEM 可以將已發布的 DSU 中繼資料串連起來。
圖 7. 串連已發布的 DSU 中繼資料