A/B (無縫) 系統更新

舊版 A/B 系統更新 (又稱無縫更新) 可確保在 無線 (OTA) 更新期間,磁碟上仍有可運作的開機系統。這種做法可降低更新後裝置無法運作的機率,因此維修和保固中心需要更換裝置和重新刷機的次數也會減少。其他商用級作業系統 (例如 ChromeOS) 也成功使用 A/B 更新。

如要進一步瞭解 A/B 系統更新和運作方式,請參閱「分區選取 (插槽)」。

A/B 系統更新具有下列優點:

  • OTA 更新可以在系統執行時進行,不會中斷使用者操作。使用者在 OTA 期間仍可繼續使用裝置,更新期間的唯一停機時間是裝置重新啟動至更新後的磁碟分割區。
  • 更新後重新啟動的時間不會比一般重新啟動長。
  • 如果 OTA 無法套用 (例如因為快閃記憶體損壞),使用者不會受到影響。使用者會繼續執行舊版 OS,用戶端可以自由重新嘗試更新。
  • 如果套用 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 服務提供。如果 OEM 未使用 Google 的 OTA 基礎架構,可以重複使用 AOSP 系統程式碼,但必須提供自己的用戶端。

如果 OEM 提供自己的用戶端,則用戶端必須:

  • 決定更新時間。由於 A/B 更新會在背景執行,因此不再需要使用者啟動。為避免干擾使用者,建議您在裝置處於閒置維護模式時 (例如夜間) 透過 Wi-Fi 安排更新。不過,用戶端可以使用任何您想要的啟發式方法。
  • 與 OTA 封裝伺服器聯絡,判斷是否有可用的更新。這部分應與現有的用戶端程式碼大致相同,但您需要發出信號,表示裝置支援 A/B 測試。(Google 的用戶端也提供「立即檢查」按鈕,供使用者檢查是否有最新更新。)
  • 如果更新套件的 HTTPS 網址可用,請呼叫 update_engineupdate_engine 會在目前未使用的分區上更新原始區塊,同時串流更新套件。
  • 根據 update_engine 結果代碼,向伺服器回報安裝成功或失敗。如果更新成功,update_engine 會在下次重新啟動時,指示開機載入器啟動新作業系統。如果新版 OS 無法啟動,開機載入程式會回復為舊版 OS,因此用戶端不需要執行任何工作。如果更新失敗,用戶端需要根據詳細的錯誤代碼,決定何時 (以及是否) 要再試一次。舉例來說,優質的用戶端可以辨識出部分 (「差異」) OTA 封裝失敗,並改為嘗試完整 OTA 封裝。

用戶端也可以選擇執行下列操作:

  • 顯示通知,要求使用者重新啟動。如果您想實施一項政策,鼓勵使用者定期更新,則可以在用戶端中加入這項通知。 如果用戶端未提示使用者,使用者下次重新啟動時仍會收到更新。(Google 的用戶端可設定每次更新的延遲時間)。
  • 顯示通知,告知使用者是否已啟動新版作業系統,或是否應啟動新版作業系統,但卻還原為舊版作業系統。(Google 的用戶端通常也不會這麼做)。

在系統端,A/B 系統更新會影響下列項目:

  • 磁碟分割區選取 (插槽)、update_engine 精靈和開機載入程式互動 (如下所述)
  • 建構程序和 OTA 更新套件產生作業 (詳情請參閱「實作 A/B 更新」)

選取分區 (時段)

A/B 系統更新會使用兩組分區,稱為「位置」 (通常是位置 A 和位置 B)。系統會從「目前」插槽執行,而「未使用」插槽中的分割區在正常運作期間不會由執行中的系統存取。這種做法可將未使用的插槽做為備用插槽,讓更新作業具備容錯能力:如果在更新期間或更新後立即發生錯誤,系統可以回溯至舊插槽,繼續使用正常運作的系統。為達成這個目標,OTA 更新不應更新目前插槽使用的任何分割區 (包括只有一個副本的分割區)。

每個插槽都有 bootable 屬性,指出插槽是否含有正確的系統,可供裝置啟動。系統執行時,目前使用的插槽可供開機,但另一個插槽可能含有舊版 (但仍正確) 的系統、新版系統或無效資料。無論目前是哪個運算單元,都會有一個有效運算單元 (開機載入器會在下次開機時從這個運算單元啟動),或是偏好運算單元。

每個插槽也都有使用者空間設定的「成功」屬性,只有在插槽可啟動時才相關。成功的運算單元應可啟動、執行及更新自身。如果可開機的插槽未標示為成功 (嘗試從該插槽開機多次後),開機載入程式應將該插槽標示為無法開機,包括將有效插槽變更為另一個可開機的插槽 (通常是嘗試開機進入新有效插槽前,立即執行的插槽)。介面的具體詳細資料定義在 boot_control.h 中。

更新引擎精靈

A/B 系統更新會使用名為 update_engine 的背景精靈,準備讓系統啟動新的更新版本。這個常駐程式可以執行下列動作:

  • 從目前的 A/B 分區讀取資料,並按照 OTA 封包的指示,將資料寫入未使用的 A/B 分區。
  • 在預先定義的工作流程中呼叫 boot_control 介面。
  • 按照 OTA 封裝的指示,在寫入所有未使用的插槽分割區後,從 new 分割區執行 post-install 程式。(詳情請參閱「安裝後」)。

由於 update_engine 精靈本身並未參與啟動程序,因此在更新期間,目前的 Slot 中的 SELinux 政策和功能會限制其可執行的動作 (這些政策和功能必須等到系統啟動新版本後才能更新)。為維護穩固的系統,更新程序不應修改分區表、目前插槽中的分區內容,或無法透過恢復原廠設定清除的非 A/B 分區內容。

更新引擎來源

update_engine 來源位於 system/update_engine。A/B OTA dexopt 檔案會分別存放在 installd 和套件管理員中:

如需實際運作的範例,請參閱 /device/google/marlin/device-common.mk

更新引擎記錄

如果是 Android 8.x 以前的版本,update_engine 記錄會位於 logcat 和錯誤報告中。如要在檔案系統中提供 update_engine 記錄,請將下列變更修補到建構作業中:

這些變更會將最新的 update_engine 記錄副本儲存到 /data/misc/update_engine_log/update_engine.YEAR-TIME。除了目前的記錄外,系統還會在 /data/misc/update_engine_log/ 下方儲存最近五筆記錄。擁有 log 群組 ID 的使用者可以存取檔案系統記錄。

系統啟動載入程式互動

update_engine (和其他可能的常駐程式) 會使用 boot_control HAL,指示開機載入器要從何處開機。常見情境範例和相關聯的狀態包括:

  • 正常情況:系統目前是從 A 或 B 插槽執行,目前尚未套用任何更新。系統目前的插槽可啟動、成功, 且為有效插槽。
  • 更新作業進行中:系統目前從插槽 B 執行,因此插槽 B 是可啟動、成功且有效的插槽。由於系統正在更新 Slot A 的內容,但尚未完成,因此 Slot 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 中啟用串流更新,請挑選下列修補程式:

如要支援 Android 7.1 以上版本的串流 A/B 更新,無論是使用 Google 行動服務 (GMS) 或任何其他更新用戶端,都必須套用這些修補程式。

A/B 更新的生命週期

當系統可下載無線更新 (在程式碼中稱為「有效負載」payload) 時,更新程序就會開始。裝置中的政策可能會根據電量、使用者活動、充電狀態或其他政策,延後下載及套用酬載。此外,由於更新會在背景執行,使用者可能不知道更新正在進行中。這表示更新程序隨時可能因政策、意外重新啟動或使用者動作而中斷。

此外,OTA 套件中的中繼資料會指出更新可以串流傳輸;同一個套件也可以用於非串流安裝。伺服器可能會使用中繼資料告知用戶端正在串流,以便用戶端正確交接 OTA update_engine。如果裝置製造商有自己的伺服器和用戶端,只要確保伺服器會將更新識別為串流更新 (或假設所有更新都是串流更新),且用戶端會對 update_engine 進行正確的串流呼叫,就能啟用串流更新。製造商可以利用套件屬於串流變體的特性,將標記傳送至用戶端,觸發移交至架構端,以進行串流作業。

有效負載可用後,更新程序如下:

步驟 活動
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) 收到使用其他路徑或建構靜態二進位檔的指令,否則程式庫會從舊系統映像檔載入,而非新系統映像檔。

舉例來說,您可以將殼層指令碼當做舊系統殼層二進位檔解譯的安裝後程式 (頂端有 #! 標記),然後從新環境設定程式庫路徑,執行更複雜的安裝後二進位檔程式。或者,您也可以從專用的小型磁碟分割區執行安裝後步驟,以便更新主要系統磁碟分割區中的檔案系統格式,不會發生回溯相容性問題或需要逐步更新;這樣一來,使用者就能直接從原廠映像檔更新至最新版本。

新安裝後程式會受到舊系統中定義的 SELinux 政策限制。因此,安裝後步驟適合在特定裝置上執行設計所需的作業,或執行其他盡力而為的作業。安裝後步驟不適合在重新啟動前進行一次性錯誤修正,因為這類修正作業需要無法預測的權限。

所選的安裝後程式會在 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、設定存取權限,然後重新啟動裝置。系統成功啟動新版本後,就會刪除擷取的檔案。