舊版 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 網址呼叫
update_engine
(如果有)。update_engine
會在串流更新套件時,更新目前未使用的分區上的原始區塊。 -
根據
update_engine
結果碼,向伺服器回報安裝成功或失敗的情況。如果更新成功,update_engine
會在下次重新啟動時,指示啟動載入程式啟動至新作業系統。如果新作業系統無法啟動,引導程式會改用舊作業系統,因此用戶端不必採取任何行動。如果更新失敗,用戶端需要根據詳細的錯誤代碼決定何時 (以及是否) 重試。舉例來說,良好的用戶端可以辨識部分 (「diff」) OTA 套件失敗,並改為嘗試完整 OTA 套件。
用戶端可視需要執行下列操作:
- 顯示通知,要求使用者重新啟動。如果您想實施一項政策,鼓勵使用者定期更新,則可將這項通知加入您的用戶端。如果用戶端未向使用者發出提示,使用者還是會在下次重新啟動時收到更新。(Google 的用戶端有可設定的每次更新延遲時間)。
- 顯示通知,告知使用者是否已啟動至新的 OS 版本,或是預期要啟動至新版本,但實際上卻回復至舊版 OS。(Google 的用戶端通常不會執行這兩項操作)。
在系統層級,A/B 系統更新會影響下列項目:
-
分割區選取 (插槽)、
update_engine
守護程式和引導程式互動 (如下所述) - 建構程序和 OTA 更新套件產生程序 (請參閱「實作 A/B 更新」一文)
分區選項 (時段)
A/B 系統更新會使用兩組分區,稱為「位置」 (通常是位置 A 和位置 B)。系統會從「current」分區執行,而「unused」分區則在正常運作期間不會由執行中的系統存取。這種做法可將未使用的插槽保留為備用,讓更新作業具備錯誤耐受性:如果在更新期間或更新後立即發生錯誤,系統可以回溯至舊插槽,並繼續運作。為達成這個目標,OTA 更新中不應更新 目前插槽使用的任何分割區 (包括只有一個副本的分割區)。
每個插槽都有可啟動屬性,指出插槽是否包含可讓裝置啟動的正確系統。系統執行時,目前的插槽可啟動,但其他插槽可能會包含舊版 (仍正確) 的系統、較新的版本或無效的資料。無論「目前」的插槽為何,都會有一個「有效」插槽 (即下次啟動時會從中啟動的引導程式) 或「偏好」插槽。
每個插槽也都有由使用者空間設定的成功屬性,只有在插槽可啟動時才相關。成功的運算單元應可自行啟動、執行及更新。可啟動的插槽如果在多次嘗試啟動後仍未標示為成功,應由 Bootloader 標示為無法啟動,包括將有效的插槽變更為其他可啟動插槽 (通常是指在嘗試啟動新的有效插槽之前立即執行的插槽)。介面具體詳細資料已在
boot_control.h
中定義。
更新引擎 Daemon
A/B 系統更新會使用名為 update_engine
的背景 Daemon,讓系統準備啟動至新版更新版本。這個守護程式可以執行下列動作:
- 根據 OTA 套件的指示,從目前的插槽 A/B 分區讀取資料,並將任何資料寫入未使用的插槽 A/B 分區。
- 在預先定義的工作流程中呼叫
boot_control
介面。 - 按照 OTA 套件的指示,在寫入所有未使用的插槽分割區後,從新分割區執行安裝後程式。(詳情請參閱「安裝後」)。
由於 update_engine
守護程序不會參與啟動程序,因此在更新期間,其功能會受到 current 槽中的 SELinux 政策和功能的限制 (系統必須先啟動至新版本,才能更新這些政策和功能)。為了維持系統穩定性,更新程序不應修改分區表、目前插槽中分區的內容,或非 A/B 分區的內容,因為這些內容無法透過恢復原廠設定來清除。
更新引擎來源
update_engine
來源位於 system/update_engine
中。A/B OTA dexopt 檔案會在 installd
和套件管理工具之間分割:
-
frameworks/native/cmds/installd/
ota* 包含安裝後指令碼、chroot 的二進位檔、呼叫 dex2oat 的 installd 複本、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/
會儲存最近五筆記錄。擁有 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
空間不一定足以下載更新套件。由於原始設備製造商和使用者都不想浪費 /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 交給 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
指令。
如要順利完成安裝後程序,舊版核心必須能夠:
- 掛接新的檔案系統格式。除非舊版核心支援,否則無法變更檔案系統類型,包括使用壓縮檔案系統 (例如 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
、設定存取權限,然後重新啟動裝置,並在系統成功啟動至新版本後刪除已擷取的檔案。