目錄
- 1. 什麼是固件卷
- 2. 是麼是 FFS 文件
- 3. 什麼是 FDF 文件
- 4. UEFI 中模塊的概念
一、UEFI 固件卷
如果一個磁盤是沒有經過分區的簡單狀態並且沒有文件系統的話是什麼樣的,所有的文件扁平化的分佈在整個磁盤空間,沒有組織邏輯,沒有文件夾等等,這不是一個理想的狀態。固件卷的概念就類似於磁盤分區,它是 UEFI 固件中用於存放各種固件文件,即FFS 文件的容器。裏面按規定組織了各種 UEFI 文件。比如:DXE 驅動(.efi)、PEI 模塊、微碼(microcode)、配置數據、ACPI 表、變量存儲區、Logo 圖片等資源。
在 UEFI 固件鏡像中,所有模塊都必須放進某個 FV 裏才能被固件識別和加載。一個 FV 裏都放什麼內容一般按照功能決定。UEFI 固件鏡像通常由多個 FV 組成,例如典型分佈:
+----------------------------+
| SEC/PEI FV |
+----------------------------+
| DXE FV |
+----------------------------+
| NVRAM FV |
+----------------------------+
| Microcode FV |
+----------------------------+
不同廠商的分佈不一樣,但通常有兩類:
-
Boot FV:存放 SEC/PEI/DXE 核心模塊 → 固件啓動必須依賴。
-
Runtime / Data FV:放 ACPI 表、變量存儲區域、Logo、微碼等資料。
那麼在 EDKII 工程中 FV 文件是怎麼組織和生成的呢?
先説結論,EDKII 用 FDF 文件 描述要生成哪些 FV,典型的 FDF 文件如下:
[FV.FVMAIN]
FvAlignment = 16
BLOCKSIZE = 0x10000
INF MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf
INF MdeModulePkg/Core/Dxe/DxeMain.inf
FILE RAW {
Microcode/Microcode.bin
}
根據這個 FDF 文件,EDKII 在編譯時會把.inf 描述的模塊編譯出的 .efi,還有.bin、.raw以及其他資源文件全部打包進 FV 中。最終生成的 FV 會被合併成 *.fd 或最終固件鏡像(BIOS ROM)。
在第一章開發環境搭建和第二章 EDKII 工程目錄介紹中有涉及到 QEMU 虛擬機所用的 OVMF.fd 固件的編譯。EDKII 工程下的OVMF 目錄如下
OvmfPkg/
├── OvmfPkgX64.dsc # 平台描述文件
├── OvmfPkgX64.fdf # 鏡像佈局文件
├── PlatformDxe/ # 平台初始化驅動
├── Include/
└── Library/
OvmfPkg 目錄就是我們要開發的平台的固件的相關內容,如果我們有自己的板U平台,可自己創建 xxxPkg 目錄,裏面要放平台描述文件和鏡像佈局文件,有關dsc文件在第二章中有講。fdf文件就是上文中提到的定義要生成的 FV 文件。
二、FFS 文件
平時咱們磁盤裏的文件夾中能夠存放各種各樣的文件,txt、exe等等,但是 FV 不行,它裏面只能存放 FFS 文件。FFS 可以理解為專門給固件使用的一種文件格式,它是對模塊本身加上特定信息的封裝,有點類似於網絡中的數據報,裏面放了目標數據,但是因為要遵循特定的協議,需要進行封裝。具體來説,FFS 文件的內容包括
-
文件頭(File Header):標識該模塊是什麼
-
文件體(File Body):模塊本身(通常是 PE32/PE32+ 的 .efi 文件)
-
文件區塊(Section):每個 Section 是一種數據類型
┌─────────────────────────┐
│ FFS File Header │ → 文件類型、GUID、校驗等信息
├─────────────────────────┤
│ Section 1: PE32 │ → 模塊的代碼 (.efi)
├─────────────────────────┤
│ Section 2: UI Name │ → 模塊名字
├─────────────────────────┤
│ Section 3: DEPS │ → 依賴信息
└─────────────────────────┘
常見 FFS 文件類型有
| 類型 | 用途 |
|---|---|
| PEI Module | PEI 階段模塊 |
| DXE Driver | DXE 驅動 |
| RAW | 原始數據,如 microcode 或 logo |
| FREEFORM | 自定義數據塊 |
| PEI/DXE Combo | 既可在 PEI 又可在 DXE 使用 |
三、FDF 文件
FDF 是 EDK2 編譯固件時用的 固件佈局描述文件。比如 OVMF 目錄下
OvmfPkg/
├── OvmfPkgX64.dsc # 平台描述文件
├── OvmfPkgX64.fdf # 鏡像佈局文件
├── PlatformDxe/ # 平台初始化驅動
├── Include/
└── Library/
OvmfPkgX64.fdf 就是 OVMF 固件對應的佈局描述文件。它告訴編譯器要生成哪些 FV,每個 FV 裏放哪些 FFS 文件,最終 Firmware 鏡像如何組合。如果沒有 FDF,EDK2 就不知道怎麼把模塊打包成一個固件 ROM。它描述了ROM 大小、地址、有哪些 Firmware Volume(FV)、 各 FV 裝哪些模塊(INF、BIN、Raw)、 微碼、ACPI、NVRAM 等放在哪、 最終如何拼成固件鏡像(FD)。前面我們已經給出了一個 FDF 文件的例子,這裏再舉一個簡單的例子來説明
[FV.FVMAIN]
INF MdeModulePkg/Core/Dxe/DxeMain.inf
INF MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf
FILE RAW {
Microcode/Microcode.bin
}
[FV.FVMAIN]:定義一個名字叫 FVMAIN 的 Volume
INF ...:把某個 UEFI 模塊(通過 INF 編譯)打包進來
FILE RAW:加入一個原始文件,比如 microcode
最終這些內容會被打包成:FVMAIN.FV → 固件卷,並且會放入最終的固件鏡像中。比如
~/edk2/Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd
FV 文件也會單獨存在,比如
~/edk2/Build/OvmfX64/DEBUG_GCC5/FV/xxx.FV
簡單描述 FV,FDS,FFS三者之間的關係:
INF 文件 → 編譯 → 生成 EFI 模塊 → 封裝成 FFS 文件 → 打包進 FV → 合併成最終固件鏡像(FD)
↑ ↑
FDF 決定怎麼打包 ←--------------------+
+-------------+ +-----------+ +-----------+ +--------+
| .inf 文件 | ---> | .efi 文件 | ---> | .ffs 文件 | ---> | FV |
+-------------+ +-----------+ +-----------+ +--------+
↑
|
FDF 決定佈局
上一章對 PEIM 進行了介紹,PEI 核心會掃描固件卷中的 PEIM 並逐個執行,因此再舉個 PEIM 的例子:
EDK2 源碼
└── DxeIplPeim.inf
└── DxeIplPeim.c
↓ 編譯
DxeIplPeim.efi
↓ 封裝
DxeIplPeim.ffs
↓ 打包進 FV(由 FDF 決定)
PEIFV.FV
↓ 合併多個 FV → 最終 ROM
OVMF.fd(就是固件 BIOS)
FV 文件內部包含多個 FFS 文件,如
PEIFV.FV
├── PeiMain.ffs
├── DxeIplPeim.ffs
├── LoadFilePei.ffs
├── PeiCore.ffs
└── ...
附錄:模塊介紹
在 EDKII 中,一個模塊通常是指一個獨立的、功能相對完整的軟件組件,被涉及用來實現特定的 UEFI 功能,比如
- 一個驅動程序(如CPU初始化驅動、PCIe總線驅動)
- 一個UEFI應用程序(如Shell應用、引導管理器)
- 一個庫的實現
- 一個協議(Protocol)的實現
每個模塊都擁有自己獨立的源代碼目錄,並且通過一個名為[name].inf的模塊聲明文件來描述自己。inf文件類似於這個模塊的説明書,包括模塊名字,源代碼,依賴庫,編譯鏈接選項等。
平台和模塊是上下層的關係,一個固件平台可以包括很多個功能模塊,前面説了inf文件是模塊描述文件,DSC/FDF就是平台描述文件。最終構建工具根據 説明書(.inf)和 總設計圖(DSC/FDF)來加工構件並最終組裝成房子。
一個標準的 EDKII 模塊通常包含以下三個關鍵文件:
[name].c/.asm。模塊的主要源代碼文件,實現了模塊的功能邏輯,包含了C語言或彙編語言的實現代碼。[name].inf。這是模塊的説明書。[Defines]:定義模塊的基本屬性,如模塊類型、組件 GUID、版本等。[Sources]:列出模塊所有的源代碼文件(.c, .h, .asm等)。[Packages]:聲明本模塊所依賴的包聲明文件(.dec)。這相當於C語言中的#include <...>,指明需要哪些外部接口/定義。[LibraryClasses]:聲明本模塊需要鏈接哪些庫。例如UefiDriverEntryPoint,UefiLib,BaseLib等。[Protocols],[Guids],[Ppis]:聲明本模塊使用了哪些 EDKII 內置的或外部定義的協議、GUID 和 PPI。構建工具會據此檢查依賴關係。
[name].uni。可選項。Unicode 字符串文件,用於本地化(如多語言支持)。它定義了模塊中可顯示的用户字符串,方便實現不同語言的用户界面。
UEFI 中模塊的常見類型
| 模塊類型 | 説明 | 輸出結果 | 運行環境 |
|---|---|---|---|
UEFI_DRIVER |
UEFI 驅動程序 | 一個 .efi可執行文件 |
在UEFI引導服務環境(BS)下運行,通常通過 LoadImage/StartImage加載。 |
UEFI_APPLICATION |
UEFI 應用程序 | 一個 .efi可執行文件 |
與 UEFI_DRIVER類似,但通常設計為一次性執行並退出,例如在 UEFI Shell 下運行的命令。 |
DXE_DRIVER |
DXE 階段驅動程序 | 一個 .efi可執行文件 |
在DXE(Driver Execution Environment)階段被調度執行,是固件初始化的核心。 |
PEIM |
PEI 階段模塊 | 一個 .efi可執行文件 |
在 PEI(Pre-EFI Initialization)階段運行,負責非常早期的硬件初始化。 |
BASE |
基礎模塊 | 一個庫文件(如 .lib) |
這是一個特殊的類型,用於構建不依賴特定UEFI環境的庫(Library)。這些庫可以被其他類型的模塊使用。 |
LIBRARY_CLASS |
庫類實現 | 通常是一個 .lib庫文件 |
這並不是一個獨立的可執行模塊,而是某個庫類(Library Class) 的具體實現。例如,你提供一個實現了 BaseLib庫類接口的實例。 |
舉個簡單例子
假設我們有一個非常簡單的應用 HelloWorld。
MyPkg/
├── Application/
│ └── HelloWorld/
│ ├── HelloWorld.c // 源代碼,打印 "Hello World"
│ ├── HelloWorld.inf // 模塊聲明文件
│ └── HelloWorld.uni // 可選的字符串文件
├── MyPkg.dsc // 包描述文件,需要在此聲明包含HelloWorld模塊
└── MyPkg.dec // 包聲明文件
HelloWorld.inf文件內容可能如下:
[Defines]
INF_VERSION=0x00010005
BASE_NAME=HelloWorld
FILE_GUID=12345678-1234-1234-1234-123456789ABC
MODULE_TYPE=UEFI_APPLICATION # 聲明這是一個UEFI應用
VERSION_STRING=1.0
ENTRY_POINT=UefiMain # 入口函數名
[Sources]
HelloWorld.c
[Packages]
MdePkg/MdePkg.dec # 依賴MdePkg包,因為它包含了UEFI_BASIC_SERVICES的定義
[LibraryClasses]
UefiApplicationEntryPoint # 應用需要這個庫
UefiLib # 提供了Print函數等
# 如果代碼中使用了gEfiSimpleTextOutProtocolGuid,則需要聲明
[Protocols]
gEfiSimpleTextOutProtocolGuid
Steady Progress!