A/B(無縫)系統更新

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驗證啟動不需要 A/B 更新。)

關於A/B系統更新

A/B 更新需要對客戶端和系統進行更改。但是,OTA 套件伺服器不應需要更改:更新套件仍透過 HTTPS 提供服務。對於使用Google 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將告訴引導程式在下次重新啟動時啟動到新作業系統。如果新作業系統無法啟動,引導程式將回退到舊作業系統,因此用戶端無需執行任何操作。如果更新失敗,客戶端需要根據詳細的錯誤代碼決定何時(以及是否)重試。例如,好的客戶端可以識別出部分(「diff」)OTA 套件失敗,並嘗試使用完整的 OTA 套件。

或者,客戶可以:

  • 顯示要求使用者重新啟動的通知。如果您想實施鼓勵使用者定期更新的策略,則可以將此通知新增至您的用戶端。如果客戶端不提示用戶,那麼用戶無論如何都會在下次重新啟動時獲得更新。 (Google 的客戶端有一個每次更新可設定的延遲。)
  • 顯示一則通知,告訴使用者是否啟動到新的作業系統版本,或是否應該啟動但又回到舊的作業系統版本。 (Google 的客戶通常兩者都不做。)

在系統方面,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和套件管理器之間劃分:

有關工作範例,請參閱/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除了目前日誌之外,最近的 5 個日誌也保存在/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進行串流傳輸來啟用串流更新。製造商可以利用該套件是串流變體的事實來向客戶端發送標誌,以觸發作為串流傳輸到框架端。

Payload可用後,更新流程如下:

活動
1使用markBootSuccessful()將目前槽(或「來源槽」)標記為成功(如果尚未標記)。
2透過呼叫函數setSlotAsUnbootable()將未使用的插槽(或「目標插槽」)標記為不可啟動。當前插槽始終在更新開始時標記為成功,以防止引導程式回退到未使用的插槽,該插槽很快就會出現無效資料。如果系統已達到可以開始應用更新的程度,即使其他主要元件損壞(例如崩潰循環中的 UI),當前插槽也會被標記為成功,因為可以推送新軟體來修復這些問題問題。

更新有效負載是一個不透明的 blob,其中包含更新到新版本的說明。更新負載由以下部分組成:
  • 元數據。元資料是更新有效負載中相對較小的一部分,包含用於在目標槽上產生和驗證新版本的操作清單。例如,操作可以解壓縮某個 blob 並將其寫入目標分割區中的特定區塊,或從來源分割區讀取、套用二進位補丁並寫入目標分割區中的某些區塊。
  • 額外數據。作為更新有效負載的主體,與操作相關的額外資料由這些範例中的壓縮 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命令。

為了使安裝後成功,舊核心必須能夠:

  • 掛載新的檔案系統格式。檔案系統類型不能更改,除非舊核心支援它,包括使用壓縮檔案系統(即 SquashFS)時使用的壓縮演算法等詳細資訊。
  • 了解新分割區的安裝後程式格式。如果使用可執行和可連結格式(ELF) 二進位文件,它應該與舊核心相容(例如,如果體系結構從32 位元版本切換到64 位元版本,則在舊32 位元核心上運行的64 位元新程式) 。除非指示載入程式 ( 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 ,在裝置重新啟動前設定存取權限,並在系統成功啟動到新版本後刪除已擷取的檔案。