從 Zircon 核心啟動使用者空間啟動程序 (使用者啟動程序)

Zircon 採用微核心設計。微核心設計的複雜性在於如何啟動初始使用者空間程序。通常,這項作業會透過讓核心實作最小版本的檔案系統讀取和程式載入,只為了引導目的而達成,即使這些核心設施在啟動後從未使用過也一樣。Zircon 則採用不同的做法。

啟動載入程式和核心啟動

系統啟動載入程式會將核心載入記憶體,並將控制權轉移至核心的啟動程式碼。本文未說明啟動載入器通訊協定的詳細資訊。與 Zircon 搭配使用的啟動載入程式會以 Zircon 啟動映像檔格式載入核心映像檔和資料 blob。ZBI 格式是一種簡單的容器格式,可嵌入啟動載入器傳遞的項目,包括硬體專屬資訊、提供啟動選項的 核心「命令列」,以及 RAM 磁碟映像檔 (通常已壓縮)。核心會在開機初期階段擷取一些必要資訊,供自己使用。

BOOTFS

Zircon 啟動映像檔中嵌入的項目之一,是初始 RAM 磁碟檔案系統映像檔。圖片通常會使用 zstd 格式進行壓縮。解壓縮後,映像檔會以 BOOTFS 格式顯示。這是一個簡單的唯讀檔案系統格式,只會列出檔案名稱,以及每個檔案在 BOOTFS 映像檔中的偏移量和大小 (兩個值都必須與頁面對齊,且長度上限為 32 位元)。

主要 BOOTFS 映像檔包含使用者空間系統需要執行的所有內容:可執行檔、共用程式庫和資料檔案。這包括實作裝置驅動程式和更進階的檔案系統,讓您可以從儲存空間或網路裝置讀取更多程式碼和資料。

系統完成自助啟動後,主要 BOOTFS 中的檔案就會成為以 /boot 為根目錄的唯讀檔案系統樹狀結構 (並由元件管理工具提供服務)。

核心載入使用者啟動程序

核心不包含任何用於解壓縮 zstd 格式的程式碼,也不包含任何用於解讀 BOOTFS 格式的程式碼。相反地,所有這類工作都是由第一個稱為 userboot 的使用者空間處理程序完成。

userboot 是一般使用者空間處理程序。它只能像任何其他程序一樣,透過 vDSO 發出標準系統呼叫,並受完整的 vDSO 執行 制度規範。userboot 的特別之處在於載入方式。

userboot 會以 ELF 動態共用物件形式建構,使用與 vDSO 相同的 RODSO 版面配置。與 vDSO 一樣,userboot ELF 映像會在編譯時嵌入核心。其簡單的版面配置表示,載入時不需要核心在啟動時解讀 ELF 標頭。核心只需要知道三件事:唯讀區段的大小、可執行區段的大小,以及 userboot 進入點的位址。在編譯期間,這些值會從 userboot ELF 映像檔中擷取,並用於核心程式碼中的常數。

如同其他程序一樣,userboot 必須從已對應至其位址空間的 vDSO 開始,才能發出系統呼叫。核心會將 userboot 和 vDSO 對應至第一個使用者程序,然後在 userboot 進入點開始執行。

核心傳送 processargs 訊息

在一般程式載入作業中,系統會將啟動訊息傳送至每個新程序。程序的第一個執行緒會在註冊中接收管道句柄。接著,它就能讀取資料和創作者傳送的句柄。

核心會使用完全相同的通訊協定啟動 userboot。核心指令列會分割成字詞,這些字詞會成為引導訊息中的環境字串。這則訊息包含 userboot 本身所需的所有句柄,以及系統其他部分用於存取核心設施的句柄。按照一般格式,句柄資訊項目會說明每個句柄的用途。包括 PA_VMO_VDSO 句柄

使用者啟動程序在 vDSO 中找到系統呼叫

標準慣例可讓新程序瞭解其 vDSO 對應項目,但程序必須解讀 vDSO 的 ELF 標頭和符號表,才能找出系統呼叫的進入點。為避免這種複雜性,userboot 會以不同方式在 vDSO 中尋找進入點。

當核心將 userboot 對應至第一個使用者程序時,會選擇記憶體中的隨機位置,就像一般程式載入作業一樣。不過,當系統對其中的 vDSO 進行對應時,不會像平常一樣選擇其他隨機位置。而是會立即在記憶體中將 vDSO 映像檔置於 userboot 映像檔之後。這樣一來,vDSO 程式碼一律會位於 userboot 程式碼的固定偏移位置。

在編譯期間,系統會從 vDSO ELF 映像檔中擷取所有系統呼叫進入點的符號表項目。接著,這些符號會轉換為連結器指令碼符號定義,這些定義會使用每個符號在 vDSO 映像檔中的固定偏移量,藉此從連結器提供的 _end 符號定義該符號。如此一來,userboot 程式碼就能在 userboot 映像檔本身之後,直接呼叫記憶體中每個 vDSO 進入點的確切位置。

使用者啟動程序會解壓縮 BOOTFS

userboot 首先會讀取核心傳送的啟動訊息。在從核心取得的句柄中,其中一個具有句柄資訊項目 PA_HND(PA_VMO_BOOTDATA, 0)。這是一個VMO,其中包含來自啟動載入程式的 ZBI。userboot 會從這個 VMO 讀取 ZBI 標頭,尋找第一個類型為 ZBI_TYPE_STORAGE_BOOTFS 的項目。其中包含 BOOTFS 映像檔。項目的 ZBI 標頭會指出是否已壓縮 (通常是已壓縮)。userboot 會在 VMO 的這個部分進行對應。userboot 包含 zstd 格式支援程式碼,用於將項目解壓縮為新的 VMO。

使用者啟動程序會從 BOOTFS 載入第一個「實際」使用者程序

接著,userboot 會檢查從核心收到的環境字串,這些字串代表核心指令列。如果有字串 userboot.next=file+optional_arg1+optional_arg2=foo+...,則 file 會載入為第一個實際使用者程序,並將以「+」分隔的引數傳遞給該程序。如果沒有這類選項,預設的檔案bin/component_manager+--boot。這些檔案位於 BOOTFS 映像檔中。

userboot 會實作功能齊全的 ELF 程式載入器,用於載入檔案。通常,載入的檔案是具有 PT_INTERP 程式標頭的動態連結可執行檔。在這種情況下,userboot 會尋找 PT_INTERP 中命名的檔案,並改為載入該檔案。

接著,userboot 會在隨機位址載入 vDSO。它會以標準慣例啟動新程序,並傳遞管道句柄和 vDSO 基本位址。在該管道中,userboot 會傳送標準的 processargs 訊息。它會傳遞從核心收到的所有重要句柄 (將特定句柄 (例如程序本身和執行緒本身句柄) 替換為新程序的句柄,而非 userboot 本身)。

使用者啟動程序載入器服務

依照標準程式載入通訊協定,當 userboot 透過 PT_INTERP 載入程式時,會在主訊息前傳送額外的 processargs 訊息,用於動態連結器的使用。此訊息包含管道的 PA_LDSVC_LOADER 句柄,userboot 會在該頻道上提供標準載入器服務的最低實作項目。

userboot 只有一個執行緒,會持續處理載入器服務要求的迴圈,直到管道關閉為止。收到 LOADER_SVC_OP_LOAD_OBJECT 要求時,會以 BOOTFS 中的檔案形式查詢前置符號為 lib/ 的物件名稱,並傳回其內容的 VMO。因此,第一個「實際」使用者程序可以是 (通常也是) 需要各種共用程式庫的動態連結可執行檔。動態連結器、可執行檔和共用程式庫都會從相同的 BOOTFS 頁面載入,這些頁面稍後會以檔案形式顯示在 /boot 中。

userboot 載入的可執行檔 (即 component manager) 通常應在啟動完成後關閉其載入器服務管道。這樣一來,userboot 就會知道不再需要該值。

使用者啟動程序終止服務

當載入器服務管道關閉 (或執行檔沒有 PT_INTERP,因此不需要載入器服務,然後在程序啟動後),userboot 就不再需要執行任何操作。

如果在核心指令列上提供 userboot.shutdown 選項userboot 會等待所啟動的程序結束,然後關閉系統 (就像使用 power shutdown 指令一樣)。這項功能可用於執行單一測試程式,然後關閉機器 (或模擬器)。舉例來說,指令列 userboot.next=bin/core-tests userboot.shutdown 會執行 Zircon 核心測試,然後關閉。

否則,userboot 不會等待程序結束。userboot 會立即退出,讓第一個「實際」使用者程序負責啟動及關閉系統的其餘部分。