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服務提供。不使用 Google OTA 基礎設施的 OEM 將能夠重用 AOSP 系統代碼,但需要提供自己的客戶端。
對於為自己的客戶提供服務的 OEM,客戶需要:
- 決定何時進行更新。因為 A/B 更新發生在後台,它們不再是用戶啟動的。為避免干擾用戶,建議在設備處於空閒維護模式(例如夜間)和 Wi-Fi 時安排更新。但是,您的客戶可以使用您想要的任何啟發式方法。
- 檢查您的 OTA 軟件包服務器並確定是否有可用的更新。這應該與您現有的客戶端代碼基本相同,除了您需要發出設備支持 A/B 的信號。 (Google 的客戶端還包括一個“立即檢查”按鈕,供用戶檢查最新更新。)
- 使用更新包的 HTTPS URL 調用
update_engine
,假設有一個可用。update_engine
將在流式傳輸更新包時更新當前未使用分區上的原始塊。 - 根據
update_engine
結果代碼向您的服務器報告安裝成功或失敗。如果更新成功應用,update_engine
將告訴引導加載程序在下次重新啟動時啟動到新操作系統。如果新操作系統無法啟動,引導加載程序將回退到舊操作系統,因此客戶端無需執行任何操作。如果更新失敗,客戶端需要根據詳細的錯誤代碼決定何時(以及是否)重試。例如,一個好的客戶端可以識別出部分(“差異”)OTA 包失敗,並嘗試使用完整的 OTA 包。
或者,客戶可以:
- 顯示要求用戶重新啟動的通知。如果您想實施鼓勵用戶定期更新的策略,則可以將此通知添加到您的客戶端。如果客戶端不提示用戶,那麼用戶將在下次重新啟動時獲得更新。 (Google 的客戶端具有每次更新可配置的延遲。)
- 顯示一條通知,告知用戶他們是否啟動到新的操作系統版本,或者他們是否應該這樣做但又退回到舊的操作系統版本。 (谷歌的客戶通常兩者都不做。)
在系統方面,A/B 系統更新會影響以下方面:
- 分區選擇(插槽)、
update_engine
守護進程和引導加載程序交互(如下所述) - 構建過程和 OTA 更新包生成(在實施 A/B 更新中描述)
分區選擇(槽)
A/B 系統更新使用稱為插槽的兩組分區(通常是插槽 A 和插槽 B)。系統從當前插槽運行,而未使用插槽中的分區在正常運行期間不被正在運行的系統訪問。這種方法通過將未使用的插槽保留為後備來使更新具有抗故障性:如果在更新期間或更新後立即發生錯誤,系統可以回滾到舊插槽並繼續擁有工作系統。為了實現這一目標,當前插槽使用的任何分區都不應作為 OTA 更新的一部分進行更新(包括只有一個副本的分區)。
每個插槽都有一個可引導屬性,說明該插槽是否包含設備可以從中引導的正確係統。當前插槽在系統運行時是可引導的,但另一個插槽可能有舊的(仍然正確的)系統版本、較新的版本或無效數據。無論當前插槽是什麼,都有一個插槽是活動插槽(引導加載程序將在下次引導時引導形成的插槽)或首選插槽。
每個插槽還具有由用戶空間設置的成功屬性,僅當插槽也是可引導時才相關。一個成功的插槽應該能夠啟動、運行和更新自身。未標記為成功的可引導插槽(在多次嘗試從其引導之後)應由引導加載程序標記為不可引導,包括將活動插槽更改為另一個可引導插槽(通常是在嘗試引導之前立即運行的插槽進入新的、活躍的)。接口的具體細節在boot_control.h
中定義。
更新引擎守護進程
A/B 系統更新使用名為update_engine
的後台守護程序來準備系統以啟動到新的更新版本。此守護程序可以執行以下操作:
- 根據 OTA 包的指示,從當前 slot A/B 分區讀取數據,並將任何數據寫入未使用的 slot 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* 包括 postinstall 腳本、chroot 的二進製文件、調用 dex2oat 的 installd 克隆、post-OTA move-artifacts 腳本和 move 腳本的 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 位構建,則在舊 32 位內核上運行的 64 位新程序)。除非指示加載器 (
ld
) 使用其他路徑或構建靜態二進製文件,否則庫將從舊系統映像而不是新系統映像加載。
例如,您可以將 shell 腳本用作安裝後程序,由舊系統的 shell 二進製文件用#!
解釋。標記在頂部),然後設置來自新環境的庫路徑以執行更複雜的二進制安裝後程序。或者,您可以從專用的較小分區運行安裝後步驟,以更新主系統分區中的文件系統格式,而不會導致向後兼容性問題或墊腳石更新;這將允許用戶從工廠映像直接更新到最新版本。
新的安裝後程序受到舊系統中定義的 SELinux 策略的限制。因此,安裝後步驟適用於在給定設備上執行設計所需的任務或其他盡力而為的任務(即更新支持 A/B 的固件或引導加載程序、為新版本準備數據庫副本等。 )。安裝後步驟不適用於需要意外權限的重啟前一次性錯誤修復。
選定的安裝後程序在postinstall
上下文中運行。新掛載分區中的所有文件都將用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
,在重啟設備前設置訪問權限,在系統成功啟動進入新版本後刪除提取的文件。