4. UEFI 工程结构解析
UEFI 固件开发的核心是 EDK2(EFI Development Kit II)框架。理解其工程结构是进行模块开发与平台移植的基础。本章将深入剖析 EDK2 的目录布局、三大核心描述文件(.dsc、.dec、.fdf)以及 Package 与 Module 的层次关系。
4.1 EDK2 顶层目录结构
一个标准的 EDK2 工作区(Workspace)通常包含以下顶层目录。开发者需熟悉每个目录的职责,以便快速定位代码与配置文件。
| 目录/文件 | 说明 | 典型内容 |
|---|---|---|
| BaseTools/ | 构建工具集,包含编译、链接、生成固件镜像的工具链 | GNUmakefile, Python 脚本, C 源码 |
| Conf/ | 全局构建配置文件,如 target.txt、tools_def.txt | 工具链路径、目标架构、编译选项 |
| MdePkg/ | 核心基础包,定义 UEFI 规范中的数据类型、协议、PPI 等 | UEFI 规范头文件、库函数 |
| MdeModulePkg/ | 通用模块包,提供大量可复用的驱动与库 | 磁盘驱动、控制台驱动、BDS 阶段代码 |
| IntelFrameworkPkg/ | Intel 框架包,包含传统 BIOS 遗留接口 | Framework 协议、SMM 相关代码 |
| NetworkPkg/ | 网络协议栈实现 | TCP/IP、HTTP Boot、iSCSI 驱动 |
| ShellPkg/ | UEFI Shell 实现 | Shell 命令、Shell 应用程序 |
| OvmfPkg/ | QEMU 虚拟平台包,用于模拟器调试 | OVMF 固件、虚拟平台描述文件 |
| Platform/ | 厂商或社区提供的平台定制包 | 板级初始化代码、平台描述文件 |
| Build/ | 编译输出目录(自动生成) | 中间文件、最终固件镜像 |
提示: 初学者应重点关注
MdePkg 和 MdeModulePkg,它们构成了 EDK2 的“标准库”与“通用驱动库”,是学习 UEFI 编程的起点。
4.2 Package 与 Module 的概念
EDK2 采用 Package(包) 和 Module(模块) 两层组织架构。Package 是逻辑上的功能集合,Module 是具体的可编译单元。
- Package(.dec 文件定义):包含一组相关的 Module、Library、Protocol、PPI 和 GUID 声明。例如
MdePkg是一个 Package,它定义了所有 UEFI 规范中的基础类型。 - Module(.inf 文件定义):一个独立的可编译单元,可以是驱动(DXE/SMM/UEFI Driver)、应用程序(Shell App)或库(Library Instance)。每个 Module 必须有一个
.inf文件描述其源文件、依赖和编译选项。
关键关系: Package 提供接口声明(.dec),Module 实现具体功能(.inf + .c/.h)。一个 Package 可以包含多个 Module,而一个 Module 可以依赖多个 Package 提供的接口。
4.3 三大核心描述文件详解
EDK2 的构建系统依赖三个关键描述文件:.dsc(平台描述)、.dec(包声明)、.fdf(固件描述)。它们共同决定了固件的组成、布局和编译方式。
4.3.1 .dec 文件(Package Declaration File)
.dec 文件定义了一个 Package 对外提供的接口,包括 GUID、Protocol、PPI、PCD 和 Library Class。它是 Package 的“头文件索引”。
# MdePkg/MdePkg.dec 示例片段
[Defines]
DEC_SPECIFICATION = 0x00010005
PACKAGE_NAME = MdePkg
PACKAGE_GUID = 1E73767F-8A52-4b2c-BF0C-DB83E8E2B7B1
PACKAGE_VERSION = 1.08
[Includes]
Include # 公共头文件路径
[LibraryClasses]
BaseLib|Include/Library/BaseLib.h
UefiLib|Include/Library/UefiLib.h
[Guids]
gEfiGlobalVariableGuid = {0x8BE4DF61, 0x93CA, 0x11d2, {0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C}}
[Protocols]
gEfiSimpleTextInProtocolGuid = {0x387477C1, 0x69C7, 0x11d2, {0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B}}
4.3.2 .inf 文件(Module Information File)
每个 Module 必须有一个 .inf 文件,描述其源文件、依赖的 Library、定义的 GUID 以及编译选项。
# MdeModulePkg/Universal/DiskIoDxe/DiskIoDxe.inf 示例
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = DiskIoDxe
FILE_GUID = 6B38F7B4-AD98-4e3f-BD30-5DEEC0B2E5C0
MODULE_TYPE = UEFI_DRIVER
ENTRY_POINT = DiskIoDriverEntryPoint
[Sources]
DiskIo.c
DiskIo.h
[Packages]
MdePkg/MdePkg.dec
MdeModulePkg/MdeModulePkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiLib
MemoryAllocationLib
BaseLib
[Protocols]
gEfiDiskIoProtocolGuid ## PRODUCES
gEfiBlockIoProtocolGuid ## CONSUMES
4.3.3 .dsc 文件(Platform Description File)
.dsc 文件是整个平台固件的“构建清单”。它指定了要包含哪些模块、使用哪些库实例、PCD 默认值以及全局编译选项。
# OvmfPkg/OvmfPkgX64.dsc 示例片段
[Defines]
PLATFORM_NAME = Ovmf
PLATFORM_GUID = 5a9e7754-8270-4b8e-9b0f-9f3d5f7b9c1a
PLATFORM_VERSION = 0.1
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/OvmfX64
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS = DEBUG|RELEASE
SKUID_IDENTIFIER = DEFAULT
[LibraryClasses]
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
# 平台特定库覆盖
SerialPortLib|OvmfPkg/Library/SerialPortLib/SerialPortLib.inf
[Components]
# 包含的模块列表
MdeModulePkg/Universal/DiskIoDxe/DiskIoDxe.inf
MdeModulePkg/Universal/ConSplitterDxe/ConSplitterDxe.inf
NetworkPkg/TcpDxe/TcpDxe.inf
OvmfPkg/PlatformDxe/PlatformDxe.inf
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000
gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageVariableBase|0xFFE00000
4.3.4 .fdf 文件(Flash Description File)
.fdf 文件定义了固件镜像在 Flash 中的物理布局,包括 FV(Firmware Volume)、FFS(Firmware File System)以及各个模块在镜像中的偏移地址。
# OvmfPkg/OvmfPkgX64.fdf 示例片段
[FD.BFV]
BaseAddress = 0xFFE00000
Size = 0x00200000
ErasePolarity = 1
[FV.SECFV]
FvAlignment = 16
ERASE_POLARITY = 1
MEMORY_MAPPED = TRUE
STICKY_WRITE = TRUE
LOCK_CAP = TRUE
LOCK_STATUS = TRUE
READ_DISABLED_CAP = FALSE
READ_ENABLED = TRUE
READ_STATUS = TRUE
READ_LOCK_CAP = TRUE
READ_LOCK_STATUS = TRUE
WRITE_DISABLED_CAP = FALSE
WRITE_ENABLED = TRUE
WRITE_STATUS = TRUE
WRITE_LOCK_CAP = TRUE
WRITE_LOCK_STATUS = TRUE
INF OvmfPkg/Sec/SecMain.inf
[FV.DXEFV]
FvAlignment = 16
ERASE_POLARITY = 1
INF MdeModulePkg/Universal/DiskIoDxe/DiskIoDxe.inf
INF MdeModulePkg/Universal/ConSplitterDxe/ConSplitterDxe.inf
INF NetworkPkg/TcpDxe/TcpDxe.inf
注意:
.dsc 决定“编译什么”,.fdf 决定“如何摆放”。两者必须保持一致:.dsc 中列出的模块,必须在 .fdf 的某个 FV 中有对应的 INF 条目,否则模块不会被包含进最终固件镜像。
4.4 构建系统工作流程
EDK2 的构建过程可以概括为以下步骤:
- 解析 .dsc:读取平台描述文件,确定要编译的模块列表、库实例和 PCD 配置。
- 解析 .dec:加载所有依赖 Package 的接口声明,建立 GUID 和 Protocol 的映射表。
- 解析 .inf:对每个模块,读取其源文件列表、依赖库和编译选项。
- 编译:调用编译器(如 GCC、MSVC、ICC)编译每个模块的源文件,生成
.efi文件。 - 链接与打包:根据
.fdf定义的布局,将.efi文件打包成 FFS,再组合成 FV,最终生成完整的固件镜像.fd文件。
实践要点: 修改固件时,通常只需编辑
.dsc 添加新模块,并在 .fdf 的对应 FV 中添加 INF 引用。如果模块使用了新的 Protocol 或 PCD,还需确保对应的 .dec 文件已被包含。
4.5 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
编译报错:Module not found | .dsc 中引用了不存在的模块路径 | 检查模块路径是否正确,确认 .inf 文件存在 |
链接错误:Unresolved external symbol | 模块依赖的 Library 未在 .dsc 的 [LibraryClasses] 中声明 | 在 .dsc 中添加对应的 Library 实例映射 |
固件无法启动:No FV found | .fdf 中 FV 的 BaseAddress 或 Size 与硬件不匹配 | 核对 Flash 数据手册,修正 .fdf 中的地址参数 |
| Protocol 未安装 | 模块的 .inf 中未正确声明 [Protocols] 依赖 | 检查 .inf 的 [Protocols] 段,确保 ## PRODUCES / ## CONSUMES 标注正确 |