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

Zircon 採用微核心設計。微核心設計的複雜性是如何啟動初始使用者空間程序。之所以做到這一點,通常是為了達到此目的,讓核心為了啟動程序而實作核心的檔案系統讀取和程式載入最低版本,即使啟動時間之後未使用這些核心功能也一樣。Zircon 會採取不同的做法。

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

系統啟動載入程式會將核心載入記憶體,並將控制項轉移至核心的啟動程式碼。本文不提供系統啟動載入程式通訊協定的詳細資料。與 Zircon 搭配使用的啟動載入程式會同時載入核心映像檔和資料 blob (採用 Zircon Boot Image 格式)。ZBI 格式是一種簡易的容器格式,可嵌入系統啟動載入程式傳遞的項目,包括硬體專屬資訊、提供啟動選項的核心「指令列」,以及 RAM 磁碟映像檔 (通常經過壓縮)。核心會在啟動階段的早期階段擷取一些基本資訊,供自己的用途使用。

BOOTFS

Zircon Boot 映像檔中嵌入的其中一個項目是初始 RAM 磁碟檔案系統映像檔。影像通常會使用 LZ4 格式壓縮。解壓縮後的圖片會採用 BOOTFS 格式。這是一個簡單的唯讀檔案系統格式,其中只會列出檔案名稱,而 BOOTFS 圖片中的每個檔案偏移值和大小 (這兩個值都必須以頁面對齊,且上限 32 位元)。

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

系統自行啟動後,主要 BOOTFS 中的檔案就會成為在 /boot 根層級的唯讀檔案系統樹狀結構 (並由元件管理員提供)。

核心載入使用者啟動程序

核心不包含任何解壓縮 LZ4 格式的程式碼,也沒有任何用來解譯 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 包含 LZ4 格式支援代碼,可用來將項目解壓縮到新的 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 會等待其啟動程序結束,然後關閉系統 (就像使用 dm shutdown 指令一樣)。這有助於執行單一測試程式,然後關閉裝置 (或模擬器)。舉例來說,指令列 userboot.next=bin/core-tests userboot.shutdown 會執行 Zircon 核心測試,然後關閉。

否則,userboot 不會等待程序結束。userboot 會立即結束,導致第一個「實際」使用者程序啟動及移除系統的其餘部分。