Android 有兩種更新機制:A/B(無縫)更新和非 A/B 更新。為了降低程式碼複雜性並增強更新過程,在 Android 11 中,這兩種機制透過虛擬 A/B 進行統一,以最小化的儲存成本為所有裝置帶來無縫更新。 Android 12 提供虛擬 A/B 壓縮選項來壓縮快照分割區。在 Android 11 和 Android 12 中,以下內容均適用:
- 虛擬 A/B 更新與 A/B 更新一樣無縫。虛擬 A/B 更新可最大限度地減少設備離線和不可用的時間。
- 虛擬 A/B 更新可以回滾。如果新作業系統無法啟動,裝置會自動回滾到先前的版本。
- 虛擬 A/B 更新僅複製引導程式使用的分割區,從而最大限度地減少額外空間。其他可更新分割區會被快照。
背景和術語
本節定義術語並描述支援虛擬 A/B 的技術。
裝置映射器
Device-mapper 是 Android 中常用的 Linux 虛擬區塊層。對於動態分區,像/system
這樣的分區是一堆分層設備:
- 堆疊底部是實體超級分區(例如
/dev/block/by-name/super
)。 - 中間是一個
dm-linear
設備,指定超級分區中的哪些區塊形成給定分區。這在 A/B 裝置上顯示為/dev/block/mapper/system_[a|b]
,在非 A/B 裝置上顯示為/dev/block/mapper/system
。 - 頂部有一個
dm-verity
設備,是為驗證分割區建立的。該設備驗證dm-linear
設備上的區塊是否已正確簽署。它顯示為/dev/block/mapper/system-verity
並且是/system
掛載點的來源。
圖 1 顯示了/system
掛載點下的堆疊的樣子。
圖 1. /system 掛載點下的堆疊
dm 快照
Virtual A/B 依賴dm-snapshot
,它是一個裝置映射器模組,用於對儲存裝置的狀態進行快照。使用dm-snapshot
時,有四個裝置在使用:
- 基本設備是進行快照的設備。在此頁面上,基本設備始終是動態分區,例如係統或供應商。
- 寫入時複製(COW) 設備,用於記錄對基本設備的變更。它可以是任何大小,但必須足夠大以容納對基本設備的所有更改。
- 快照設備是使用
snapshot
目標建立的。對快照設備的寫入將寫入 COW 設備。從快照設備讀取 從基本設備或 COW 設備讀取,取決於正在存取的資料是否已被快照更改。 - 來源設備是使用
snapshot-origin
目標建立的。讀取到來源設備,直接從基礎設備讀取。寫入原始設備直接寫入基礎設備,但原始資料透過寫入 COW 設備進行備份。
圖 2. dm-snapshot 的裝置映射
壓縮快照
在 Android 12 及更高版本中,由於/data
分區的空間要求可能很高,因此您可以在建置中啟用壓縮快照來滿足/data
分區的更高空間要求。
虛擬 A/B 壓縮快照基於 Android 12 及更高版本中提供的以下元件建置:
這些組件可以實現壓縮。為實現壓縮快照功能而進行的其他必要變更將在下一節中給出:壓縮快照的 COW 格式、 dm-user和Snapuserd 。
用於壓縮快照的 COW 格式
在 Android 12 及更高版本中,壓縮快照使用 COW 格式。與用於未壓縮快照的內核內建格式類似,壓縮快照的 COW 格式具有元資料和資料的交替部分。原始格式的元資料僅允許替換操作:將基礎映像中的區塊X替換為快照中區塊Y的內容。壓縮快照COW格式更具表現力,支援以下操作:
- 複製:基礎設備中的區塊X應替換為基礎設備中的區塊Y。
- 替換:基本設備中的區塊X應替換為快照中區塊Y的內容。每個塊都經過 gz 壓縮。
- 零:基本設備中的塊X應替換為全零。
- XOR :COW 設備在區塊X和區塊Y之間儲存 XOR 壓縮位元組。 (適用於 Android 13 及更高版本。)
完整的 OTA 更新僅包含替換和歸零操作。增量 OTA 更新還可以複製操作。
Android 12 中的 dm 用戶
dm-user 核心模組使userspace
能夠實現設備映射器塊設備。 dm-user 表條目在/dev/dm-user/<control-name>
下建立一個雜項設備。 userspace
進程可以輪詢設備以接收來自核心的讀寫請求。每個請求都有一個關聯的緩衝區,供用戶空間填充(用於讀取)或傳播(用於寫入)。
dm-user
核心模組為核心提供了一個新的用戶可見接口,該接口不屬於上游 kernel.org 程式碼庫。在此之前,Google 保留修改 Android 中dm-user
介面的權利。
快照程式
dm-user
的snapuserd
用戶空間元件實作虛擬 A/B 壓縮。
在 Virtual A/B 的未壓縮版本中(無論是在 Android 11 及更低版本中,還是在沒有壓縮快照選項的 Android 12 中),COW 裝置是原始檔案。啟用壓縮後,COW 將充當dm-user
設備,該設備連接到snapuserd
守護程序的實例。
核心不使用新的 COW 格式。因此snapuserd
元件在 Android COW 格式和核心內建格式之間轉換請求:
圖 3. snapuserd 作為 Android 和核心 COW 格式之間的轉換器的流程圖
這種轉換和解壓縮永遠不會在磁碟上發生。 snapuserd
元件攔截核心中發生的 COW 讀寫,並使用 Android COW 格式實作它們。
異或壓縮
對於搭載 Android 13 及更高版本的設備,預設啟用的 XOR 壓縮功能使用戶空間快照能夠在舊區塊和新區塊之間儲存 XOR 壓縮位元組。當虛擬 A/B 更新中僅更改區塊中的幾個位元組時,XOR 壓縮儲存方案使用的空間比預設儲存方案更少,因為快照不儲存完整的 4K 位元組。快照大小的減少是可能的,因為 XOR 資料包含許多零並且比原始區塊資料更容易壓縮。在 Pixel 設備上,XOR 壓縮可將快照大小減少 25% 到 40%。
對於升級到 Android 13 及更高版本的設備,必須啟用 XOR 壓縮。有關詳細信息,請參閱異或壓縮。
虛擬 A/B 壓縮過程
本部分提供有關 Android 13 和 Android 12 中使用的虛擬 A/B 壓縮過程的詳細資訊。
讀取元資料(Android 12)
元資料由snapuserd
守護程式建構。元資料主要是兩個 ID 的映射,每個 ID 8 字節,代表要合併的磁區。在dm-snapshot
中,它稱為disk_exception
。
struct disk_exception {
uint64_t old_chunk;
uint64_t new_chunk;
};
當舊資料塊被新資料塊取代時,就會使用磁碟異常。
snapuserd
守護程式透過 COW 庫讀取內部 COW 文件,並為 COW 文件中存在的每個 COW 操作建立元資料。
建立dm- snapshot
裝置時,會從核心中的dm-snapshot
啟動元資料讀取。
下圖提供了元資料建構的IO路徑的時序圖。
圖 4.元資料建構中 IO 路徑的序列流
合併(Android 12)
啟動程序完成後,更新引擎會將插槽標記為啟動成功,並透過將dm-snapshot
目標切換到dm-snapshot-merge
目標來啟動合併。
dm-snapshot
遍曆元資料並為每個磁碟異常啟動合併 IO。合併 IO 路徑的高階概述如下所示。
圖 5.合併 IO 路徑概述
如果在合併過程中裝置重新啟動,則合併將在下次重新啟動時恢復,並完成合併。
設備映射器分層
對於搭載 Android 13 及更高版本的設備,虛擬 A/B 壓縮中的快照和快照合併過程由snapuserd
用戶空間元件執行。對於升級到 Android 13 及更高版本的設備,必須啟用此功能。有關詳細信息,請參閱用戶空間合併。
以下介紹Virtual A/B壓縮過程:
- 該框架將
/system
分區掛載在dm-verity
設備上,該設備堆疊在dm-user
設備之上。這意味著來自根檔案系統的每個 I/O 都會路由到dm-user
。 -
dm-user
將 I/O 路由到用戶空間snapuserd
守護進程,該守護程序處理 I/O 請求。 - 合併操作完成後,框架會將
dm-verity
折疊到dm-linear
(system_base
) 之上並刪除dm-user
。
圖 6.虛擬 A/B 壓縮過程
快照合併過程可以中斷。如果在合併過程中裝置重新啟動,合併過程將在重新啟動後恢復。
初始化轉換
使用壓縮快照啟動時,第一階段 init 必須啟動snapuserd
來掛載分割區。這帶來了一個問題:當載入並強制執行sepolicy
時, snapuserd
會被置於錯誤的上下文中,其讀取請求會因 selinux 拒絕而失敗。
為了解決這個問題, snapuserd
與init
同步轉換,如下所示:
- 第一階段
init
從 ramdisk 啟動snapuserd
,並將開啟的檔案描述符儲存到環境變數。 - 第一階段
init
將根檔案系統切換到系統分割區,然後執行init
的系統副本。 -
init
的系統副本將組合的 sepolicy 讀取到字串中。 -
Init
在所有 ext4 支援的頁面上呼叫mlock()
。然後,它會停用快照裝置的所有裝置映射器表,並停止snapuserd
。此後禁止從分區讀取,因為這樣做會導致死鎖。 - 使用
snapuserd
的 ramdisk 副本的開啟描述符,init
使用正確的 selinux 上下文重新啟動守護程序。快照設備的設備映射器表被重新啟動。 - Init 呼叫
munlockall()
- 再次執行 IO 是安全的。
空間使用
下表提供了使用 Pixel 作業系統和 OTA 大小的不同 OTA 機制的空間使用情況比較。
尺寸影響 | 非A/B | A/B | 虛擬A/B | 虛擬 A/B(壓縮) |
---|---|---|---|---|
原廠圖片 | 4.5GB超大(3.8G鏡像+700M預留) 1 | 9GB超級(3.8G + 700M預留,兩個插槽) | 4.5GB超大(3.8G鏡像+700M預留) | 4.5GB超大(3.8G鏡像+700M預留) |
其他靜態分區 | /快取 | 沒有任何 | 沒有任何 | 沒有任何 |
OTA 期間的額外儲存空間(應用 OTA 後返回的空間) | /數據 1.4GB | 0 | /數據 3.8GB 2 | /數據 2.1GB 2 |
應用 OTA 所需的總儲存空間 | 5.9GB 3 (超級和數據) | 9GB(超級) | 8.3GB 3 (超級和數據) | 6.6GB 3 (超級和數據) |
1表示基於像素映射的假設佈局。
2假設新系統映像與原始系統映像大小相同。
3在重新啟動之前,空間需求是暫時的。
若要實作虛擬 A/B,或使用壓縮快照功能,請參閱實作虛擬 A/B
,Android 有兩種更新機制:A/B(無縫)更新和非 A/B 更新。為了降低程式碼複雜性並增強更新過程,在 Android 11 中,這兩種機制透過虛擬 A/B 進行統一,以最小化的儲存成本為所有裝置帶來無縫更新。 Android 12 提供虛擬 A/B 壓縮選項來壓縮快照分割區。在 Android 11 和 Android 12 中,以下內容均適用:
- 虛擬 A/B 更新與 A/B 更新一樣無縫。虛擬 A/B 更新可最大限度地減少設備離線和不可用的時間。
- 虛擬 A/B 更新可以回滾。如果新作業系統無法啟動,裝置會自動回滾到先前的版本。
- 虛擬 A/B 更新僅複製引導程式使用的分割區,從而最大限度地減少額外空間。其他可更新分割區會被快照。
背景和術語
本節定義術語並描述支援虛擬 A/B 的技術。
裝置映射器
Device-mapper 是 Android 中常用的 Linux 虛擬區塊層。對於動態分區,像/system
這樣的分區是一堆分層設備:
- 堆疊底部是實體超級分區(例如
/dev/block/by-name/super
)。 - 中間是一個
dm-linear
設備,指定超級分區中的哪些區塊形成給定分區。這在 A/B 裝置上顯示為/dev/block/mapper/system_[a|b]
,在非 A/B 裝置上顯示為/dev/block/mapper/system
。 - 頂部有一個
dm-verity
設備,是為驗證分割區建立的。該設備驗證dm-linear
設備上的區塊是否已正確簽署。它顯示為/dev/block/mapper/system-verity
並且是/system
掛載點的來源。
圖 1 顯示了/system
掛載點下的堆疊的樣子。
圖 1. /system 掛載點下的堆疊
dm 快照
Virtual A/B 依賴dm-snapshot
,它是一個裝置映射器模組,用於對儲存裝置的狀態進行快照。使用dm-snapshot
時,有四個裝置在使用:
- 基本設備是進行快照的設備。在此頁面上,基本設備始終是動態分區,例如係統或供應商。
- 寫入時複製(COW) 設備,用於記錄對基本設備的變更。它可以是任何大小,但必須足夠大以容納對基本設備的所有更改。
- 快照設備是使用
snapshot
目標建立的。對快照設備的寫入將寫入 COW 設備。從快照設備讀取 從基本設備或 COW 設備讀取,取決於正在存取的資料是否已被快照更改。 - 來源設備是使用
snapshot-origin
目標建立的。讀取到來源設備,直接從基礎設備讀取。寫入原始設備直接寫入基礎設備,但原始資料透過寫入 COW 設備進行備份。
圖 2. dm-snapshot 的裝置映射
壓縮快照
在 Android 12 及更高版本中,由於/data
分區的空間要求可能很高,因此您可以在建置中啟用壓縮快照來滿足/data
分區的更高空間要求。
虛擬 A/B 壓縮快照基於 Android 12 及更高版本中提供的以下元件建置:
這些組件可以實現壓縮。為實現壓縮快照功能而進行的其他必要變更將在下一節中給出:壓縮快照的 COW 格式、 dm-user和Snapuserd 。
用於壓縮快照的 COW 格式
在 Android 12 及更高版本中,壓縮快照使用 COW 格式。與用於未壓縮快照的內核內建格式類似,壓縮快照的 COW 格式具有元資料和資料的交替部分。原始格式的元資料僅允許替換操作:將基礎映像中的區塊X替換為快照中區塊Y的內容。壓縮快照COW格式更具表現力,支援以下操作:
- 複製:基礎設備中的區塊X應替換為基礎設備中的區塊Y。
- 替換:基本設備中的區塊X應替換為快照中區塊Y的內容。每個塊都經過 gz 壓縮。
- 零:基本設備中的塊X應替換為全零。
- XOR :COW 設備在區塊X和區塊Y之間儲存 XOR 壓縮位元組。 (適用於 Android 13 及更高版本。)
完整的 OTA 更新僅包含替換和歸零操作。增量 OTA 更新還可以複製操作。
Android 12 中的 dm 用戶
dm-user 核心模組使userspace
能夠實現設備映射器塊設備。 dm-user 表條目在/dev/dm-user/<control-name>
下建立一個雜項設備。 userspace
進程可以輪詢設備以接收來自核心的讀寫請求。每個請求都有一個關聯的緩衝區,供用戶空間填充(用於讀取)或傳播(用於寫入)。
dm-user
核心模組為核心提供了一個新的用戶可見接口,該接口不屬於上游 kernel.org 程式碼庫。在此之前,Google 保留修改 Android 中dm-user
介面的權利。
快照程式
dm-user
的snapuserd
用戶空間元件實作虛擬 A/B 壓縮。
在 Virtual A/B 的未壓縮版本中(無論是在 Android 11 及更低版本中,還是在沒有壓縮快照選項的 Android 12 中),COW 裝置是原始檔案。啟用壓縮後,COW 將充當dm-user
設備,該設備連接到snapuserd
守護程序的實例。
核心不使用新的 COW 格式。因此snapuserd
元件在 Android COW 格式和核心內建格式之間轉換請求:
圖 3. snapuserd 作為 Android 和核心 COW 格式之間的轉換器的流程圖
這種轉換和解壓縮永遠不會在磁碟上發生。 snapuserd
元件攔截核心中發生的 COW 讀寫,並使用 Android COW 格式實作它們。
異或壓縮
對於搭載 Android 13 及更高版本的設備,預設啟用的 XOR 壓縮功能使用戶空間快照能夠在舊區塊和新區塊之間儲存 XOR 壓縮位元組。當虛擬 A/B 更新中僅更改區塊中的幾個位元組時,XOR 壓縮儲存方案使用的空間比預設儲存方案更少,因為快照不儲存完整的 4K 位元組。快照大小的減少是可能的,因為 XOR 資料包含許多零並且比原始區塊資料更容易壓縮。在 Pixel 設備上,XOR 壓縮可將快照大小減少 25% 到 40%。
對於升級到 Android 13 及更高版本的設備,必須啟用 XOR 壓縮。有關詳細信息,請參閱異或壓縮。
虛擬 A/B 壓縮過程
本部分提供有關 Android 13 和 Android 12 中使用的虛擬 A/B 壓縮過程的詳細資訊。
讀取元資料(Android 12)
元資料由snapuserd
守護程式建構。元資料主要是兩個 ID 的映射,每個 ID 8 字節,代表要合併的磁區。在dm-snapshot
中,它稱為disk_exception
。
struct disk_exception {
uint64_t old_chunk;
uint64_t new_chunk;
};
當舊資料塊被新資料塊取代時,就會使用磁碟異常。
snapuserd
守護程式透過 COW 庫讀取內部 COW 文件,並為 COW 文件中存在的每個 COW 操作建立元資料。
建立dm- snapshot
裝置時,會從核心中的dm-snapshot
啟動元資料讀取。
下圖提供了元資料建構的IO路徑的時序圖。
圖 4.元資料建構中 IO 路徑的序列流
合併(Android 12)
啟動程序完成後,更新引擎會將插槽標記為啟動成功,並透過將dm-snapshot
目標切換到dm-snapshot-merge
目標來啟動合併。
dm-snapshot
遍曆元資料並為每個磁碟異常啟動合併 IO。合併 IO 路徑的高階概述如下所示。
圖 5.合併 IO 路徑概述
如果在合併過程中裝置重新啟動,則合併將在下次重新啟動時恢復,並完成合併。
設備映射器分層
對於搭載 Android 13 及更高版本的設備,虛擬 A/B 壓縮中的快照和快照合併過程由snapuserd
用戶空間元件執行。對於升級到 Android 13 及更高版本的設備,必須啟用此功能。有關詳細信息,請參閱用戶空間合併。
以下介紹Virtual A/B壓縮過程:
- 該框架將
/system
分區掛載在dm-verity
設備上,該設備堆疊在dm-user
設備之上。這意味著來自根檔案系統的每個 I/O 都會路由到dm-user
。 -
dm-user
將 I/O 路由到用戶空間snapuserd
守護進程,該守護程序處理 I/O 請求。 - 合併操作完成後,框架會將
dm-verity
折疊到dm-linear
(system_base
) 之上並刪除dm-user
。
圖 6.虛擬 A/B 壓縮過程
快照合併過程可以中斷。如果在合併過程中裝置重新啟動,合併過程將在重新啟動後恢復。
初始化轉換
使用壓縮快照啟動時,第一階段 init 必須啟動snapuserd
來掛載分割區。這帶來了一個問題:當載入並強制執行sepolicy
時, snapuserd
會被置於錯誤的上下文中,其讀取請求會因 selinux 拒絕而失敗。
為了解決這個問題, snapuserd
與init
同步轉換,如下所示:
- 第一階段
init
從 ramdisk 啟動snapuserd
,並將開啟的檔案描述符儲存到環境變數。 - 第一階段
init
將根檔案系統切換到系統分割區,然後執行init
的系統副本。 -
init
的系統副本將組合的 sepolicy 讀取到字串中。 -
Init
在所有 ext4 支援的頁面上呼叫mlock()
。然後,它會停用快照裝置的所有裝置映射器表,並停止snapuserd
。此後禁止從分區讀取,因為這樣做會導致死鎖。 - 使用
snapuserd
的 ramdisk 副本的開啟描述符,init
使用正確的 selinux 上下文重新啟動守護程序。快照設備的設備映射器表被重新啟動。 - Init 呼叫
munlockall()
- 再次執行 IO 是安全的。
空間使用
下表提供了使用 Pixel 作業系統和 OTA 大小的不同 OTA 機制的空間使用情況比較。
尺寸影響 | 非A/B | A/B | 虛擬A/B | 虛擬 A/B(壓縮) |
---|---|---|---|---|
原廠圖片 | 4.5GB超大(3.8G鏡像+700M預留) 1 | 9GB超級(3.8G + 700M預留,兩個插槽) | 4.5GB超大(3.8G鏡像+700M預留) | 4.5GB超大(3.8G鏡像+700M預留) |
其他靜態分區 | /快取 | 沒有任何 | 沒有任何 | 沒有任何 |
OTA 期間的額外儲存空間(應用 OTA 後返回的空間) | /數據 1.4GB | 0 | /數據 3.8GB 2 | /數據 2.1GB 2 |
應用 OTA 所需的總儲存空間 | 5.9GB 3 (超級和數據) | 9GB(超級) | 8.3GB 3 (超級和數據) | 6.6GB 3 (超級和數據) |
1表示基於像素映射的假設佈局。
2假設新系統映像與原始系統映像大小相同。
3在重新啟動之前,空間需求是暫時的。
若要實作虛擬 A/B,或使用壓縮快照功能,請參閱實作虛擬 A/B