OTA 更新

無線更新 (OTA) 是 Fuchsia 作業系統更新的機制。本文將詳細說明 OTA 更新在 Fuchsia 上的運作方式。

更新程序分為以下階段:

檢查更新

作業系統更新程序的兩個進入點為 omaha-clientsystem-update-checker 元件。

omaha-clientsystem-update-checker 的用途相同,都是用於檢查是否有作業系統更新,並開始更新。

一般來說,如果產品想要使用 Omaha 判斷更新可用性,應使用 omaha-client。如果產品不想使用 Omaha,而是想直接從套件存放區檢查更新,則應使用 system-update-checker

在任何 Fuchsia 系統上,可能只會執行下列其中一個元件:

使用 omaha-client 進行更新檢查

在啟動程序期間,omaha-client 會啟動並開始定期檢查更新。在這些檢查期間,omaha-client 會輪詢 Omaha 伺服器,檢查是否有更新。

使用 Omaha 的好處如下:

  • 這可讓您在一系列 Fuchsia 裝置上分階段推出系統更新。舉例來說,您可以設定只更新機隊裝置的 10%。也就是說,只有 10% 的裝置會在輪詢 Omaha 時顯示有可用的更新。其餘 90% 的裝置不會顯示可用的更新。
  • 可讓您使用不同的更新管道。舉例來說,測試裝置可以從測試管道取得更新,並取得最新 (可能不穩定) 的軟體。這可讓實際工作環境裝置從實際工作環境管道取得更新,並取得最穩定的軟體。您可以選擇將管道資訊與產品和版本一併提供給 Omaha。

圖:使用 omaha-client 檢查更新

圖 1. 使用 omaha-client 的簡易版更新檢查程序。有政策可限制 omaha-client 是否可以檢查或套用更新。

omaha-client 從 Omaha 伺服器取得更新套件網址後,就會通知 system-updater 開始更新。omaha-client

使用 system-update-checker 進行更新檢查

未搭載 omaha-client 的裝置會使用 system-update-checker。視設定方式而定,system-update-checker 會定期輪詢更新套件。如果未指定 auto_update,這些檢查作業預設為停用。

如要檢查是否有可用的更新,system-update-checker 會檢查下列條件:

  • 目前執行的系統映像檔 (位於 /pkgfs/system/meta) 的雜湊值,是否與更新套件中系統映像檔的雜湊值 (位於 packages.json) 不同?
  • 如果系統映像檔沒有差異,目前在系統上執行的 vbmeta 是否與更新套件的 vbmeta 不同?
  • 如果沒有 vbmeta,目前在系統上執行的 ZBI 是否與更新套件的 ZBI 不同?

如果任何一項答案為「是」,system-update-checker 就會知道更新套件已變更。一旦系統更新檢查器發現更新套件已變更,system-update-checker 就會觸發 system-updater,以預設更新套件 (fuchsia-pkg://fuchsia.com/update) 開始更新。

圖:使用 system-update-checker 檢查更新

圖 2. 使用 system-update-checker 的簡易版更新檢查程序。

如果不需要更新,更新檢查器會儲存伺服器上最後一次看到的更新套件。在後續的更新檢查作業中,系統會將擷取的更新套件雜湊值與伺服器上已知的最後一個雜湊值進行比對。如果最新更新套件的雜湊自上次檢查後有所變更,更新檢查器會將執行中系統的 vbmeta 和 ZBI 與更新套件中的相應映像檔進行比較。如果執行中的映像檔與更新套件之間的 vbmeta 或 ZBI 不同,檢查器就會啟動系統更新。

監控

如果用戶端想監控更新進度和狀態,可以實作 fuchsia.update.AttemptsMonitor 通訊協定,並提供用戶端端至 fuchsia.update.Manager FIDL 通訊協定的 MonitorAllUpdateChecks() 方法。只有在透過其他方法啟動更新,或目前正在進行更新時,fuchsia.update.AttemptsMonitor 例項才會收到訊息。這不會觸發新的更新。

fuchsia.update.AttemptsMonitor 執行個體會收到 OnStart 訊息,其中包含 fuchsia.update.Monitor 通訊協定的伺服器端。這可讓用戶端接收及處理 OnState 訊息,通知更新狀態變更。

另一個做法是實作 fuchsia.update.Monitor,並將用戶端端點提供給 fuchsia.update.Manager 通訊協定的 CheckNow() 方法。系統會開始檢查更新。它只會監控目前執行中的更新,並在更新完成後關閉句柄。

更新前置作業

無論更新是否由 omaha-clientsystem-update-checker 或強制更新檢查觸發,更新都必須寫入磁碟。

更新程序分為下列步驟:

圖:起始狀態圖

圖 3. 裝置目前執行假設的 OS 1 版本 (在插槽 A),並開始更新至假設的 OS 2 版本 (至插槽 B)。警告:實際上磁碟可能並未以這種方式分割。

擷取更新套件

system-updater 會使用提供的更新套件網址擷取更新套件。接著,動態索引會更新,以參照新的更新套件。更新套件的範例可能如下所示:

/board
/epoch.json
/firmware
/fuchsia.vbmeta
/packages.json
/recovery.vbmeta
/version
/zbi.signed
/zedboot.signed
/meta/contents
/meta/package

如果擷取失敗是因為空間不足,system-updater 就會觸發垃圾收集,刪除靜態或動態索引或保留套件組合中未參照的所有 BLOB。垃圾收集後,system-updater 會重試擷取作業。如果重試失敗,system-updater取代保留的套件集,只保留嘗試擷取的更新套件 (如果更新套件網址包含 hash,否則會清除保留的套件集),然後再次觸發垃圾收集,並重試更新套件擷取作業。

圖:擷取更新套件

圖 4. system-updater 會指示 pkg-resolver 解析第 2 版更新套件。我們假設 system-updater 因空間不足而無法擷取更新套件,因此觸發垃圾收集作業,以便將槽 B 參照的 0 版 blob 驅逐,然後重試擷取 2 版更新套件。

更新套件可視需要包含 update-mode 檔案。這個檔案會判斷系統更新是在正常模式還是強制復原模式下進行。如果沒有更新模式檔案,system-updater 預設為一般模式。

當模式為 ForceRecovery 時,system-updater 會將映像檔寫入復原模式,並將插槽 A 和 B 標示為無法開機,然後啟動復原模式。詳情請參閱 ForceRecovery 的實作方式

確認板子相符

目前執行中的系統有位於 /config/build-info/board 的電路板檔案。system-updater 會驗證系統中的板卡檔案是否與更新套件中的板卡檔案相符。

圖:驗證板塊是否相符

圖 5system-updater 會驗證更新套件中的板子是否與插槽 A 中的板子相符。

驗證是否支援紀元

更新套件包含一個紀元檔案 (epoch.json)。如果更新套件的紀元 (目標紀元) 小於 system-updater 的紀元 (來源紀元),OTA 就會失敗。如需其他背景資訊,請參閱 RFC-0071

圖:支援驗證紀元

圖 6. system-updater 會將更新套件中的紀元與目前 OS 的紀元進行比較,藉此驗證更新套件中的紀元是否受支援。

取代保留套件組合

取代保留套件集合,並使用目前的更新套件和稍後在 OTA 程序中擷取的所有套件。

保留套件集是指一組不會遭到垃圾收集的套件 (除了靜態和動態索引中的套件)。這可用來防止垃圾收集刪除目前更新程序所需的 BLOB。舉例來說,假設裝置擷取了更新所需的部分套件,但因其他原因而重新啟動。裝置再次開始 OTA 時,仍需要重新啟動前擷取的套件,但這些套件並未受到動態索引的保護 (就像保留套件集合一樣,會在重新啟動時清除)。將這些套件新增至保留的套件集合後,system-updater 就能觸發垃圾收集 (例如移除先前系統版本使用的 blob),而不會撤銷先前的作業。

觸發垃圾收集

系統會觸發垃圾收集功能,刪除舊系統專屬的所有 BLOB。這個步驟可為任何新套件釋出額外空間。

圖:垃圾收集

圖 7. system-updater 會指示 pkg-cache 對舊系統專屬的所有 BLOB 進行垃圾收集。在此範例中,這表示 pkg-cache 會移除第 1 版更新套件專屬參照的 BLOB。

擷取剩餘套件

系統更新程式會剖析更新套件中的 packages.json 檔案。packages.json 如下所示:

{
  "version": 1,
  "content": [
    "fuchsia-pkg://fuchsia.com/sshd-host/0?hash=123..abc",
    "fuchsia-pkg://fuchsia.com/system-image/0?hash=456..def"
    ...
  ]
}

system-updater 會指示 pkg-resolver 解析所有套件網址。解析套件時,套件管理系統只會擷取更新所需的 BLOB,也就是尚未存在的 BLOB。套件管理系統會擷取整個 BLOB,而非系統目前可能有的差異。

擷取所有套件後,系統會觸發 BlobFS 同步處理作業,將 Blob 刷新至永久性儲存空間。這個程序可確保 BlobFS 中提供所有系統更新所需的 BLOB。

圖:擷取剩餘的套件

圖 8. system-updater 會指示 pkg-resolver 解析 packages.json 中參照的版本 2 套件。

將圖片寫入區塊裝置

system-updater 會決定哪些圖片需要寫入區塊裝置。圖片分為兩種:素材資源和韌體。

接著,system-updater 會指示分區映像檔安裝工具寫入系統啟動載入程式和韌體。這些圖片的最終位置不受裝置是否支援 ABR 為避免閃存耗損,只有在映像檔與區塊裝置上現有的映像檔不同時,才會將映像檔寫入分區。

接著,system-updater 會指示分區映像檔安裝工具寫入 Fuchsia ZBI 及其 vbmeta。這些圖片的最終位置取決於裝置是否支援 ABR 如果裝置支援 ABR,則分區映像檔安裝工具會將 Fuchsia ZBI 和其 vbmeta 寫入目前未開機的插槽 (替代插槽)。否則,分區映像檔安裝工具會將這些檔案寫入 A 和 B 區段 (如果有 B 區段的話)。

最後,system-updater 會指示分區映像檔安裝工具寫入復原 ZBI 及其 vbmeta。就像啟動載入器和韌體一樣,最終位置不受裝置是否支援 ABR

圖:將圖片寫入區塊裝置

圖 9. system-updater 會透過分區映像檔安裝工具將第 2 版圖片寫入 B 槽。

將備用分割區設為有效

如果裝置支援 ABR,system-updater 會使用分區映像檔安裝工具將替代分割區設為有效。這樣一來,裝置會在下次開機時啟動至替代分區。

您可以透過多種方式參照時段狀態。舉例來說,內部分區映像檔安裝工具使用 Successful,而 FIDL 服務則使用 Healthy,其他情況可能會使用 Active、Inactive、Bootable、Unbootable、Current、Alternate 等。

重要的中繼資料是 3 個資訊,每個核心間隔都會儲存這 3 個資訊。這項資訊可協助判斷每個核心位置的狀態。舉例來說,在標示插槽 B 為有效之前,中繼資料可能會如下所示:

中繼資料 插槽 A 版位 B
優先順序 15 0
剩餘嘗試次數 0 0
健康* 1 0

標示為有效後,插槽 B 的中繼資料會如下所示:

中繼資料 插槽 A 版位 B
優先順序 14 15**
剩餘嘗試次數 0 7**
健康料理 1 0

如果裝置不支援 ABR,則系統會略過這項檢查,因為沒有其他分割區。而是在每次更新時,寫入至有效的分區。

圖:將備用磁碟區設為有效

圖 10. system-updater 會將插槽 B 設為「Active」,讓裝置在下次啟動時開機至插槽 B。

重新啟動

視更新設定而定,裝置可能會或不會重新啟動。裝置重新啟動後,會以新插槽啟動。

圖:重新啟動

圖 11. 裝置會重新啟動至槽 B,並開始執行第 2 版。

驗證更新

系統驗證更新後,就會提交更新。

系統會以以下方式驗證更新:

重新啟動至更新後的版本

下次啟動時,系統啟動載入程式需要判斷要啟動至哪個插槽。在這個範例中,由於插槽 B 的優先順序較高,且剩餘的嘗試次數大於 0 (請參閱「將備用分割區設為活動」),因此啟動載入程式會決定要啟動至插槽 B。接著,Bootloader 會驗證 B 的 ZBI 是否與 B 的 vbmeta 相符,並最終啟動至 B 槽。

在早期啟動後,fshost 會使用新的系統映像檔套件啟動 pkgfs。這是在更新前置作業期間,packages.json 中參照的系統映像檔套件。系統映像檔套件內含 static_packages 檔案,其中列出新系統的基本套件。例如:

pkg-resolver/0 = new-version-hash-pkg-resolver
foo/0 = new-version-hash-foo
bar/0 = new-version-hash-bar
...
// Note the system-image package is not referenced in static_packages
// because it's impossible for it to refer to its own hash.

pkgfs 接著會將所有這些套件載入為基本套件。套件會顯示在 /pkgfs/{packages, versions} 中,表示套件已安裝或啟用。接著,系統會啟動 pkg-resolverpkg-cachenetstack 等。

提交更新

system-update-committer 元件會執行各種檢查,驗證新更新是否成功。例如,它會指示 BlobF 任意讀取 1 MiB 的資料。如果系統已在開機時提交,系統會略過這些檢查。如果檢查失敗,且視系統設定方式而定,system-update-committer 可能會觸發重新啟動。

更新完成驗證後,目前的分區 (插槽 B) 會標示為 Healthy。以「將備用磁碟區設為活動」一文中所述的範例為例,現在的啟動中繼資料可能會如下所示:

中繼資料 插槽 A 版位 B
優先順序 14 15
剩餘嘗試次數 7 0
健康料理 0 1

然後,系統會將替代分區 (插槽 A) 標示為無法啟動。啟動中繼資料現在可能會長這樣:

中繼資料 插槽 A 版位 B
優先順序 0 15
剩餘嘗試次數 0 0
健康料理 0 1

之後,系統會將更新視為已提交。因此:

  • 系統會一律在下次系統更新前,以 B 槽啟動。
  • 系統會放棄啟動至插槽 A,直到下次系統更新覆寫插槽 A 為止。
  • 由插槽 A 參照的 BLOB 現可進行垃圾收集。
  • 系統現在允許後續更新。更新檢查器發現新更新時,整個更新程序就會重新開始。