2018-04-18Google_A/B(無縫)系統更新概覽-2

A/B(無縫)系統更新

A/B 系統更新(也稱為無縫更新)的目標是確保在無線下載 (OTA) 更新期間在磁盤上保留一個可正常啟動和使用的系統。采用這種方式可以降低更新之后設備無法啟動的可能性,這意味著用戶需要將設備送到維修和保修中心進行更換和刷機的情況將會減少。其他某些商業級操作系統(例如 ChromeOS)也成功運用了 A/B 更新機制。

要詳細了解 A/B 系統更新,請參見分區選擇(槽位)一節。

A/B 系統更新可帶來以下好處:

  • OTA 更新可以在系統運行期間進行,而不會打斷用戶。用戶可以在 OTA 期間繼續使用其設備。在更新期間,唯一的一次宕機發生在設備重新啟動到更新后的磁盤分區時。
  • 更新后,重新啟動所用的時間不會超過常規重新啟動所用的時間。
  • 如果 OTA 無法應用(例如,因為刷機失?。脩魧⒉粫艿接绊?。用戶將繼續運行舊的操作系統,并且客戶端可以重新嘗試進行更新。
  • 如果 OTA 更新已應用但無法啟動,設備將重新啟動回舊分區,并且仍然可以使用??蛻舳丝梢灾匦聡L試進行更新。
  • 任何錯誤(例如 I/O 錯誤)都只會影響未使用的分區組,并且用戶可以進行重試。由于 I/O 負載被特意控制在較低水平,以免影響用戶體驗,因此發生此類錯誤的可能性也會降低。
  • 更新包可以流式傳輸到 A/B 設備,因此在安裝之前不需要先下載更新包。流式更新意味著用戶沒有必要在 /data/cache 上留出足夠的可用空間來存儲更新包。
  • 緩存分區不再用于存儲 OTA 更新包,因此無需確保緩存分區的大小要足以應對日后的更新。
  • dm-verity 可保證設備將使用未損壞的啟動映像。如果設備因 OTA 錯誤或 dm-verity 問題而無法啟動,則可以重新啟動到舊映像。(Android 驗證啟動不需要 A/B 更新。)

關于 A/B 系統更新

進行 A/B 更新時,客戶端和系統都需要進行更改。不過,OTA 更新包服務器應該不需要進行更改:更新包仍通過 HTTPS 提供。對于使用 Google OTA 基礎架構的設備,系統更改全部是在 AOSP 中進行,并且客戶端代碼由 Google Play 服務提供。不使用 Google OTA 基礎架構的原始設備制造商 (OEM) 將能夠重復使用 AOSP 系統代碼,但需要自行提供客戶端。

如果 OEM 自行提供客戶端,客戶端需要:

  • 確定何時進行更新。由于 A/B 更新是在后臺進行,因此不再需要由用戶啟動。為了避免干擾用戶,建議將更新安排在設備處于閑時維護模式(如夜間)并已連接到 WLAN 網絡時進行。不過,客戶端可以使用您希望使用的任何啟發法。
  • 向 OTA 更新包服務器進行核查,確定是否有可用的更新。這應與您現有的客戶端代碼大體相同,不過您需要表明相應設備支持 A/B 更新。(Google 的客戶端還包含立即檢查按鈕,以便用戶檢查是否有最新更新。)
  • 調用 update_engine(使用 HTTPS 網址),以獲取更新包(假設有可用的更新包)。update_engine 將在流式傳輸更新包的同時,在當前未使用的分區上更新原始數據塊。
  • 根據 update_engine 結果代碼向您的服務器報告安裝是成功了還是失敗了。如果更新已成功應用,update_engine 將會告知引導加載程序在下次重新啟動時啟動到新的操作系統。如果新的操作系統無法啟動,引導加載程序將會回退到舊的操作系統,因此無需在客戶端執行任何操作。如果更新失敗,客戶端將需要根據詳細的錯誤代碼確定何時(以及是否)重試。例如,優秀的客戶端能夠識別出是一部分(“diff”)OTA 更新包失敗,并改為嘗試完整的 OTA 更新包。

客戶端可能會:

  • 顯示通知,以提醒用戶重新啟動系統。如果您想要實施鼓勵用戶定期更新的政策,則可以將該通知添加到客戶端。如果客戶端不提示用戶,用戶將會在下次重新啟動系統時收到更新。(Google 的客戶端會有延遲,該延遲可按每次更新進行配置。)
  • 顯示通知,以告知用戶他們是啟動到了新的操作系統版本,還是應啟動到新的操作系統版本,但卻回退到了舊的操作系統版本。(Google 的客戶端通常不會顯示此類通知。)

在系統方面,A/B 系統更新會影響以下各項:

  • 分區選擇(槽位)、update_engine 守護進程,以及引導加載程序交互(如下所述)
  • 編譯過程和 OTA 更新包生成(如實現 A/B 更新中所述)

注意:只有對于新設備,才建議通過 OTA 實現 A/B 系統更新。

分區選擇(槽位)

A/B 系統更新使用兩組稱為槽位(通常是槽位 A 和槽位 B)的分區。系統從“當前”槽位運行,但在正常操作期間,運行中的系統不會訪問未使用的槽位中的分區。這種方法通過將未使用的槽位保留為后備槽位,來防范更新出現問題:如果在更新期間或更新剛剛完成后出現錯誤,系統可以回滾到原來的槽位并繼續正常運行。為了實現這一目標,當前槽位使用的任何分區(包括只有一個副本的分區)都不應在 OTA 更新期間進行更新。

每個槽位都有一個“可啟動”屬性,該屬性用于表明相應槽位存儲的系統正確無誤,設備可從相應槽位啟動。系統運行時,當前槽位處于可啟動狀態,但另一個槽位則可能包含舊版本(仍然正確)的系統、包含更新版本的系統,或包含無效的數據。無論當前槽位是哪一個,都有一個槽位是活動槽位(引導加載程序在下次啟動時將使用的槽位,也稱為首選槽位)。

此外,每個槽位還都有一個由用戶空間設置的“成功”屬性,僅當相應槽位處于可啟動狀態時,該屬性才具有相關性。被標記為成功的槽位應該能夠自行啟動、運行和更新。未被標記為成功的可啟動槽位(多次嘗試使用它啟動之后)應由引導加載程序標記為不可啟動,其中包括將活動槽位更改為另一個可啟動的槽位(通常是更改為在嘗試啟動到新的活動槽位之前正在運行的槽位)。關于相應接口的具體詳細信息在[ boot_control.h](https://android.googlesource.com/platform/hardware/libhardware/+/master/include/hardware/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](https://android.googlesource.com/platform/system/update_engine/) 中。A/B OTA dexopt 文件分開放到了 installd 和一個程序包管理器中:

  • [frameworks/native/cmds/installd/](https://android.googlesource.com/platform/frameworks/native/+/master/cmds/installd/)ota* 包括安裝后腳本、用于 chroot 的二進制文件、負責調用 dex2oat 的已安裝克隆、OTA 后 move-artifacts 腳本,以及 move 腳本的 rc 文件。
  • [frameworks/base/services/core/java/com/android/server/pm/OtaDexoptService.java](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/OtaDexoptService.java)(加上 [OtaDexoptShellCommand](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java))是負責為應用準備 dex2oat 命令的程序包管理器。

如需實際示例,請參閱 [/device/google/marlin/device-common.mk](https://android.googlesource.com/device/google/marlin/+/nougat-dr1-release/device-common.mk)

更新引擎日志

對于 Android 8.x 及更低版本,可在 logcat 及錯誤報告中找到 update_engine 日志。要使 update_engine 日志可在文件系統中使用,請將以下更改添加到您的細分版本中:

這些更改會將最新的 update_engine 日志的副本保存到 /data/misc/update_engine_log/update_engine.<var style="box-sizing: inherit; color: rgb(236, 64, 122); -webkit-font-smoothing: auto; font-weight: 700;">YEAR</var>-<var style="box-sizing: inherit; color: rgb(236, 64, 122); -webkit-font-smoothing: auto; font-weight: 700;">TIME</var>。除當前日志以外,最近的五個日志也會保存在 /data/misc/update_engine_log/ 下方。擁有日志組 ID 的用戶將能夠訪問相應的文件系統日志。

引導加載程序交互

boot_control HAL 供 update_engine(可能還有其他守護進程)用于指示引導加載程序從何處啟動。常見的示例情況及其相關狀態包括:

  • 正常情況:系統正在從其當前槽位(槽位 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 更新幾乎不需要臨時存儲空間,并且只需要能夠存儲大約 100KiB 元數據的存儲空間即可。

要在 Android 7.1 中實現流式更新,請選擇以下補丁程序:

無論是使用 Google 移動服務 (GMS),還是使用任何其他更新客戶端,都需要安裝這些補丁程序,才能在 Android 7.1 中支持流式傳輸 A/B 更新包。

A/B 更新過程

當有 OTA 更新包(在代碼中稱為有效負載)可供下載時,更新流程便開始了。設備中的政策可以根據電池電量、用戶活動、充電狀態或其他政策來延遲下載和應用有效負載。此外,由于更新是在后臺運行,因此用戶可能并不知道正在進行更新。所有這些都意味著,更新流程可能隨時會由于政策、意外重新啟動或用戶操作而中斷。

OTA 更新包本身所含的元數據可能會指示可進行流式更新,在這種情況下,相應更新包也可采用非流式安裝方式。服務器可以利用這些元數據告訴客戶端正在進行流式更新,以便客戶端正確地將 OTA 移交給 update_engine。如果設備制造商具有自己的服務器和客戶端,便可以通過確保以下兩項來實現流式更新:確保服務器能夠識別出更新是流式更新(或假定所有更新都是流式更新),并確保客戶端能夠正確調用 update_engine 來進行流式更新。制造商可以根據更新包是流式更新變體這一事實向客戶端發送一個標記,以便在進行流式更新時觸發向框架端的移交工作。

有可用的有效負載后,更新流程將遵循如下步驟:

| 步驟 | 操作 |
| 1 | 通過 markBootSuccessful() 將當前槽位(或“源槽位”)標記為成功(如果尚未標記)。 |
| 2 | 調用函數 setSlotAsUnbootable(),將未使用的槽位(或“目標槽位”)標記為不可啟動。當前槽位始終會在更新開始時被標記為成功,以防止引導加載程序回退到未使用的槽位(該槽位中很快將會有無效數據)。如果系統已做好準備,可以開始應用更新,那么即使其他主要組件出現損壞(例如界面陷入崩潰循環),當前槽位也會被標記為成功,因為可以通過推送新軟件來解決這些問題。

更新有效負載是不透明的 Blob,其中包含更新到新版本的指示。更新有效負載由以下部分組成:

  • 元數據。元數據在更新有效負載中所占的比重相對較小,其中包含一系列用于在目標槽位上生成和驗證新版本的操作。例如,某項操作可能會解壓縮特定 Blob 并將其寫入到目標分區中的特定塊,或者從源分區讀取數據、應用二進制補丁程序,然后寫入到目標分區中的特定塊。
  • 額外數據。與操作相關的額外數據在更新有效負載中占據了大部分比重,其中包含這些示例中的已壓縮 Blob 或二進制補丁程序。

|
| 3 | 下載有效負載元數據。 |
| 4 | 對于元數據中定義的每項操作,都將按順序發生以下行為:將相關數據(如果有)下載到內存中、應用操作,然后釋放關聯的內存。 |
| 5 | 對照預期的哈希重新讀取并驗證所有分區。 |
| 6 | 運行安裝后步驟(如果有)。如果在執行任何步驟期間出現錯誤,則更新失敗,系統可能會通過其他有效負載重新嘗試更新。如果上述所有步驟均已成功完成,則更新成功,系統會執行最后一個步驟。 |
| 7 | 調用 setActiveBootSlot(),將未使用的槽位標記為活動槽位。將未使用的槽位標記為活動槽位并不意味著它將完成啟動。如果引導加載程序(或系統本身)未讀取到“成功”狀態,則可以將活動槽位切換回來。 |
| 8 | 安裝后步驟(如下所述)包括從“新更新”版本中運行仍在舊版本中運行的程序。如果此步驟已在 OTA 更新包中定義,則為強制性步驟,且程序必須返回并顯示退出代碼 0,否則更新失敗。 |
| 9 | 在系統足夠深入地成功啟動到新槽位并完成重新啟動后檢查之后,系統會調用 markBootSuccessful(),將現在的當前槽位(原“目標槽位”)標記為成功。 |

注意:第 3 步和第 4 步占用了大部分更新時間,因為這兩個步驟涉及寫入和下載大量數據,并且可能會因政策或重新啟動等原因而中斷。

安裝后

對于定義了安裝后步驟的每個分區,update_engine 都會將新分區裝載到特定位置,并執行與裝載的分區相關的 OTA 中指定的程序。例如,如果安裝后程序被定義為相應系統分區中的 usr/bin/postinstall,則系統會將未使用槽位中的這個分區裝載到一個固定位置(例如 /postinstall_mount),然后執行 /postinstall_mount/usr/bin/postinstall命令。

為確保成功執行安裝后步驟,舊內核必須能夠:

  • 裝載新的文件系統格式。文件系統類型不能更改(除非舊內核中支持這么做),包括使用的壓縮算法(如果使用 SquashFS 等經過壓縮的文件系統)等詳細信息。
  • 理解新分區的安裝后程序格式。如果使用可執行且可鏈接格式 (ELF) 的二進制文件,則該文件應該與舊內核兼容(例如,如果架構從 32 位細分版本改為使用 64 位細分版本,則 64 位的新程序應該可以在舊的 32 位內核上運行)。除非加載程序 (ld) 收到使用其他路徑或編譯靜態二進制文件的指令,否則將會從舊系統映像而非新系統映像加載各種庫。

例如,您可以使用 shell 腳本作為安裝后程序(由舊系統中頂部包含 #! 標記的 shell 二進制文件解析),然后從新環境設置庫路徑,以便執行更復雜的二進制安裝后程序?;蛘?,您可以從專用的較小分區執行安裝后步驟,以便主系統分區中的文件系統格式可以得到更新,同時不會產生向后兼容問題或引發 stepping-stone 更新;這樣一來,用戶便可以從出廠映像直接更新到最新版本。

新的安裝后程序將受舊系統中定義的 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 并設置訪問權限,在系統成功啟動到新版本后會刪除所提取的文件。

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

上次更新日期:四月 3, 2018

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容