A/B 系統更新,也稱為無縫更新,可確保在無線 (OTA) 更新期間磁盤上保留可用的啟動系統。這種方法降低了更新後設備處於非活動狀態的可能性,這意味著維修和保修中心的設備更換和設備刷新次數更少。其他商業級操作系統,如ChromeOS,也成功地使用了 A/B 更新。
有關 A/B 系統更新及其工作方式的更多信息,請參閱分區選擇(槽) 。
A/B 系統更新提供以下好處:
- OTA 更新可以在系統運行時進行,不會打擾用戶。用戶可以在 OTA 期間繼續使用他們的設備——更新期間唯一的停機時間是設備重新啟動到更新的磁盤分區時。
- 更新後,重新啟動時間不會比常規重新啟動時間長。
- 如果OTA申請失敗(比如刷機壞了),用戶不會受到影響。用戶將繼續運行舊操作系統,客戶端可以自由地重新嘗試更新。
- 如果應用了 OTA 更新但無法啟動,設備將重新啟動回到舊分區並保持可用。客戶端可以自由地重新嘗試更新。
- 任何錯誤(例如 I/O 錯誤)只會影響未使用的分區集,並且可以重試。此類錯誤也變得不太可能,因為 I/O 負載有意降低以避免降低用戶體驗。
- 更新可以流式傳輸到 A/B 設備,無需在安裝之前下載包。流意味著用戶不需要有足夠的可用空間來將更新包存儲在
/data
或/cache
上。 - 緩存分區不再用於存儲OTA更新包,因此無需確保緩存分區足夠大以備將來更新。
- dm-verity保證設備將引導未損壞的映像。如果設備由於錯誤的 OTA 或 dm-verity 問題而無法啟動,則設備可以重新啟動到舊映像。 (Android Verified Boot不需要 A/B 更新。)
關於 A/B 系統更新
A/B 更新需要對客戶端和系統進行更改。然而,OTA 包服務器不需要更改:更新包仍然通過 HTTPS 提供。對於使用谷歌 OTA 基礎架構的設備,系統更改都在 AOSP 中,客戶端代碼由 Google Play 服務提供。未使用谷歌 OTA 基礎設施的 OEM 將能夠重用 AOSP 系統代碼,但需要提供自己的客戶端。
對於為自己的客戶供貨的 OEM,客戶需要:
- 決定何時進行更新。因為 A/B 更新發生在後台,所以它們不再由用戶啟動。為避免打擾用戶,建議在設備處於空閒維護模式(例如夜間和 Wi-Fi)時安排更新。但是,您的客戶可以使用您想要的任何啟發式方法。
- 檢查您的 OTA 包服務器並確定是否有更新可用。這應該與您現有的客戶端代碼基本相同,除了您要表明設備支持 A/B。 (Google 的客戶端還包含一個“立即檢查”按鈕,供用戶檢查最新更新。)
- 使用更新包的 HTTPS URL 調用
update_engine
,假設一個可用。update_engine
將在流式傳輸更新包時更新當前未使用分區上的原始塊。 - 根據
update_engine
結果代碼向您的服務器報告安裝成功或失敗。如果更新應用成功,update_engine
將告訴引導加載程序在下次重啟時引導至新操作系統。如果新操作系統無法啟動,引導加載程序將回退到舊操作系統,因此客戶端不需要任何工作。如果更新失敗,客戶端需要根據詳細的錯誤代碼決定何時(以及是否)重試。例如,一個好的客戶可以識別出部分(“差異”)OTA 包失敗並嘗試使用完整的 OTA 包。
可選地,客戶端可以:
- 顯示要求用戶重啟的通知。如果您想實施鼓勵用戶定期更新的策略,則可以將此通知添加到您的客戶端。如果客戶端不提示用戶,那麼用戶無論如何都會在下次重啟時獲得更新。 (谷歌的客戶端有一個每次更新的可配置延遲。)
- 顯示一條通知,告訴用戶他們是否啟動到新的操作系統版本,或者他們是否應該這樣做但又退回到舊的操作系統版本。 (谷歌的客戶通常兩者都不做。)
在系統方面,A/B 系統更新會影響以下方面:
- 分區選擇(槽)、
update_engine
守護程序和引導加載程序交互(如下所述) - 構建過程和 OTA 更新包生成(在實施 A/B 更新中描述)
分區選擇(槽)
A/B 系統更新使用兩組稱為插槽的分區(通常是插槽 A 和插槽 B)。系統從當前插槽運行,而未使用插槽中的分區在正常操作期間不會被正在運行的系統訪問。這種方法通過將未使用的插槽保留為後備來使更新具有抗故障性:如果在更新期間或更新後立即發生錯誤,系統可以回滾到舊插槽並繼續運行系統。為實現這一目標,當前插槽使用的分區不應作為 OTA 更新的一部分進行更新(包括只有一個副本的分區)。
每個插槽都有一個可引導屬性,表明該插槽是否包含設備可以從中引導的正確係統。當前插槽在系統運行時是可引導的,但另一個插槽可能有舊的(仍然正確的)系統版本、新版本或無效數據。無論當前插槽是什麼,都有一個插槽是活動插槽(引導加載程序將在下次啟動時從中啟動的插槽)或首選插槽。
每個插槽也有一個由用戶空間設置的成功屬性,只有當插槽也是可引導時才相關。一個成功的插槽應該能夠啟動、運行和自我更新。引導加載程序應將未標記為成功的可引導插槽(在多次嘗試從中引導後)標記為不可引導,包括將活動插槽更改為另一個可引導插槽(通常更改為在嘗試引導之前立即運行的插槽進入新的、活躍的)。接口的具體細節定義在boot_control.h
中。
更新引擎守護進程
A/B 系統更新使用名為update_engine
的後台守護進程來準備系統啟動到新的更新版本。該守護進程可以執行以下操作:
- 根據 OTA 包的指示,從當前插槽 A/B 分區讀取並將任何數據寫入未使用的插槽 A/B 分區。
- 在預定義的工作流程中調用
boot_control
接口。 - 按照 OTA 包的指示,在寫入所有未使用的插槽分區後,從新分區運行安裝後程序。 (有關詳細信息,請參閱安裝後)。
由於update_engine
守護進程本身不參與引導過程,因此在更新期間它可以執行的操作受到當前插槽中的SELinux策略和功能的限制(這些策略和功能在系統引導進入新版本)。為了保持系統的穩健性,更新過程不應修改分區表、當前插槽中的分區內容或無法通過恢復出廠設置擦除的非 A/B 分區的內容。
更新引擎源
update_engine
源位於system/update_engine
。 A/B OTA dexopt 文件在installd
和包管理器之間拆分:
-
frameworks/native/cmds/installd/
ota* 包括安裝後腳本、chroot 的二進製文件、調用 dex2oat 的安裝克隆、OTA 後移動工件腳本和移動腳本的 rc 文件。 -
frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java
(加上OtaDexoptShellCommand
)是為應用程序準備 dex2oat 命令的包管理器。
有關工作示例,請參閱/device/google/marlin/device-common.mk
。
更新引擎日誌
對於 Android 8.x 版本和更早版本,可以在logcat
和錯誤報告中找到update_engine
日誌。要使update_engine
日誌在文件系統中可用,請將以下更改修補到您的構建中:
這些更改將最新的update_engine
日誌副本保存到/data/misc/update_engine_log/update_engine. YEAR - TIME
除了當前日誌,最近的五個日誌保存在/data/misc/update_engine_log/
下。具有日誌組 ID 的用戶將能夠訪問文件系統日誌。
引導加載程序交互
update_engine
(可能還有其他守護進程)使用boot_control
HAL 來指示引導加載程序從什麼引導。常見示例場景及其相關狀態包括:
- 正常情況:系統從其當前插槽(插槽 A 或 B)運行。到目前為止尚未應用任何更新。系統的當前插槽是可引導的、成功的和活動的插槽。
- 正在進行更新:系統正在從插槽 B 運行,因此插槽 B 是可引導的、成功的和活動的插槽。插槽 A 被標記為無法啟動,因為插槽 A 的內容正在更新但尚未完成。在此狀態下重新啟動應繼續從插槽 B 啟動。
- 更新已應用,重啟掛起:系統正在從插槽 B 運行,插槽 B 可引導且成功,但插槽 A 被標記為活動(因此被標記為可引導)。插槽 A 尚未標記為成功,引導加載程序應嘗試從插槽 A 引導一些次數。
- 系統重新啟動到新的更新:系統是第一次從插槽 A 運行,插槽 B 仍然可以啟動並成功,而插槽 A 只能啟動,並且仍然處於活動狀態但未成功。用戶空間守護進程
update_verifier
應在進行一些檢查後將插槽 A 標記為成功。
流式更新支持
用戶設備在/data
上並不總是有足夠的空間來下載更新包。由於 OEM 和用戶都不想浪費/cache
分區上的空間,一些用戶沒有更新,因為設備無處可存儲更新包。為解決此問題,Android 8.0 添加了對流式 A/B 更新的支持,即在下載塊時將塊直接寫入 B 分區,而無需將塊存儲在/data
上。流式 A/B 更新幾乎不需要臨時存儲,只需要足夠的存儲空間來存儲大約 100 KiB 的元數據。
要在 Android 7.1 中啟用流式更新,請選擇以下補丁:
無論是使用Google 移動服務 (GMS)還是任何其他更新客戶端,都需要這些補丁來支持 Android 7.1 及更高版本中的流式 A/B 更新。
A/B 更新的生命週期
當 OTA 包(在代碼中稱為有效載荷)可供下載時,更新過程開始。設備中的策略可能會根據電池電量、用戶活動、充電狀態或其他策略延遲有效負載下載和應用程序。此外,由於更新在後台運行,用戶可能不知道正在進行更新。所有這些都意味著更新過程可能會因策略、意外重啟或用戶操作而隨時中斷。
可選地,OTA 包本身中的元數據表明可以流式傳輸更新;同樣的包也可以用於非流式安裝。服務器可以使用元數據告訴客戶端它正在流式傳輸,以便客戶端將 OTA 正確地移交給update_engine
。擁有自己的服務器和客戶端的設備製造商可以通過確保服務器識別更新正在流式傳輸(或假設所有更新都在流式傳輸)並且客戶端正確調用update_engine
進行流式傳輸來啟用流式更新。製造商可以利用包是流式變體這一事實來向客戶端發送一個標誌,以觸發作為流式傳輸到框架端的切換。
有效負載可用後,更新過程如下:
步 | 活動 |
---|---|
1個 | 使用markBootSuccessful() 將當前插槽(或“源插槽”)標記為成功(如果尚未標記)。 |
2個 | 通過調用函數setSlotAsUnbootable() 將未使用的插槽(或“目標插槽”)標記為不可啟動。當前插槽始終在更新開始時標記為成功,以防止引導加載程序回退到未使用的插槽,該插槽很快就會有無效數據。如果系統已經達到可以開始應用更新的程度,即使其他主要組件損壞(例如崩潰循環中的 UI),當前插槽也會被標記為成功,因為可以推送新軟件來修復這些問題。更新有效負載是一個不透明的 blob,其中包含更新到新版本的說明。更新有效負載包括以下內容:
|
3個 | 下載負載元數據。 |
4個 | 對於元數據中定義的每個操作,關聯數據(如果有的話)按順序下載到內存,應用操作,並丟棄關聯內存。 |
5個 | 整個分區被重新讀取並根據預期的哈希值進行驗證。 |
6個 | 運行安裝後步驟(如果有)。如果在任何步驟的執行過程中出現錯誤,更新將失敗,並可能使用不同的有效負載重新嘗試。如果到目前為止所有步驟都成功,則更新成功並執行最後一步。 |
7 | 未使用的插槽通過調用setActiveBootSlot() 標記為活動的。將未使用的插槽標記為活動並不意味著它將完成引導。如果引導加載程序(或系統本身)沒有讀取到成功狀態,它可以將活動插槽切換回來。 |
8個 | 安裝後(如下所述)涉及從“新更新”版本運行程序,同時仍在舊版本中運行。如果在 OTA 包中定義,則此步驟是強制性的,程序必須以退出代碼0 返回;否則,更新失敗。 | 9 | 在系統成功引導到新插槽並完成重啟後檢查後,當前插槽(以前稱為“目標插槽”)通過調用markBootSuccessful() 標記為成功。 |
安裝後
對於定義了安裝後步驟的每個分區, update_engine
將新分區掛載到特定位置並執行 OTA 中指定的與掛載分區相關的程序。例如,如果安裝後程序在系統分區中定義為usr/bin/postinstall
,則未使用插槽中的此分區將安裝在固定位置(例如/postinstall_mount
)和/postinstall_mount/usr/bin/postinstall
執行/postinstall_mount/usr/bin/postinstall
命令。
要使後安裝成功,舊內核必須能夠:
- 安裝新的文件系統格式。除非在舊內核中支持文件系統類型,否則無法更改文件系統類型,包括詳細信息,例如使用壓縮文件系統(即 SquashFS)時使用的壓縮算法。
- 了解新分區的安裝後程序格式。如果使用可執行和可鏈接格式 (ELF) 二進製文件,它應該與舊內核兼容(例如,如果架構從 32 位構建切換到 64 位構建,則 64 位新程序在舊的 32 位內核上運行)。除非加載器 (
ld
) 被指示使用其他路徑或構建靜態二進製文件,否則將從舊系統映像而不是新系統映像加載庫。
例如,您可以使用 shell 腳本作為安裝後程序,由舊系統的 shell 二進製文件用#!
頂部的標記),然後從新環境設置庫路徑以執行更複雜的二進制安裝後程序。或者,您可以從專用的較小分區運行安裝後步驟,以更新主系統分區中的文件系統格式,而不會導致向後兼容性問題或墊腳石更新;這將允許用戶直接從出廠映像更新到最新版本。
新的安裝後程序受限於舊系統中定義的 SELinux 策略。因此,安裝後步驟適用於在給定設備上執行設計所需的任務或其他盡力而為的任務(即更新支持 A/B 的固件或引導加載程序、為新版本準備數據庫副本等。 ).安裝後步驟不適用於需要意外權限的重新啟動前的一次性錯誤修復。
選定的安裝後程序在postinstall
SELinux 上下文中運行。新掛載分區中的所有文件都將被標記為postinstall_file
,無論它們在重新引導到新系統後的屬性是什麼。新系統中 SELinux 屬性的更改不會影響安裝後步驟。如果安裝後程序需要額外權限,則必須將這些權限添加到安裝後上下文中。
重啟後
重新啟動後, update_verifier
使用 dm-verity 觸發完整性檢查。此檢查在 zygote 之前開始,以避免 Java 服務進行任何會阻止安全回滾的不可逆更改。在此過程中,如果經過驗證的引導或 dm-verity 檢測到任何損壞,引導加載程序和內核也可能觸發重新引導。檢查完成後, update_verifier
標記引導成功。
update_verifier
將只讀取/data/ota_package/care_map.txt
中列出的塊,當使用 AOSP 代碼時,它包含在 A/B OTA 包中。 Java系統更新客戶端,如GmsCore,提取care_map.txt
,在重啟設備前設置訪問權限,並在系統成功啟動進入新版本後刪除提取的文件。