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 程式碼就能直接呼叫每個 vDSO 進入點,並在 userboot 映像檔本身之後,在記憶體中顯示的確切位置。
使用者啟動程序解壓縮 BOOTFS
userboot 的第一件事是讀取核心傳送的啟動訊息。在從核心取得的控制代碼中,有一個控制代碼包含控制代碼資訊項目 PA_HND(PA_VMO_BOOTDATA, 0)。這是包含系統啟動載入程式 ZBI 的 VMO。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 會等待啟動的程序結束,然後關閉系統 (就像使用 power shutdown 指令一樣)。這項功能很實用,可執行單一測試程式,然後關閉機器 (或模擬器)。例如,指令列 userboot.next=bin/core-tests userboot.shutdown 會執行 Zircon 核心測試,然後關閉。
否則,userboot 不會等待程序結束。userboot 會立即結束,並由第一個「實際」使用者程序負責啟動和關閉系統的其他部分。