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

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

推出 BootFS 和啟動解決方案的套件簡介。

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

摘要

這個 RFC 提議將套件介紹給啟動檔案系統,這將可為後期使用者模式帶來套件隔離和命名空間的好處,這種做法也能在早期啟動階段,並移除第三方驅動程式庫開發的阻礙。

提振精神

早期啟動執行檔組件和沙箱機制在元件架構建立可靠的封裝架構之前就已經存在,目前我們尚未投入心力,將目前可供之後使用者空間可用的工具導入早期啟動階段。因此,使用者空間啟動程序會發現本身遇到幾個問題,藉此解決封裝問題,例如程序沙箱作業、可驗證的內容和變數程式庫版本等。

這些早期啟動問題指出,執行時間和建構映像檔與啟動檔案系統映像檔的建構時間互動中,有的複雜性和效率不彰的問題,在早期啟動期間進行封裝後,可以大幅排除這類問題。早期啟動封裝作業除瞭解決現有問題之外,還有改善系統健康狀態的機會。將適用於整個使用者空間的執行檔和程式庫的內容 ID 標準化,讓我們可以重複使用先前分散儲存空間 (例如啟動檔案系統和 blobf) 的相等資料副本。

元件架構具有一個「套件」概念,可當做抽象圈的抽象化機制。雖然元件架構和封裝系統的結構在嚴格來說各自獨立,但兩者間的交互影響,足以讓組成與封裝世界的目標能夠合作運作。

最近的工作 (例如元件管理員做為第一個使用者啟動程序後執行的工作) 導致「元件全部向下」架構的發展越來越迅速。即使是檔案系統和裝置驅動程式等早期啟動執行檔,也能做為 Fuchsia 元件啟動,或是立即遷移至該模型。現在,是將系統的元件化與「套裝行程一手包辦」系統組件搭配使用,並將所有激勵封裝至使用者空間啟動程序的值都應用於這個系統。

具體而言,啟動檔案系統中缺少套件名稱間隔將成為樹狀結構外驅動程式庫開發的阻礙,因為該映像檔無法產生有效的 fuchsia 映像檔,而啟動檔案系統中編碼的執行檔在共用 lib Deps 中存在版本偏差。此外,目前的啟動映像檔正在外洩 ABI。驅動程式會根據是否有其他驅動程式,而根據是否有其他驅動程式變更其行為,例如選擇使用較新的程式庫,而且不需要明確定義其他驅動程式庫的依附元件,即可執行這項操作。同樣地,由於驅動程式全都位於啟動程序的同一個資料夾中,因此驅動程式名稱會同時建立 ABI。這類無意間的 ABI 較難淘汰其可用時間,而且這種行為模式已開始有前所未有的高峰 (Windows 驅動程式和電玩遊戲 DRM 驅動程式公開這類意外的 ABI)。

相關人員

講師:hjfreyer@google.com

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

顧問:

社交化:透過設計文件探索相關主題,並與相關人員合作,然後允許 tq-eng 在 RFC 之前進行一般討論。

設計

這項變更包括啟動檔案系統和 /Boot 目錄、BootResolver、BootUrl 和產品組合。

啟動檔案系統設定變更

圖片變更

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

  1. 我們已在執行階段中計算 Merkles 來驗證內容,而且相關作業很快就進行了,因此在 zbi 建立程序 (也會壓縮圖片) 的程序下,並不連續。
  2. 本提案所需的新圖片建構程序已經在實際圖片建構步驟之前確定內容身分,以便填入中繼.far 檔案。這表示以內容為基礎的簡化功能大部分都是在建立資訊清單以推動映像檔建構步驟的過程中完成。我們應該確保在這個階段完成所有可能的簡化作業,仍然容易理解。

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

名為「pkg_map」的開機新檔案會維護一個從人類可讀的套件名稱與編碼套件的中繼根根深層根的對應。

隨著新增的中繼.fars 和新的 pkg_map 檔案增加,Bootfs 大小也會增加。其他部分則只會處理至已有的啟動檔案系統檔案的新目錄項目。系統在 x64 架構上壓縮了 70KiB,以保守的方式增加約 70KiB。

/開機變更

/Boot 會引入新的子目錄,稱為 /blob。其名稱前置字串為 /blob 的所有啟動檔案系統映像檔檔案都會存放在這裡。當啟動檔案系統中的所有元件都遷移至套件時,頂層目錄只會含有核心 VM、殼層指令碼,以及做為元件管理員的「命名空間」所需的檔案。

元件管理員命名空間一開始會是 /boot 的子目錄,如同所有依附元件預期會遭到「綁定」。最終,我們的目標是將元件管理員整合為採用 SWD 堆疊進行解析的中繼.far 編碼。

BootResolver 變更

套件名稱使用速度啟動啟動檔案系統元件的核心工作「嚴重」與套件解析器完成的工作重疊。使用者可理解的套件名稱必須對應至 content-id 的中繼.far,Meta.far 必須解碼,且其內容檔案必須用來建構命名空間。因此,這項設計旨在盡可能重複使用現有的套件解析邏輯。

如要重複使用套件解析器的邏輯,最簡單的進入點是 package-directory::serve。這個進入點採用 BlobFS 和可識別中繼.far 的內容 ID、開啟中繼.far、剖析其中繼/內容檔案,然後建構並提供在檔案中編碼的命名空間。只要我們能夠以 BlobFS 用戶端的形式向 API 提供啟動檔案系統支援的 blob 目錄,即可重複使用這個程式庫。

    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 目前不會使用其網址的主機或路徑區段,因為沒有存放區和封裝進行編碼。也就是說,我們不用加入新的網址配置和解析器,而是在現有的 BootResolver 中導入新的元件,並搭配現有的 fuchsia-boot 配置。如果網址中沒有套件路徑,則會成為應使用「BootResolver Changes」中所述新的解析路徑。

例如:

fuchsia-boot:///#my_component.cm 開機解析器會將此網址解讀為一個未封裝元件,其命名空間已可在 /Boot 目錄中正確設定。

fuchsia-boot:///my_package#my_component.cm 開機解析器會將這個網址解讀為封裝元件,其中含有「my_package」和 my_package 中繼根目錄的對應存在,且應用於建構「my_package」命名空間專屬的命名空間。

產品/圖片組合變更

Bootfs 映像檔建構是由建構系統執行 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 是套件中繼中繼.far 的 ID,並將套件名稱的對應新增至中繼.far 的 merkle 根目錄。建立啟動檔案系統映像檔後,地圖會以 JSON 格式寫入至「pkg_map」檔案 (如上方Bootfs 變更一節所述)。

我們選擇在映像檔組合層級實作這項轉換,原因有三個:

  • ProductAssembly 只會合併所屬套件中的套件清單。

  • ImageAssemblyConfig 驗證依然很簡單。

  • 「套件」產品組合中的驗證將繼續對「套件」運作。

實作

功能導入

您可以同時完成映像檔組合變更、啟動檔案系統變更、BootResolver 變更和 BootUrl 變更。

為了確保在實作過程中不會逐步導入 bootfs_package_labels 套件,我們會先從映像檔組合變更開始。我們會在啟動檔案系統套件資訊清單匯總後實作這項功能,並會檢查此組合是否為空白。

BootUrl 的語意將變更超過 3 個 CL。首先,在將第一個啟動檔案系統元件遷移至套件名稱使用速度之前,如果 BootUrl 包含套件路徑或存放區,我們就會將 BootUrl 視為無效 (以確保我們保留套件路徑的可用性來做為解決方案策略的指標)。其次,除了遷移至 fuchsia_package 的第一個啟動檔案系統元件外,我們也允許在 fuchsia-boot 網址中建立套件路徑。第三,加上最後一個啟動檔案系統元件遷移至 fuchsia_package 時,我們將禁止不含套件的搜尋引擎啟動網址。

遷移

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

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

效能

系統大小

在 x64 架構上,啟動檔案系統映像檔會以保守的方式增加約 70KiB。這是因為所有元件都遷移至套件後,啟動檔案系統大小會由新增的中繼.fars 和新的 pkg_map 檔案增加。其他部分則只需要為封裝在封裝前的啟動檔案系統檔案目錄項目重新命名。

執行階段的影響

目前,系統會在 Component_manager 啟動時將整個開機程序立即剖析成目錄。遷移之後,我們會將剖析工作延後到在 /boot 內設定元件的命名空間,直到該元件啟動為止。

除了先前剖析啟動檔案系統標頭的做法外,還有一些其他工作,我們必須剖析中繼.far。

ZBI 已簽署,因此目前我們不會驗證 zbi 中的啟動檔案系統檔案內容,只會驗證 zbi 本身。如果我們在啟動檔案系統中並未針對 blob 的執行階段驗證,就會嚴格維持目前的安全性位置。

有個潛在的錯誤指出,啟動檔案系統在現今的位置可能會找到自身,就是組件可能會將來源檔案錯誤地放在錯誤連結下方。在啟動檔案系統中出現 blob 意味著,我們可以視需要對啟動檔案系統 blob 的執行階段驗證,保護自身不受這種情況影響。我們一開始並不打算這麼做。

建構時間影響

zbi 建構的建構時間效能維持不變;遷移後,而不是針對啟動檔案系統中包含的每個依附元件行進目的地項目資訊清單,而是改為逐步讓套件資訊清單編碼,以對 blob 進行編碼。實際上,我們會追蹤完全相同的構件數量。

回溯相容性

本提案的許多變更都是在啟動檔案系統中獨立執行。在啟動檔案系統中更新執行單元時,一次更新單元沒有比一次整個啟動檔案系統執行個體更精細的程度。因此,如果系統提供的啟動檔案系統封裝實作不完整,並未提供封裝的啟動檔案系統元件,也不會有風險。

其中一個潛在相容性問題的原因,是,若映像檔組合工具自行納入舊版啟動檔案系統映像檔,以及含有新元件管理員的相同 zbi 中的新元件管理員。如果發生這種情況,當遷移作業完成,且啟動程序不允許非封裝元件時,可能會導致錯誤。不過,由於產品組合是如何建構啟動檔案系統,因此目前無法顯示這個狀態。

同樣地,「功能實作」一文中所述的 BootUrl 語意變更不會與功能實作或遷移作業有所不同,因為這些變更已包含在啟動檔案系統中。

安全性考量

套件名稱使用速度/程序沙箱機制,能夠減少程序可存取的構件,藉此嚴格提升執行階段的安全性。

啟動檔案系統映像檔會經過簽署且處於唯讀狀態,因此主要安全問題是映像檔組合器是否值得信任,與映像檔的格式無關。如果有的話,假如我們決定啟動啟動檔案系統映像檔應以漸進方式更新,那麼在啟動檔案系統中提供執行檔的根層級身分,將有機會進一步改善安全性。

隱私權注意事項

無。

測試

元件解析器和產品組合中的測試做法都已詳細記錄,日後也會擴充以涵蓋新功能。

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

說明文件

需要更新使用者空間啟動程序的 API 說明文件和啟動檔案系統映像檔組合的說明文件。

缺點、替代方案和未知

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

目前,意外在 bootfs_labels 清單依附元件清單中納入套件是免人工管理。這項變更生效後,是否出現套件在語意上具有重大意義。因此,我們需要不小心「清除」從 bootfs_labels 命名空間納入的套件。

這裡的唯一複雜度涉及 fuchsia_driver_packages。fuchsia_driver_package 是專屬的建構範本;產品組合與 fuchsia_driver_package 互動的方式,取決於驅動程式庫是否放入啟動檔案系統或 blobf。放在 bootfs_labels 中時,產品組合會逐步執行驅動程式庫套件,將驅動程式庫視為一組依附元件群組,而當驅動程式放在 blobf 中時,驅動程式庫會透過 meta.far 正確封裝。藉此讓單一驅動程式庫目標能根據產品在啟動檔案系統與 blobf 之間切換。過去可行,因為啟動檔案系統產品組合並未將任何套件在 deps 圖形中是否存在任何意義上。不過,透過啟動檔案系統」套件名稱行距,我們會將 fuchsia_package (及其相關聯的 package_manifest) 解讀為宣告,以將套件命名空間的中繼.far 編碼到啟動檔案系統中。

如果我們現在要放置啟動檔案系統命名空間功能,最後大約有 20 個驅動程式會同時以未封裝的 deps 模式放入啟動檔案系統,並透過相關聯的中繼.far 和相關聯的 blob 在啟動檔案系統中編碼。此外,在變更 Driver_manager 的執行元件前,驅動程式庫的 Meta.far 編碼將不會使用。我們並不是使用未使用的封裝擴充啟動環境,而是將所有 fuchsia_packages 清除,然後再逐步將目標遷移至適當的封裝。我們選擇清除 fuchsia_driver_packages 的 package_manifests,並導入新的 gn 中繼資料障礙,避免 list_package_manifests 順利抵達 fuchsia 驅動程式。這麼做可以大幅降低驅動程式庫程式產品組合邏輯的複雜度,而不是將每個使用 fuchsia_driver_package 的驅動程式庫分割為啟動檔案系統目標和 blobfs 目標。此外,建構中的單一套件定義可在最終結果中提供任一用途,因此可避免分割每個程序,然後移除所有「僅限舊版啟動檔案系統」虛擬套件。

遺憾的是,這有各自的複雜性,例如遮蔽真正的 fuchsia_package 目標,並對使用者隱藏其名稱。這會導致與其他現有和進行中的工作 (例如黃金測試) 不相容,以確認裝置上所有套件資訊清單的預期名稱都是在名稱,或者產品組合工作需要僅根據套件目標產生套件索引的標籤。

因此,較不干擾的做法是只新增標籤,並將早期啟動中的元件遷移至套件時,請將元件從舊標籤移到新標籤中。我們最終必須合併這些清單,如果驅動程式尚未遷移至適當的元件,就需要重新查看 fuchsia-驅動程式庫-套件範本的複雜度。

在產品或圖片組合作業中導入

  • 系統是否應在產品組件或映像檔組裝層級剖析啟動檔案系統元件的套件資訊清單剖析作業?也就是說,我們是否應該延遲資訊清單剖析作業,直到要呼叫 Zbi 工具實際建構啟動檔案系統時為止?
    • ProductAssembly (ffx 組裝產品)
    • 優點:
      • ImageAssembly 一直專注於產生自己圖片檔 (zbi、blobf 等)
      • ImageAssemblyConfig 包含一份較簡單的清單,列出所有啟動檔案系統檔案。
    • 缺點:
      • 因為用於產生舊版組合輸入組合的 ImageAssemblyConfig 必須與 ProductAssembly 建立的 ImageAssemblyConfig 相符 (或者驗證需要捨棄/削減),因此需要進行較為複雜的驗證。
      • 產品組合結束時完成的驗證,必須知道可在哪個位置找到啟動檔案系統元件的「套件」(例如,針對結構化設定進行的值/檔案存在驗證)。
    • ImageAssembly (ffx 組合建立系統)
    • 優點:
      • ProductAssembly 只會將 不同套裝組合中的套件清單合併
      • ImageAssemblyConfig 驗證方式很簡單
      • 「套件」產品組合中的驗證將繼續對「套件」運作。
    • 缺點:
      • ImageAssemblyConfig 不再包含啟動檔案系統檔案的完整內容。
      • 建立 zbi 前,Image Assembly 必須改為執行套件 -> 至項目對應。

要在啟動檔案系統中對中繼.fars 進行編碼,或是在建構開機映像檔時設定命名空間嗎?

其中一種替代方法是建構映像檔,藉此為啟動檔案系統中的「命名空間」編碼,讓 /boot 底下每個元件都有自己的子目錄,這個元件管理員會用來當做元件的命名空間根目錄。

在啟動檔案系統的中繼.fars 編碼中,我們透過啟動解析器,在 SWD 套件解析程式庫中加入依附元件,藉此解讀中繼.far 格式,並負責維護及持續維護這個做法,以便在中繼資料 (Meta.far) 格式發生變更時繼續運作。

在建構映像檔期間執行命名空間設定時,我們會教導系統如何解讀 package-manifest/meta.far 格式,以及編寫程式碼,將中繼.far 格式轉譯為啟動檔案系統映像檔中的套件名稱空間,藉此增加產品組合的複雜度。換句話說,新的程式碼將由另一個團隊負責解讀中繼資料值,並負責將其同步處理。SWD 團隊表示,新的團隊對於中繼.far 格式的依附元件有所疑慮,而只要整合單一套件編碼,並重複使用由 SWD 維護的工具與該格式互動,即可完全避免這種情況。

這兩種策略的資源影響幾乎相等 (KiB 順序)。

最後,套件不僅是編碼的命名空間,如要使用平台版本管理等執行階段功能,則必須使用 meta.fars 中的中繼資料,例如套件版本。如未使用 metadata.fars,我們需要在以啟動檔案系統架構為基礎的命名空間中,加入一種編碼這個「額外資訊」的新方式,並教導元件管理員這類額外套件中繼資料編碼的服務。