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

Zircon 是微核心的設計。微核心設計的複雜性在於如何啟動初始使用者空間程序。之所以要這麼做,通常是為了讓核心只為了啟動程序而實作最小版本的檔案系統讀取與程式載入功能,即使這些核心設施在啟動後從未使用過。Zircon 採取不同的做法。

系統啟動載入程式和核心啟動

系統啟動載入程式會將核心載入記憶體,並將控制權轉交給核心的啟動程式碼。本文不會詳細說明系統啟動載入程式通訊協定。與 Zircon 搭配使用的啟動載入程式會載入採用 Zircon Boot Image 格式的核心映像檔和資料 blob。ZBI 格式是一種簡單的容器格式,可嵌入由系統啟動載入程式傳送的項目,包括硬體專屬資訊、提供啟動選項的核心「指令列」,以及 RAM 磁碟映像檔 (通常為壓縮)。核心會擷取一些重要資訊,供其在啟動階段的早期階段使用。

BOOTFS

嵌入 Zircon Boot 映像檔的其中一個項目是初始 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 程式碼就能直接呼叫每個 vDSO 進入點,位置會在 userboot 圖片本身之後顯示在記憶體中的確切位置。

使用者啟動程序解壓縮 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 要求時,系統會查詢 lib/ 前置字串的物件名稱做為 BOOTFS 中的檔案,並傳回其內容的 VMO。因此,第一個「實際」使用者程序可能是 (而且通常是) 需要多個共用程式庫的動態連結執行檔。動態連結器、執行檔和共用程式庫都會從同一個 BOOTFS 頁面載入,之後會以檔案的形式顯示在 /boot 中。

完成啟動後,userboot (即 component manager) 要載入的執行檔應一般關閉其載入器服務管道。這可讓 userboot 瞭解該商品已不需要了。

隨著使用者啟動程序腳踏車騎行

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

如果已在核心指令列提供 userboot.shutdown 選項,則 userboot 會等待啟動的程序結束,然後關閉系統 (就像使用 dm shutdown 指令一樣)。如要執行單一測試程式,然後關閉裝置 (或模擬器),這種做法會很實用。舉例來說,指令列 userboot.next=bin/core-tests userboot.shutdown 會執行 Zircon 核心測試,然後關閉。

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