| RFC-0167:早期使用者空間啟動程序中的套件 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 將套件導入 BootFS 和開機解析。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2022-05-10 |
| 審查日期 (年-月-日) | 2022-06-13 |
摘要
這項 RFC 建議在啟動檔案系統中導入套件。這項功能可將使用者模式的套件隔離和命名空間優點帶到早期啟動程序,並移除第三方驅動程式庫開發的阻礙。
提振精神
早期啟動可執行組件和沙箱化技術,是在元件架構具備穩固的封裝架構之前就已存在,時至今日,我們仍未投入資源,將現在可供後續使用者空間使用的工具,帶入早期啟動程序。因此,使用者空間啟動程序面臨了許多問題,而這些問題正是導入封裝技術的原因,例如程序沙箱、可驗證內容和可變動的程式庫版本。
這些早期啟動問題會導致啟動程序與啟動檔案系統映像檔互動時,出現不必要的複雜情況和效率低落問題,而採用早期啟動封裝化,就能大幅消除這些問題。早期啟動的封裝化作業可提供改善系統健康狀態的機會,而不僅是解決現有問題。在整個使用者空間中,針對可執行檔和程式庫採用標準化的內容 ID,可讓我們重複使用先前不相連儲存空間 (例如啟動檔案系統和 blobfs) 中的等效資料副本。
元件架構具有「套件」概念,可做為 fuchsia-pkg 的抽象層;雖然元件架構和封裝系統嚴格來說是分開的,但兩者緊密交織,因此元件化和封裝世界目標是合作關係。
近期工作 (例如元件管理員做為第一個後使用者啟動程序執行) 正在推動「一路向下都是元件」的架構。即使是檔案系統和裝置驅動程式等早期啟動的可執行檔,也會以 Fuchsia 元件的形式啟動,或目前正朝這個模型遷移。現在是時候將系統的元件化與「一路向下封裝」的系統組件相符,並將所有促使封裝的值帶入使用者空間啟動程序。
具體來說,由於無法產生有效的 Fuchsia 映像檔 (其中啟動檔案系統中編碼的可執行檔在共用程式庫依附元件中存在版本偏差),因此啟動檔案系統中缺少套件命名空間會阻礙樹狀結構外驅動程式庫開發。此外,今天的啟動檔案系統映像檔會洩漏 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
已諮詢:
社交化:透過設計文件與利害關係人探討主題,然後在 RFC 前開放 tq-eng 進行一般討論。
設計
這項異動涉及對啟動檔案系統和 /boot 目錄、BootResolver、BootUrl 和產品組件的變更。
啟動檔案系統變更
圖片變更
紫紅色啟動檔案系統套件會以啟動檔案系統中的 meta.far 檔案表示,而啟動檔案系統目錄項目會將其命名為 blob/<merkle root of meta.far>。套件資訊清單中的每個 Blob 都會在啟動檔案系統中收到新的目錄項目,blob/<merkle root of the dependency>。
- 我們已在執行階段計算 merkle 來驗證內容,速度也夠快,因此在 zbi 建立等程序中執行這項操作 (也會壓縮圖片) 並不重要。
- 這項提案要求的新映像檔建構程序,會在實際建構映像檔的步驟之前,先判斷內容 ID,以便填入 meta.far 檔案。這表示在產生資訊清單以驅動映像檔建構步驟的過程中,大部分的內容重複資料刪除作業都已完成。請確保我們已在這個階段完成所有可能的重複資料刪除作業,這應該相當簡單。
討論在 /blob 下的啟動檔案系統中新增項目時,我們只會參考新增的啟動檔案系統目錄項目,該項目會參照相同的基礎檔案 (概念上為硬連結)。
啟動檔案系統中名為「pkg_map」的新檔案會維護從人類可讀的套件名稱到 meta.far Merkle 根的對應,後者會編碼套件。
啟動檔案系統大小會因新加入的 meta.fars 和新的 pkg_map 檔案而增加。 其他所有項目都只是啟動檔案系統檔案的新目錄項目,該檔案已存在。保守估計,在 x64 架構上,bootfs 會增加約 70KiB 的壓縮大小。
/boot 變更
/boot 中會新增名為 /blob 的子目錄。名稱前置字串為 /blob 的啟動檔案系統映像檔中所有檔案,都會放置於該處。將啟動檔案系統中的所有元件遷移至套件後,頂層目錄只會包含核心 vmos、Shell 指令碼,以及 /boot 成為元件管理員「命名空間」所需的檔案。
元件管理員命名空間一開始會是 /boot 的子目錄,並「配置」為所有依附元件預期的樣子。最終目標是將元件管理工具也匯聚到 meta.far 編碼中,並依賴 SWD 堆疊進行解析。
BootResolver 變更
套件命名空間啟動檔案系統元件的核心工作大量重疊,必須將容易理解的套件名稱對應至含有內容 ID 的 meta.far,並解碼 meta.far,然後使用其內容檔案建構命名空間。因此,設計目標是盡可能重複使用現有的套件解析器邏輯。
如要重複使用套件解析器的邏輯,最簡單的進入點是 package-directory::serve。這個進入點會取得 BlobFS 和內容 ID (識別 meta.far)、開啟 meta.far、剖析 meta/contents 檔案,並建構及提供檔案中編碼的命名空間。只要我們能將 Blob 的啟動檔案系統支援目錄提供給 API 做為 BlobFS 用戶端,就可以直接重複使用這個程式庫。
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 啟動配置,導入新的元件載入作業,而不是導入新的網址配置和解析器。網址中是否有套件路徑,會成為判斷是否應使用「BootResolver 變更」一節所述新解析路徑的指標。
例如:
fuchsia-boot:///#my_component.cm bootresolver 會將這個網址解讀為未封裝的元件,其命名空間已在 /boot 目錄中正確設定。
fuchsia-boot:///my_package#my_component.cm bootresolver 會將這個網址解讀為封裝元件,其中「my_package」到 meta.far 的 Merkle 根目錄的對應存在,且應使用該對應建構「my_package」命名空間專屬的命名空間。
產品/圖片組合變更
Bootfs 映像檔的建構作業分為兩個階段,先由建構系統執行工作,再由映像檔組裝系統執行工作。
首先,我們會導入名為 bootfs_packages 的新 gn 變數。
這份清單會在 product.gni 中宣告,並指派給名為 bootfs_package_labels 的呼叫端變數,傳遞至任何將 build/input:bootfs 指派給 bootfs_labels 變數的 assemble_system 呼叫。
將啟動檔案系統元件從無套件編碼遷移至啟動檔案系統套件時,我們會將其從包含該元件的群組依附元件中移除,並新增至 bootfs_packages 依附元件集。bootfs_labels
接著,在產生映像檔組裝設定時,我們會使用現有的 list_package_manifests 範本,從呼叫端的 bootfs_package_labels 變數中定義的套件收集套件資訊清單。
接著,映像檔組裝會從建構遍歷取得資訊清單,並使用這些資訊清單呼叫 zbi 等工具,將建構目錄中的檔案封裝成映像檔。套件資訊清單格式包含「blob」物件清單,在建立映像檔時,系統會使用這些物件將名為 blob/<merkle_root> 的檔案新增至啟動檔案系統映像檔。
在疊代這些 Blob 物件時,我們會檢查哪些 Blob 是套件 meta.far 的 ID,並將套件名稱對應至 meta.far 的 Merkle 根目錄,然後新增至地圖。建立啟動檔案系統映像檔後,地圖會以 JSON 格式寫入上述「bootfs 變更」一節所述的「pkg_map」檔案。
我們選擇在 Image Assembly 層級實作這項轉換,原因有三:
ProductAssembly 只會合併各種套件的套件清單。
ImageAssemblyConfig 驗證程序仍相當簡單。
產品組合中的「套件」驗證作業會繼續對「套件」執行。
實作
功能導入
您可以同時進行映像檔組裝、啟動檔案系統、BootResolver 和 BootUrl 的變更。
為確保不會在實作過程中,看到 bootfs_package_labels 導入套件,我們將從映像檔組裝變更開始。我們會實作這項功能,直到啟動檔案系統套件資訊清單匯總為止,並放置建構時間檢查,確認該集合為空白。
BootUrl 的語意將在 3 個 CL 中變更。首先,在將第一個啟動檔案系統元件遷移至套件命名空間之前,如果 BootUrl 包含套件路徑或存放區,我們會將其視為無效 (確保保留套件路徑的可用性,做為解析策略的指標)。其次,隨著第一個遷移至 fuchsia_package 的啟動檔案系統元件,我們將允許 fuchsia-boot 網址中的套件路徑。第三,隨著最後一個遷移至 fuchsia_package 的啟動檔案系統元件,我們將禁止使用不含套件的 fuchsia-boot 網址。
遷移
完整導入這些功能後,我們會逐步遷移啟動檔案系統元件。特定元件的遷移作業如下:
- 我們會找出元件的未封裝依附元件 (.cml 檔案、二進位檔等) 新增至 bootfs_labels 依附元件的位置。
- 我們會將該未封裝的依附元件集合轉換為 fuchsia_package gn 目標。
- 我們會從現有的 bootfs_labels 群組中移除套件,並將其新增至 bootfs_package_labels 群組。
- 我們會更新 bootstrap.cml 檔案中元件的網址,加入套件名稱。
效能
系統大小
保守估計,在 x64 架構上,啟動檔案系統映像檔會增加約 70 KiB 的壓縮大小。這是因為所有元件遷移至套件後,啟動檔案系統大小會因新加入的 meta.fars 和新的 pkg_map 檔案而增加。其他所有項目都只是重新命名啟動檔案系統檔案的目錄項目,該檔案先前已封裝。
執行階段影響
目前,整個啟動檔案系統會在 component_manager 啟動時,急切地剖析到目錄中。遷移後,我們會延後剖析工作,直到啟動元件為止,這相當於在 /boot 中設定元件的命名空間。
除了先前只是剖析啟動檔案系統標頭之外,還有一些額外工作;我們必須剖析 meta.far。
ZBI 已簽署,因此我們今天不會驗證 ZBI 內啟動檔案系統檔案的內容,只會驗證 ZBI 本身。如果我們沒有在啟動檔案系統中對 Blob 執行階段驗證,安全性與現在完全相同。
啟動檔案系統目前可能處於的其中一個錯誤狀態是,組件可能會將來源檔案錯誤地放在錯誤的啟動檔案系統連結下。啟動檔案系統中存在 Blob,表示如果我們願意,可以透過執行階段驗證啟動檔案系統 Blob,避免發生這種情況。我們一開始不打算這麼做。
建構時間影響
zbi 建構的建構時間效能維持不變;遷移後,系統會走訪套件資訊清單來編碼 Blob,而不是走訪開機檔案系統中每個依附元件的目的地項目資訊清單。在實務上,我們走訪的構件數量完全相同。
回溯相容性
這項提案中的許多變更都包含在啟動檔案系統中。更新啟動檔案系統內的執行單元時,只能一次更新整個啟動檔案系統,無法以更小的粒度更新。因此,系統不會提供封裝的啟動檔案系統元件,也不會有啟動檔案系統封裝實作不完整的問題。
如果映像檔組裝工具以某種方式,在同一個 zbi 中包含舊版啟動檔案系統映像檔和新的元件管理工具,就可能發生回溯相容性問題。如果發生這種情況,一旦遷移完成,且啟動檔案系統中不允許使用未封裝的元件,就可能導致錯誤。不過,由於產品組裝會建構啟動檔案系統,因此目前不會發生這種情況。
同樣地,功能實作中描述的 BootUrl 語意變更,也不會與功能實作或遷移作業有所差異,因為這也包含在啟動檔案系統中。
安全性考量
套件命名空間/程序沙箱化可減少程序可存取的構件,進而嚴格提升執行階段安全性。
啟動檔案系統映像檔已簽署且為唯讀,因此主要安全問題在於映像檔組裝工具是否受信任,與映像檔的格式無關。如果我們決定要逐步更新啟動檔案系統映像檔,在啟動檔案系統中提供可執行檔的 Merkle 根身分識別資訊,或許能進一步提升安全性。
隱私權注意事項
無。
測試
元件解析器和產品組裝的測試做法已妥善記錄,並將擴大涵蓋新功能。
在語意變更的 3 個階段中,都會加入 BootUrl 剖析器測試,以顯示系統正在強制執行 Url 的預期行為。
說明文件
使用者空間啟動的 API 說明文件,以及有關啟動檔案系統映像檔組裝的說明文件,都需要更新。
缺點、替代方案和未知事項
重複使用 bootfs_labels 與導入新標籤。
目前,如果 bootfs_labels 清單的依附元件清單中意外納入套件,系統會視為無作業。這項變更生效後,套件的存在將具有語意上的意義。因此,我們需要從 bootfs_labels 命名空間「清除」無意中納入的套件。
這裡唯一複雜的地方是 fuchsia_driver_packages。fuchsia_driver_package 是獨特的建構範本;產品組裝與 fuchsia_driver_package 的互動方式,取決於驅動程式庫是放在啟動檔案系統還是 blobfs。如果放在 bootfs_labels 中,產品組裝會逐步處理驅動程式庫套件,將驅動程式庫視為一組依附元件;如果放在 blobfs 中,驅動程式庫會透過 meta.far 正確封裝。這麼做是為了讓單一驅動程式庫目標能夠根據產品,在啟動檔案系統和 blobfs 之間切換。過去之所以能運作,是因為啟動檔案系統產品組件不會在 deps 圖表中為套件的存在與否指派任何重要性。不過,有了啟動檔案系統套件命名空間,我們就會將 fuchsia_package (及其相關聯的 package_manifest) 的存在解讀為宣告,將 meta.far 編碼至啟動檔案系統的套件命名空間。
如果我們今天推出啟動檔案系統命名空間功能,大約會有 20 個驅動程式最終會同時放在啟動檔案系統中做為未封裝的依附元件,也會在相關聯的 meta.far 和相關聯的 Blob 中編碼;在驅動程式管理員的執行器變更之前,驅動程式庫的 meta.far 編碼不會使用。我們希望清除啟動檔案系統中的所有 fuchsia_packages,然後逐步將目標遷移至適當的封裝,而不是以未使用的封裝擴充啟動檔案系統。我們選擇導入新的 gn 中繼資料障礙,防止 list_package_manifests 走訪 Fuchsia 驅動程式,藉此清除 fuchsia_driver_packages 的 package_manifests。這麼做是為了大幅簡化驅動程式庫產品組裝邏輯,而不是將使用 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 不再包含啟動檔案系統檔案的完整內容。
- Image Assembly 需要執行套件 -> 項目對應,然後才能建立 zbi。
在啟動檔案系統中編碼 meta.fars,或在啟動檔案系統映像檔建構時設定命名空間?
另一種替代方法是在啟動檔案系統中編碼「命名空間」,方法是建構映像檔,讓 /boot 下的每個元件都有自己的子目錄,元件管理員會將該子目錄做為元件的命名空間根目錄。
在啟動檔案系統中編碼 meta.fars 時,我們會在 boot-resolver 中導入 SWD 套件解析程式庫的依附元件,藉此增加早期啟動的依附元件。這些程式庫用於解讀 meta.far 格式,且會持續維護,以因應 meta.far 格式的變更。
在建構映像檔期間執行命名空間設定時,我們會教導產品如何解讀 package-manifest/meta.far 格式,並編寫程式碼將 meta.far 格式轉換為啟動檔案系統映像檔中的套件命名空間,藉此提高產品組裝的複雜度。這表示需要編寫新程式碼,且第二個團隊會「參與」解讀 meta.far 格式,並負責讓產品與該格式保持同步。SWD 團隊擔心新團隊會對 meta.far 格式產生依附元件,而只要採用單一套件編碼,並重複使用 SWD 維護的工具與該格式互動,就能完全避免這種情況。
這兩種策略的資源影響幾乎相同 (KiB 級別)。
最後,套件不僅僅是編碼的命名空間,執行階段功能 (例如平台版本控管) 需要 meta.fars 中的中繼資料,例如套件版本。如果沒有使用 meta.fars,我們就必須在以啟動檔案系統為基礎的命名空間中,導入某種新的「額外資訊」編碼方式,並教導元件管理員等服務,瞭解這種額外的套件中繼資料編碼方式。