一、PEI Core 與 PEIMs
PEI Core:是PEI階段的核心引擎,負責尋找,加載和執行各種PEIM。PEI Core首先首先運行一個小的固定的SEC階段的代碼,這段代碼通常使用CPU緩存作為臨時內存來運行。一旦找到並初始化了真正的系統內存,PEI核心就會把執行權交給永久內存管理器,從而結束使用CAR。
PEIMs:這些是實際執行硬件初始化任務的功能模塊。執行芯片組、內存控制器、橋接器和核心主板組件的最低限度初始化。關鍵任務是初始化 DRAM,使其可用於後續的啓動階段。創建 Hand-Off Blocks (HOB) 列表,這是一個數據結構,用於保存所有已發現的硬件狀態、配置信息和資源映射,並將這些信息傳遞給下一個啓動階段——DXE 階段。
下面對這兩部分的流程進行更具體的介紹:
PEI Core 主要是提供一個基本的執行環境然後找到並執行 PEIMs,它才是真正幹活的。
| 關鍵職責 | 具體任務 | 對應的概念/API |
|---|---|---|
| A. 環境建立 | 在 SEC 階段交接後,立即建立 PEI Core 自身的堆棧和數據結構(最初在 CAR 中)。 | CAR (Cache As RAM) |
| B. 服務提供 | 提供 PEI 階段的基本服務(如內存分配、HOB 創建)。 | PEI Services Table |
| C. PEIM 調度 | 搜索固件存儲空間 (FV) 以發現待執行的 PEIM 映像,並按順序執行它們。 | FV (Firmware Volume) |
| D. 內存轉換 | 一旦 DRAM 被初始化,它將所有 PEI 結構從 CAR 遷移到永久內存中。 | PeiServicesInstallPeiMemory() |
| E. 階段交接 | 所有 PEIMs 執行完畢後,構建最終的 HOB 列表,並跳轉到 DXE Core。 | DXE IPL (Initial Program Load) |
PEIMs 是實際的工作執行者,它們通過“提供 PPI”(PEIM-to-PEIM Interface)和“註冊 Notify”機制來完成工作和進行協作。
| 關鍵職責 | 具體任務 | 對應的機制 |
|---|---|---|
| A. 硬件初始化 | 初始化芯片組、時鐘、電源管理等關鍵硬件。 | PCHInitPeim, MemoryInitPeim |
| B. 資源發現 | 找到並配置系統內存 (DRAM),並報告給 PEI Core。 | Memory Init PEIM |
| C. 數據發佈 | 創建並安裝 PPI (PEI Protocol Interface),讓其他 PEIM 知道某個服務或數據已可用。 | PeiServicesInstallPpi() |
| D. 階段準備 | 收集硬件數據,創建 HOB,將配置信息傳遞給 DXE。 | PeiServicesCreateHob() |
偽代碼:
// -------------------------------------------------------------------------
// 階段 0: 接管和臨時環境建立 (運行在 CAR/Cache As RAM)
// -------------------------------------------------------------------------
Function PeiCoreMain(SecCoreData, HandoffData)
// 1. 設置 PEI Services Table
PeiServicesTable = InitializePeiServicesTable()
// 2. 初始化臨時堆棧和臨時堆 (基於CAR)
InitializePeiCoreStack(SecCoreData.StackBase)
InitializePeiTemporaryMemoryHeap()
// 3. 創建第一個 HOB: Handoff Information HOB
CreateHob(HOB_TYPE_HANDOFF, HandoffData)
// 4. 初始化 PEIM 調度器
PeimDispatcher = InitializePeimDispatcher()
// 5. 開始 PEIM 主循環
Call PeimExecutionLoop(PeimDispatcher, PeiServicesTable)
// 6. 退出 PEI 階段
Call PeiCoreExit()
End Function
// -------------------------------------------------------------------------
// 階段 1: PEIM 調度與執行主循環
// -------------------------------------------------------------------------
Function PeimExecutionLoop(Dispatcher, Services)
// 循環條件: 只要在 Firmware Volume (FV) 中還能找到未執行的 PEIM
While (Dispatcher.FindNextPeimImage() != NULL)
CurrentPeim = Dispatcher.FindNextPeimImage()
// 1. 加載 PEIM 代碼
CurrentPeim.Image = Services.FfsFindPeim(CurrentPeim.FilePath)
// 2. 執行 PEIM 的入口點 (EntryPoint)
// 實際的硬件初始化和 PPI 安裝都在這裏發生
Status = CurrentPeim.EntryPoint(CurrentPeim.Image, Services)
If (Status == EFI_SUCCESS)
MarkPeimAsExecuted(CurrentPeim)
// 3. 檢查是否有內存初始化發生
If (Services.IsMemoryInstalled() == TRUE)
// 內存初始化 PEIM 已經運行,進入關鍵的轉換流程
Call PeimMemoryTransition()
// 由於內存轉換,代碼和數據可能已被遷移,需要重新啓動循環
// 通常會重新設置 PEI Core 的環境指針
Return // 退出當前循環,進入下一個 PEI 階段
End If
Else
Log(ERROR, "PEIM failed to execute: " + CurrentPeim.Name)
End If
End While
End Function
// -------------------------------------------------------------------------
// 階段 2: 內存轉換 (最關鍵的一步)
// -------------------------------------------------------------------------
Function PeimMemoryTransition()
// 1. 發現新安裝的永久內存信息
ResourceHob = Services.GetHobByType(HOB_TYPE_RESOURCE_DESCRIPTOR)
// 2. 告訴 PEI Services Table 內存已安裝
Services.PeiServicesInstallPeiMemory()
// 3. 將 PEI Core 自身的數據、PEIMs 和 HOB 列表
// 從臨時的 CAR 空間遷移到新的永久內存中
MigratePeiCoreDataToDRAM()
// 4. 更新 Services Table 和所有 PEIMs 的指針
UpdatePointersToNewDRAMLocations()
// 5. 重新進入 PeimExecutionLoop,尋找需要二次執行的 PEIMs (Post-Memory PEIMs)
Call PeimExecutionLoop(...)
End Function
// -------------------------------------------------------------------------
// 階段 3: 退出 PEI 階段
// -------------------------------------------------------------------------
Function PeiCoreExit()
// 1. 確保所有 HOB 都已創建,生成最終的 HOB 列表
FinalizeHobList()
// 2. 查找並加載 DXE IPL PEIM (Initial Program Load)
DxeIplPeim = FindPeim("DxeIpl.inf")
// 3. 執行 DXE IPL PEIM,其工作是加載 DXE Core 映像
// 並最終將控制權交給 DXE Core
DxeIplPeim.EntryPoint(HobListAddress)
// 永遠不會返回 (Never Returns), 因為控制權已轉交給 DXE Core
End Function
-
PEI Core 負責整個流程的框架和調度(
PeiCoreMain)。 -
PEIM 在
PeimExecutionLoop中被加載和執行,完成實際的硬件配置,是真正幹活的。 -
內存轉換是分水嶺。
PeimMemoryTransition標誌着系統從使用 CPU 緩存運行(CAR)過渡到使用真正的 DRAM 內存。這是整個 PEI 階段最關鍵的一步。 -
HOB 是最終階段結果和目的。最終的 HOB 列表是 PEI 階段工作的產物,作為參數傳遞給 DXE 階段。
二、PEIMs 在 EDKII 中的體現
第一節中我們提到,PEI 會搜索固件卷(FV)中待執行的的 PEIM 鏡像(Imaga),並按照順序執行。這裏可以先簡單把 FV 理解為文件夾,文件夾中存放着特定的功能性程序,也就是 PEIM 鏡像。下面舉個例子介紹 PEIMs 在EDKII 工程中的形式。
在 EDKII 中,PEIM 本質上就是一個模塊(Module),其通常被編譯為.efi適用於 PEI 的 PE/COFF 可執行文件,且最終被放入 FV 中,一般是 FV/PEIFV 或 FV/MAINFV。
有關 EDKII 工程結構的介紹異步此博客。一般情況下,PEIM 的源代碼位於某個 Package 的目錄中,典型路徑(以 MdeModulePkg 中的示例為例):
edk2/
└── MdeModulePkg/
└── Universal/
└── PCD/
└── Pei/
├── Pcd.inf ← PEIM 的 INF 文件(最重要)
├── Pcd.c ← 源碼
└── Pcd.h
➡️ 所有 PEIM 模塊的特徵是:INF 文件中有:
[Defines]
MODULE_TYPE = PEIM
有關模塊的概念後續會補充,前面章節中有關於MdeModulePkg的介紹以及單獨編譯某模塊的方法,還有模塊的配置描述文件inf文件的簡單介紹,感興趣可以移步!
PEIM 是如何被編譯的呢?首先在 EDKII 工程中,我們最終的目的是編譯某個平台的固件文件,比如虛擬機使用的 ovmf.fd,其平台描述文件位於OvmfPkg/OvmfPkgX64.dsc。具體內容點擊此處在附錄中有介紹。這個平台描述文件會定義哪些 PEIM 模塊會被編譯。fdf文件OvmfPkg/OvmfPkgX64.fdf描述了最終的固件鏡像是如何佈局的,因此PEIM 是否進入固件由平台 FDF 文件決定。
fdf文件的介紹後續會另外補充!
例如 OvmfPkgX64.dsc 內會有:
[MdeModulePkg/Universal/PCD/Pei/Pcd.inf]
只要在 .dsc 裏被包含,構建系統就會編譯它。
編譯 ovmf 固件鏡像時
build -a X64 -t GCC5 -p OvmfPkg/OvmfPkgX64.dsc
編譯操作自動會:找到所有 INF(包括 PEIM),生成 .obj,鏈接出 PEIM 的 .efi(PEI 可執行)。
EDK II 的編譯輸出統一放在 Build/ 目錄中。比如
Build/OvmfX64/DEBUG_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/Pcd.efi
PEIM 在最終固件鏡像(FV)裏:
Build/OvmfX64/DEBUG_GCC5/FV/
├── OVMF.fd
├── OVMF_CODE.fd
├── OVMF_VARS.fd
└── PEIFV.Fv # efi 文件會被工具打包進 .FV 文件。
可以查看 FDF 文件,例如 OvmfPkgX64.fdf:
FV = PEIFV {
INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf
}
這表示 Pcd.inf (一個 PEIM)會放入該 Firmware Volume。
三、PEI Services Table 介紹
在 PEI Core 以及偽代碼中涉及到 PEI Services Table 這裏簡單介紹一下。
PEI Services Table 本質上是一個指向一系列函數指針的結構體。它允許 PEIMs 訪問底層 PEI Core 實現的服務,而無需知道這些服務的具體位置和內部細節。通俗來説就相當於執行具體任務的 PEIM 能夠通過 PEI Services Table 調用 PEI Core 中的函數。
PEI Services Table 結構通常包含多個部分,其中最重要的就是核心服務和輔助服務的指針:
- 核心服務 (Core Services): 最基礎的服務,在整個 PEI 階段都可用。
AllocatePages/AllocatePool: 內存分配。CreateHob: 創建 Hand-Off Block,將數據傳遞給 DXE。InstallPpi: 安裝 PPI (PEIM-to-PEIM Interface),用於 PEIM 間通信。LocatePpi: 查找其他 PEIM 已經安裝的 PPI。
- 啓動服務 (Boot Services): 與啓動流程管理相關的服務。
FfsFindNextPeim: 用於在 Firmware Volume (FV) 中查找下一個要執行的 PEIM。NotifyPpi: 註冊一個通知函數,當某個 PPI 被安裝時觸發。
如上文中的偽代碼所示,這個服務表在 PEI Core 啓動時(在 SEC 階段交接之後)被初始化,最初的實現可能非常精簡,使用的都是臨時內存(CAR/Cache As RAM)。當 PEI Core 加載並執行一個 PEIM 時,它會將 PEI Services Table 的地址作為參數傳遞給該 PEIM 的 EntryPoint 函數。偽代碼如下,對應上文偽代碼中的 41 行。
// PEIM Entry Point 函數的簽名
Function PeimEntryPoint(
IN PeiFileHandle,
IN **PeiServicesTablePointer // 這裏的雙指針指向服務表
)
// PEIM 現在可以使用這個表來調用服務
Status = PeiServicesTablePointer->CreateHob(...)
Status = PeiServicesTablePointer->InstallPpi(...)
End Function
四、HOB的詳細介紹,PEI 到 DXE階段
HOB 的主要任務是將 PEI 階段發現、初始化和配置的所有系統信息,以一種標準化的格式,安全可靠地傳遞給接下來運行的 DXE 階段。PEI 階段創建的 HOB 並不是一個單一的數據塊,而是一個由多個獨立 HOB 描述符串聯而成的鏈表,稱為 HOB 列表 (HOB List)。
核心作用
- 狀態傳遞: 記錄內存、CPU、芯片組等硬件的狀態和配置。
- 資源映射: 提供系統內存和 I/O 資源的詳細布局,供 DXE 階段的內存管理器和驅動程序使用。
- 服務註冊: 記錄 PEI 階段可能提供的某些特殊服務或數據。
每個 HOB 都是由一個標準化的 HOB 描述符頭部 (EFI_HOB_GENERIC_HEADER) 開始,緊接着是該 HOB 特定類型的數據。
| 字段 | 描述 |
|---|---|
Type |
HOB 的類型(見下文)。 |
Length |
整個 HOB 結構的長度(包括頭部和數據)。 |
Reserved |
保留字段。 |
HOB 列表中的每一個塊都屬於一個特定的類型,用於傳遞特定的信息。以下是幾種最關鍵的 HOB 類型:
| HOB 類型 (Type) | 結構名稱 (例如) | 傳遞的信息 |
|---|---|---|
EFI_HOB_TYPE_RESOURCE_DESCRIPTOR |
EFI_HOB_RESOURCE_DESCRIPTOR |
內存和 I/O 資源的詳細列表。 這是 DXE 內存管理器建立內存映射的基礎。 |
EFI_HOB_TYPE_MEMORY_ALLOCATION |
EFI_HOB_MEMORY_ALLOCATION |
記錄 PEI 階段已分配的內存塊(例如,用於存儲 PEI 堆棧、PEI 核心、或特定的數據結構)。 |
EFI_HOB_TYPE_HANDOFF |
EFI_HOB_HANDOFF_INFO_TABLE |
列表的第一個 HOB。 包含 PEI 和 DXE 之間的手遞手信息,例如:啓動模式、SEC 階段的堆棧基址等。 |
EFI_HOB_TYPE_CPU |
EFI_HOB_CPU |
CPU 特定的信息,例如:CPU 的最大地址空間、處理器數量等。 |
EFI_HOB_TYPE_GUID_EXTENSION |
EFI_HOB_GUID_TYPE |
允許平台或 OEM 定義自定義數據,通過 GUID 識別,用於傳遞非標準化的信息。 |
在 PEI 階段,當一個 PEIM (PEI Module) 完成初始化或發現某個資源時,它會調用 PEI 服務表中的 API 來創建 HOB,將其添加到 HOB 列表的末尾。
主要的 HOB 創建服務是:
-
PeiServicesCreateHob(): 用於創建任何類型的 HOB。 -
PeiServicesInstallPeiMemory(): 專用於創建第一個內存資源 HOB,標誌着永久內存初始化完成。
HOB 如何從 PEI 傳遞到 DXE,這是 HOB 機制的核心價值:
- PEI 結束: PEI 核心在完成所有 PEIM 的執行後,會調用
PeiCoreExitTransition()函數。 - Handoff Block 準備: 在退出前,PEI 核心確保 HOB 列表已經最終化。
- 跳轉到 DXE: PEI 核心最後會執行一個特殊的 PEIM,該 PEIM會加載 DXE 核心的映像,並使用特殊的跳轉指令(通常是
JMP或CALL)進入 DXE 階段的入口點 (DxeCoreEntry)。 - 傳遞參數: 在跳轉時,HOB 列表的起始地址會被作為參數(通常通過 CPU 寄存器)傳遞給 DXE 核心。
DXE 階段如何使用 HOB。DXE 核心是 HOB 列表的主要消費者
- 初始化 DXE 核心: DXE 核心的入口點接收到 HOB 列表的地址。它首先解析這個列表,建立其內存管理器。
- 建立內存映射: 它利用
EFI_HOB_TYPE_RESOURCE_DESCRIPTORHOB 中包含的內存信息,來建立自己的內存映射,區分可用內存和已佔用內存。 - 提取配置信息: DXE 驅動和 DXE 服務可以使用
EfiGetSystemConfigurationTable()或直接遍歷 HOB 列表來查找特定的配置信息或自定義數據。 - 銷燬(邏輯上): 一旦 DXE 核心完成了初始化並建立了自己的資源管理機制,HOB 列表的歷史使命就完成了。它所佔據的內存通常會被重新聲明為 DXE 階段可用的系統內存。
Steady Progress