4. EDK II工程结构
EDK II 的工程结构不是简单的“源码目录 + Makefile”,而是一套以包(Package)为复用边界、以模块(Module)为编译单元、以库(Library)为功能共享层、以PCD为配置参数通道、以DEC/DSC/INF/FDF四类元数据文件为组织骨架的构建体系。理解这套结构,是读懂 UEFI 固件工程、进行驱动开发和平台移植的前提。
一、核心对象与职责划分
EDK II 把“代码”和“描述代码如何被构建、如何被复用”的信息分离。代码放在模块和库中,而构建信息放在元数据文件里。四类文件各司其职:
- DEC(Package Declaration):声明一个包的对外接口,包括包名、GUID、版本、公开头文件目录、库类(Library Class)、PCD 声明、协议/PPI/GUID 声明。它是包的“名片”,其他包通过它来引用本包资源。
- INF(Information):描述一个模块或库实例的构建信息,包括源文件、依赖的库类、使用的 PCD、协议、PPI、GUID、编译选项等。每个可编译单元对应一个 INF。
- DSC(Description):平台描述文件,决定“这个平台要构建哪些模块、用哪些库实例、PCD 取什么值、编译器和架构是什么”。它是构建的顶层入口。
- FDF(Flash Description):固件镜像布局描述文件,定义 FD、FV、FFS 文件、Section 的层次结构,决定最终烧录到 Flash 中的二进制如何排布。
一句话记忆:DEC 管“有什么”,INF 管“怎么编”,DSC 管“编哪些、怎么配”,FDF 管“怎么装进 Flash”。
二、包、模块、库、PCD 的组织关系
包是最大的复用单位,一个包可以包含多个模块和多个库实例。模块通过 INF 声明自己依赖哪些库类,DSC 在平台层面把库类“绑定”到具体的库实例。PCD 则贯穿 DEC、INF、DSC 三层:DEC 声明 PCD 的存在和类型,INF 声明模块使用哪些 PCD,DSC 为平台设定 PCD 的实际值。
三、构建流程与文件协作
一次典型的 EDK II 构建,文件之间的协作顺序如下:
- 构建工具读取 DSC,确定目标平台、架构、工具链和模块列表。
- 对每个模块,读取其 INF,获取源文件、库类依赖和 PCD 使用声明。
- 根据 DSC 中的
[LibraryClasses]段,把 INF 中声明的库类解析为具体的库实例 INF。 - 读取各包的 DEC,解析头文件路径、PCD 声明和协议/GUID 定义。
- 编译所有模块和库,链接生成 EFI 可执行文件。
- 读取 FDF,把模块按规则打包成 FV、FFS,最终生成 FD 镜像。
# DSC 中库类绑定示例
[LibraryClasses]
DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
# INF 中声明依赖
[LibraryClasses]
DebugLib
MemoryAllocationLib
# DEC 中声明 PCD
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000000|UINT32|0x00000006
四、PCD 的四种类型与选择
PCD 是 EDK II 中传递配置参数的核心机制,按生效时机分为四类:
| 类型 | 生效时机 | 是否可运行时修改 | 典型用途 |
|---|---|---|---|
| FixedAtBuild | 编译时 | 否 | 调试开关、功能裁剪、固定常量 |
| PatchableInModule | 编译时,但二进制中保留可修补位置 | 否(可二进制修补) | 需要后期用工具修改的配置 |
| Dynamic | 运行时,通过 PCD 协议读写 | 是 | 驱动运行时需要读取的平台参数 |
| DynamicEx | 运行时,通过 PCD 协议读写,支持跨模块 | 是 | 跨模块共享的动态配置 |
选择原则:能用 FixedAtBuild 就不用 Dynamic,因为编译期常量可以被编译器优化,代码更小、启动更快。只有确实需要在运行时改变的参数,才使用 Dynamic 或 DynamicEx。
五、常见误区与复盘
- 误区一:把 DSC 当成 Makefile。DSC 只描述“构建什么、怎么配”,不描述“怎么编译”。编译规则由构建工具和工具链文件决定。
- 误区二:在 INF 中写死库实例路径。INF 只应声明库类,具体用哪个库实例由 DSC 决定。这样才能实现同一模块在不同平台上复用不同库实现。
- 误区三:PCD 在 DEC 中声明后忘记在 DSC 中赋值。如果 PCD 没有默认值且 DSC 未赋值,构建会报错。建议在 DEC 中给合理默认值。
- 误区四:FDF 中模块顺序随意。FV 中模块的排列顺序会影响依赖解析和启动顺序,尤其是 PEI 阶段和 DXE 阶段的调度依赖。
- 误区五:包之间循环依赖。DEC 中通过
[Packages]引用其他包,循环依赖会导致构建失败。应通过提取公共包或使用库类解耦。
调试构建失败时,优先检查三处:DSC 中库类是否绑定完整、INF 中声明的库类是否都在 DSC 中有对应实例、PCD 是否在 DEC 中声明且在 DSC 中赋值。这三处覆盖了大多数 EDK II 构建错误。
掌握 EDK II 工程结构的关键,是建立“包—模块—库—PCD—四类文件”之间的映射关系。DEC 定义接口,INF 定义单元,DSC 定义平台,FDF 定义镜像,PCD 贯穿配置。理解这套分层协作机制,才能在驱动开发和平台移植中快速定位问题、合理组织代码。