RFC-0167:早期使用者空間啟動程序套件

RFC-0167:早期使用者空間引導程序中的套件
狀態已接受
區域
  • 元件架構
說明

將套件引入 BootFS 和啟動解析。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2022-05-10
審查日期 (年-月-日)2022-06-13

摘要

這項 RFC 建議在啟動檔案系統中導入套件。這麼做可讓使用者模式在早期啟動時享有套件隔離和命名空間的優點,並移除第三方驅動程式庫開發的阻礙因素。

提振精神

在元件架構擁有穩固的封裝架構之前,早期啟動可執行的組裝和沙箱功能就已出現,而我們目前尚未投入資源,將這些工具納入早期啟動後的使用者空間。因此,使用者空間引導程序會面臨幾個問題,而這些問題正是包裝作業要解決的問題,例如程序沙箱、可驗證內容和變數程式庫版本。

這些早期啟動問題會導致與啟動檔案系統映像檔的互動在執行階段和建構階段出現不必要的複雜性和低效率,但只要在早期啟動時採用包裝化,就能大幅減少這些問題。除瞭解決現有問題,早期啟動程序的包裝化也能提供改善系統健康狀態的機會。在使用者空間中為可執行檔和程式庫的內容 ID 進行標準化,可讓我們重複使用先前不連結儲存空間 (例如啟動檔案系統和 blobfs) 中的相等資料副本。

元件架構具有「套件」概念,可做為 fuchsia-pkg 的抽象概念;雖然嚴格來說,元件架構和封裝系統是分開的,但兩者緊密結合,因此元件化和封裝世界的目標是互相配合。

近期的工作 (例如將元件管理員做為第一個使用者啟動程序後程序執行) 正在推動越來越多的「一路向下元件」架構。甚至是早期啟動可執行檔 (例如檔案系統和裝置驅動程式),也以 Fuchsia 元件的形式啟動,或正在朝向該模型遷移。接下來,我們要將系統的元件化與「從上到下套件」系統組合相符,並將所有促使套件化至使用者空間的價值帶入引導程序。

具體來說,如果啟動檔案系統中沒有套件命名空間,就會成為樹狀結構外驅動程式庫開發的阻礙,因為無法產生有效的 Fuchsia 映像檔,其中啟動檔案系統中編碼的執行檔在共用 lib 依附元件中具有版本偏差。此外,目前的啟動檔案系統映像檔會洩漏 ABI。驅動程式可以根據其他驅動程式的存在或不存在,變更啟動時的行為,例如選擇使用較新的程式庫,而且無須明確定義對其他驅動程式庫的依附元件,即可執行此操作。同樣地,由於驅動程式都會位於啟動檔案系統中的同一個資料夾,因此驅動程式名稱本身會建立 ABI。這類非預期的 ABI 存在時間越久,就越難淘汰,而這類行為早有先例 (Windows 驅動程式和電玩遊戲 DRM 驅動程式就會公開這類非預期的 ABI)。

相關人員

Facilitator:hjfreyer@google.com

審查者:geb@google.com、mcgrathr@google.com、surajmalhotra@google.com、aaronwood@google.com、galbanum@google.com、wittrock@google.com、jfsulliv@google.com

諮詢:

社交化:我們透過設計文件與利害關係人討論這個主題,然後在 RFC 前將其開放給 tq-eng 進行一般討論。

設計

這項變更涉及對啟動檔案系統和 /boot 目錄、BootResolver、BootUrl 和產品組合進行的變更。

啟動檔案系統異動

圖片變更

Fuchsia 啟動檔案系統套件會以啟動檔案系統中的 meta.far 檔案表示,其啟動檔案系統目錄項目名稱為 blob/<merkle root of meta.far>。套件資訊清單中的每個 blob 都會在啟動檔案系統中收到新的目錄項目,blob/<merkle root of the dependency>

  1. 我們已在執行階段計算 Merkle 以驗證內容,且速度相當快,因此在建立 zbi 等會壓縮圖片的程序中執行這項作業,不會造成任何影響。
  2. 這項提案所需的新圖片建構程序已在實際圖片建構步驟前決定內容 ID,以便填入 meta.far 檔案。也就是說,在產生資訊清單以驅動映像檔建構步驟的過程中,系統多半已完成內容重複排除作業。只要確保我們已在這個階段完成所有可能的去重作業,應該就會很容易。

當我們討論在 /blob 下新增啟動檔案系統項目時,我們指的是新增參照相同基礎檔案的新啟動檔案系統目錄項目 (概念上為硬連結)。

啟動檔案系統中的新檔案名為「pkg_map」,會維持從人類可讀的套件名稱到用於編碼套件的 meta.far 的梅克爾根目錄的對應關係。

新增的 meta.far 和新的 pkg_map 檔案會增加 bootfs 大小。其他所有內容都只是已存在的啟動檔案系統檔案的新目錄項目。在 x64 架構上,bootfs 的壓縮大小會增加約 70 KiB (保守估計)。

/boot 變更

我們會在 /boot 中新增一個名為 /blob 的新子目錄。啟動檔案系統映像檔中所有檔案的名稱前置字串為 /blob,將啟動檔案系統中的所有元件遷移至套件後,頂層目錄只會包含核心 vmos、shell 指令碼,以及讓 /boot 成為元件管理員的「命名空間」所需的檔案。

元件管理員命名空間一開始會是 /boot 的子目錄,並且會「排列」所有依附元件預期的內容。最終,我們希望將元件管理工具納入 meta.far 編碼,以便在解析時仰賴 SWD 堆疊。

BootResolver 變更

套件名稱空間啟動檔案系統元件的核心工作「大量」與套件解析器所執行的工作重疊。人類可讀的套件名稱必須對應至內容 ID 的 meta.far,meta.far 必須解碼,且其內容檔案必須用於建構命名空間。因此,設計目標是盡可能重複使用現有的套件解析器邏輯。

如要重複使用套件解析器的邏輯,最簡單的入口點是 package-directory::serve。這個進入點會取得 BlobFS 和用於識別 meta.far 的內容 ID,開啟 meta.far、剖析其中繼/內容檔案,並依照檔案中的編碼方式建構並提供命名空間。只要我們能夠將啟動檔案系統支援的 Blob 目錄以 BlobFS 用戶端的形式提供給 API,就可以原封不動地重複使用這個程式庫。

    let (proxy, server) =
        fidl::endpoints::create_proxy().map_err(ResolverError::CreateEndpoints)?;
    let () = package_directory::serve(
        package_directory::ExecutionScope::new(),
        <some blobfs::Client-like view on top of the bootfs blobs>,
        <meta.far hash>,
        fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE,
        server,
    )
    .await
    .map_err(ResolverError::ServePackageDirectory)?;

blobfs::Client 只會包裝 fio::DirectoryProxy,這是從元件管理員公開啟動檔案系統的方式。我們可以使用 Blobfs 用戶端包裝這個啟動檔案系統,然後將其傳遞至 package_directory::Serve 呼叫。您需要進行一些小幅變更,確保特定 blobfs::Client API 在位於啟動檔案系統時能順利失敗 (也就是需要可變性的 API,例如 open_blob_for_write、delete_blob),但在 package_directory::serve 執行期間不需要可變性。

BootUrl 變更

由於沒有可編碼的存放區或封裝,BootUrl 目前不會使用網址的主機或路徑部分。也就是說,我們可以使用現有的 Fuchsia 啟動架構,在現有的 BootResolver 中導入新的元件載入功能,而非導入新的網址架構和解析器。網址中是否有套件路徑,將成為應使用「BootResolver 變更」一文中所述新解析路徑的指標。

例如:

fuchsia-boot:///#my_component.cm bootresolver 會將這個網址解讀為未封裝的元件,其命名空間已在 /boot 目錄中正確設定。

fuchsia-boot:///my_package#my_component.cm bootresolver 會將這個網址解讀為已封裝的元件,其中「my_package」會對應至 my_package 的 meta.far 的 Merkle 根目錄,並應用於建構特定於「my_package」命名空間的命名空間。

產品/圖片組合變更

開機檔案系統映像檔建構作業會在 2 個階段執行,首先由建構系統執行,接著由映像檔組合系統執行。

首先,我們會引入名為 bootfs_packages 的新 gn 變數。這個清單會在 product.gni 中宣告,並指派給名為 bootfs_package_labels 的叫用者變數,該變數會傳遞至將 build/input:bootfs 指派給 bootfs_labels 變數的任何 assemble_system 叫用。

將啟動檔案系統元件從無包裝編碼遷移至啟動檔案系統套件時,我們會將該元件從 bootfs_labels 依附元件集中的群組依附元件中移除,並將其新增至 bootfs_packages 集合。

接下來,在產生圖片組合設定時,我們會使用現有的 list_package_manifests 範本,從叫用端的 bootfs_package_labels 變數中定義的套件收集套件資訊清單。

接下來,映像檔組合會從建構遍歷中取得資訊清單,並使用該資訊清單呼叫 zbi 等工具,將建構目錄中的檔案封裝成映像檔。套件資訊清單格式包含「blob」物件清單,這些物件會在映像檔建立時用於將 blob/<merkle_root> 命名的檔案新增至啟動檔案系統映像檔。

在對這些 Blob 物件進行疊代時,我們會檢查哪個 Blob 是套件 meta.far 的 ID,並將從套件名稱到 meta.far 的梅克爾根目錄對應新增至地圖。在啟動檔案系統映像檔建立作業結束時,系統會以 JSON 格式將對應項目寫入上述 bootfs 變更一節所述的「pkg_map」檔案。

我們選擇在圖片組合層級實作這項轉換,原因有三:

  • ProductAssembly 只會合併各種套件的套件清單。

  • ImageAssemblyConfig 驗證仍保持簡單。

  • 產品組合中「packages」的驗證會繼續在「packages」上運作。

實作

功能實作

您可以同時變更圖片組合、啟動檔案系統、BootResolver 和 BootUrl。

為確保在導入過程中,不會在 bootfs_package_labels 中看到套件的引入,我們將從圖片組合變更開始。我們會在啟動檔案系統套件資訊清單匯總點實作這項功能,並在建構期間檢查集合是否為空白。

BootUrl 的語意會隨著 3 個 CL 而變更。首先,在將第一個啟動檔案系統元件遷移至套件命名空間之前,我們會變更 BootUrl,以便在包含套件路徑或存放區時視為無效 (確保套件路徑可用,做為解析策略的指標)。其次,隨著第一個啟動檔案系統元件遷移至 fuchsia_package,我們會允許 fuchsia-boot 網址中的套件路徑。第三,隨著最後一個啟動檔案系統元件遷移至 fuchsia_package,我們會禁止不含套件的 fuchsia-boot 網址。

遷移

在完全實作這些功能後,我們會逐步遷移啟動檔案系統元件。特定元件的遷移作業如下:

  1. 我們會找出元件的未打包依附元件 (.cml 檔案、二進位檔等) 新增至 bootfs_labels 依附元件的所在位置。
  2. 我們會將未封裝的依附元件集合轉換為 fuchsia_package gn 目標。
  3. 我們會從現有的 bootfs_labels 群組中移除套件,並將其新增至 bootfs_package_labels 群組。
  4. 我們會更新 bootstrap.cml 檔案中元件的網址,以便納入套件名稱。

成效

系統大小

在 x64 架構上,啟動檔案系統映像檔的壓縮大小會增加約 70 KiB。這是因為所有元件都已遷移至套件後,啟動檔案系統大小會因新增的 meta.fars 和新的 pkg_map 檔案而增加。其他所有內容都只是將啟動檔案系統檔案的已包裝目錄項目重新命名。

執行階段影響

目前,整個啟動檔案系統會在 component_manager 啟動時立即剖析為目錄。遷移完成後,我們會將剖析作業延後,等到元件啟動時再執行,這項作業相當於在 /boot 中設定元件的命名空間。

除了先前用於剖析啟動檔案系統標頭的額外工作之外,我們還必須剖析 meta.far。

ZBI 已簽署,因此我們目前不會驗證 ZBI 內的啟動檔案系統檔案內容,只會驗證 ZBI 本身。如果我們不對啟動檔案系統中的 blob 進行執行階段驗證,那麼我們就會處於與目前相同的安全狀態。

在目前的情況下,啟動檔案系統可能會發現一個潛在的錯誤狀態,即組合可能會將來源檔案錯誤地放在錯誤的啟動檔案系統連結下。由於啟動檔案系統中含有 Blob,因此我們可以視需要對啟動檔案系統 Blob 進行執行階段驗證,以免發生這種情況。我們目前沒有這項計畫。

建構時間影響

zbi 建構作業的建構時間效能並未改變;在遷移完成後,我們會檢查套件資訊清單,對 blob 進行編碼,而非檢查啟動檔案系統中包含的每個 dep 的目的地資訊清單。實際上,我們會檢查完全相同數量的構件。

回溯相容性

這項提案涉及的許多變更都包含在啟動檔案系統中。在啟動檔案系統中更新執行單位的細緻度,比一次更新整個啟動檔案系統更小。因此,系統不會因為未完成啟動檔案系統封裝實作而提供已封裝的啟動檔案系統元件。

圖片組合器在同一 zbi 中,發現包含舊版啟動檔案系統映像檔和新元件管理器,就可能導致向下相容性問題。如果發生這種情況,一旦遷移作業完成,且啟動檔案系統不允許使用未封裝的元件,就可能會發生錯誤。不過,考量到產品組合如何建構啟動檔案系統,目前無法產生此狀態。

同樣地,BootUrl 的語意變更 (如 功能實作中所述) 也不會與功能實作或遷移作業有所出入,因為啟動檔案系統也包含這些變更。

安全性考量

套件命名空間/程序沙箱可減少程序可存取的構件,進而嚴格提升執行階段安全性。

啟動檔案系統映像檔已簽署且為唯讀,因此主要安全性問題是圖像組合器是否可信任,而這與圖像的格式無關。無論如何,如果我們決定要逐步更新啟動檔案系統映像檔,在啟動檔案系統中提供可執行檔的 Merkle 根識別碼,可能會進一步改善安全性。

隱私權注意事項

無。

測試

我們已詳細記錄元件解析器和產品組件的測試做法,並將擴展至涵蓋新功能。

在語意變更的 3 個階段中,每個階段都會新增 BootUrl 剖析器測試,以顯示系統是否強制執行網址的預期行為。

說明文件

使用者空間 Bootstrapping 的 API 說明文件,以及啟動檔案系統映像檔組合的說明文件,都需要更新。

缺點、替代方案和未知事項

重複使用 bootfs_labels 與導入新標籤。

目前,如果不小心在 bootfs_labels 清單的依附元件清單中加入套件,系統會視為無操作。在這個變更之後,套件的存在會具有語意意義。因此,我們需要從 bootfs_labels 命名空間中「清除」不小心納入的套件。

這裡唯一的複雜性涉及 fuchsia_driver_packages。fuchsia_driver_package 是專屬的建構範本;產品組合與 fuchsia_driver_package 的互動方式會因驅動程式庫是放入啟動檔案系統還是 blobfs 而有所不同。放置在 bootfs_labels 時,產品組合會逐一檢視驅動程式庫套件,將驅動程式庫視為一組依附元件;放置在 blobfs 時,驅動程式庫會透過 meta.far 正確封裝。這樣做是為了讓單一驅動程式庫目標能夠根據產品切換啟動檔案系統和 blobfs。這項功能過去可正常運作,因為啟動檔案系統產品組合不會將任何重要性指派給依附元件圖表中的套件。不過,如果使用啟動檔案系統套件命名空間,我們會將 fuchsia_package (及其相關聯的 package_manifest) 的存在解讀為宣告,以便將 meta.far 編碼套件命名空間放入啟動檔案系統。

如果我們今天要推出啟動檔案系統命名空間功能,大約有 20 個驅動程式會以未封裝的依附元件置於啟動檔案系統,並且由相關聯的 meta.far 和相關聯的 blob 在啟動檔案系統中進行編碼;驅動程式庫的 meta.far 編碼會一直閒置,直到 driver_manager 的執行程式變更為止。我們不想讓啟動檔案系統因未使用的封裝而變得臃腫,因此想從啟動檔案系統中清除所有 fuchsia_packages,然後逐步將目標遷移至適當的封裝。我們選擇透過引入新的 gn 中繼資料邊界來清除 fuchsia_driver_packages 的 package_manifests,以免 list_package_manifests 會走訪 fuchsia 驅動程式。這麼做可大幅降低驅動程式庫產品組合邏輯的複雜度,而非將每個使用 fuchsia_driver_package 的驅動程式庫分割為啟動檔案系統目標和 blobfs 目標。此外,在最終結果中,版本中的單一套件定義可用於任何用途,因此可避免分割每個項目時出現流失,然後移除所有「僅限舊啟動檔案系統」的擬似套件。

不幸的是,這會帶來其他複雜性,例如引入真正的 fuchsia_package 目標遮罩,並向使用者隱藏其名稱。這會導致與其他現有和持續進行的工作不相容,例如金本測試,用於驗證裝置上所有套件資訊清單是否符合預期名稱,或是產品組合工作,要求產生套件索引的標籤只直接依賴套件目標本身。

因此,較不具侵入性的做法是新增標籤,並在將早期啟動階段的元件遷移至套件時,將這些元件從舊標籤移至新標籤。最後,我們需要合併這些清單,如果驅動程式尚未遷移至適當的元件,就必須重新檢視 fuchsia-驅動程式庫-套件範本的複雜度。

在產品或圖片組合作業中實作

  • 應在產品組裝層級還是映像檔組裝層級,剖析啟動檔案系統元件的套件資訊清單?換句話說,我們是否應將解析資訊清單的作業延後,直到要呼叫 zbi 工具實際建構啟動檔案系統時再執行?
    • ProductAssembly (ffx 組合產品)
    • 優點:
      • ImageAssembly 會專注於產生圖片檔案本身 (zbi、blobfs 等)
      • ImageAssemblyConfig 包含所有啟動檔案系統檔案的簡單清單。
    • 缺點:
      • 需要進行更複雜的驗證,確認用於產生舊版組合輸入套件的 ImageAssemblyConfig 與 ProductAssembly 建立的 ImageAssemblyConfig 相符 (或驗證需要捨棄/弱化)。
      • 在產品組合結束時執行的驗證作業,需要知道在啟動檔案系統中哪裡可以找到元件的「套件」(例如為結構化設定執行的值檔案存在性驗證)。
    • ImageAssembly (ffx assembly create-system)
    • 優點:
      • ProductAssembly 只會合併來自各種套件的包裝清單
      • ImageAssemblyConfig 驗證仍保持簡單
      • 產品組合中「檔案包」的驗證會繼續在「檔案包」上運作。
    • 缺點:
      • ImageAssemblyConfig 不再包含啟動檔案系統檔案的完整內容。
      • 在建立 zbi 之前,Image Assembly 需要執行套件 -> 至項目對應。

在啟動檔案系統中編碼 meta.fars,還是在啟動檔案系統映像檔建構時設定命名空間?

另一種方法是在啟動檔案系統中簡單地編碼「命名空間」,方法是建構映像檔,讓 /boot 下的每個元件都有自己的子目錄,而元件管理工具會將該子目錄用作該元件的命名空間根目錄。

在啟動檔案系統中編碼 meta.far 時,我們會在 boot-resolver 中引入對 SWD 套件解析程式庫的依附元件,藉此增加早期啟動的依附元件,以便解讀 meta.far 格式,並在 meta.far 格式變更時繼續執行相關作業。

在映像檔建構期間執行命名空間設定時,我們會教導產品如何解讀套件資訊清單/meta.far 格式,並編寫程式碼將 meta.far 格式轉譯為啟動檔案系統映像檔中的套件命名空間,這意味著會有新程式碼,且第二個團隊會「在業務中」解讀 meta.far,並負責讓產品與其保持同步。SWD 團隊已表達擔心,新團隊會接手 meta.far 格式的依附元件,但只要匯集單一套件編碼,並重複使用 SWD 維護的工具與該格式互動,就能完全避免這種情況。

這兩種做法對資源的影響幾乎相同 (以千位元為單位)。

最後,套件不只是編碼的命名空間。如要使用平台版本等執行階段功能,就需要 meta.far 中的中繼資料,例如套件版本。如果未使用 meta.fars,我們需要在以啟動檔案系統為基礎的命名空間中,引進一些新的編碼方式來處理這類「額外資訊」,並教導元件管理員等服務如何處理此類額外套件中繼資料的編碼。