3、EDK2工程结构:目录、.dsc、.fdf、.dec 文件

好,咱们正式开始动手之前,得先把 EDK2 的「家底」摸清楚。

很多新手拿到 EDK2 源码,第一反应就是懵——这目录怎么这么多?这些 .dsc、.fdf、.dec 文件到底是干嘛的?

别急,我当年第一次看 EDK2 的时候,也是对着屏幕发呆了半小时。后来踩了不少坑,才慢慢理清楚。今天我就把这些经验掰开揉碎了讲给你听。

3.1 EDK2 的目录结构

EDK2 的源码目录,说白了就是一个大仓库。里面装着各种模块、库、平台配置、工具链脚本等等。

我个人习惯把 EDK2 的顶层目录分成三大块:

  • 核心库和驱动——MdePkg、MdeModulePkg 这些,是 UEFI 的「骨架」。
  • 平台相关代码——比如 OvmfPkg(虚拟机)、EmulatorPkg(模拟器),还有各种芯片厂商的包。
  • 工具和脚本——BaseTools、edksetup.sh 这些,用来编译和配置。

咱们挑几个最常用的目录说说:

目录名 作用
MdePkg UEFI 规范定义的基础头文件、数据类型、协议接口。说白了就是「字典」。
MdeModulePkg 通用的 UEFI 驱动和模块,比如文件系统、网络协议栈。
OvmfPkg 专门给 QEMU/KVM 虚拟机用的 UEFI 固件。我经常用它做实验。
BaseTools 编译工具链,把 .c 文件变成 .efi 文件的关键。
Conf 全局配置文件,比如 tools_def.txt 定义了编译器路径。
小提示:刚开始不用把所有目录都看一遍。先盯住 MdePkg 和你要用的平台包(比如 OvmfPkg)就够了。其他的,用到再查。

3.2 .dsc 文件——平台描述文件

.dsc 文件,全称是 Platform Description File。

你可以把它理解成「菜单」。这个平台要编译哪些模块?用哪个编译器?Debug 还是 Release?都在这个文件里定。

我举个例子,打开 OvmfPkg/OvmfPkgX64.dsc,你会看到类似这样的结构:

[Defines]
  PLATFORM_NAME                  = Ovmf
  PLATFORM_GUID                  = ...
  PLATFORM_VERSION               = 0.1
  DSC_SPECIFICATION              = 0x00010005
  OUTPUT_DIRECTORY               = Build/OvmfX64
  SUPPORTED_ARCHITECTURES        = X64
  BUILD_TARGETS                  = DEBUG|RELEASE|NOOPT
  SKUID_IDENTIFIER               = DEFAULT

[LibraryClasses]
  # 这里定义库的映射关系
  UefiLib|MdePkg/Library/UefiLib/UefiLib.inf
  BaseLib|MdePkg/Library/BaseLib/BaseLib.inf

[Components]
  # 这里列出要编译的模块
  OvmfPkg/PlatformPei/PlatformPei.inf
  OvmfPkg/BdsDxe/BdsDxe.inf

嗯,这里要注意:[Defines] 是全局配置,[LibraryClasses] 是库的「接线图」,[Components] 是最终要生成的模块列表。

核心要点:.dsc 文件决定了「这个固件里包含什么」。你想加一个自己的驱动?那就得在 [Components] 里加上它的 .inf 路径。

3.3 .fdf 文件——固件描述文件

.fdf 文件,全称是 Flash Description File。

它和 .dsc 是「搭档」。.dsc 管「装什么菜」,.fdf 管「菜怎么摆盘」。

说白了,.fdf 定义了固件在 Flash 里的布局——哪个模块放在哪个地址?FV(Firmware Volume)怎么划分?

我曾经在项目里遇到过一个问题:固件编译成功,但刷进去就启动不了。查了半天,发现是 .fdf 里一个模块的地址写错了,导致覆盖了关键数据。从那以后,我每次改 .fdf 都会反复核对。

一个典型的 .fdf 文件长这样:

[FD.BFV]
  BaseAddress   = 0x00000000
  Size          = 0x00200000
  ErasePolarity = 1
  BlockSize     = 0x00001000
  NumBlocks     = 0x200

[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_LOCK_CAP      = TRUE
  READ_LOCK_STATUS   = TRUE
  WRITE_DISABLED_CAP = FALSE
  WRITE_ENABLED      = TRUE
  WRITE_LOCK_CAP     = TRUE
  WRITE_LOCK_STATUS  = TRUE

  INF OvmfPkg/PlatformPei/PlatformPei.inf
  INF OvmfPkg/BdsDxe/BdsDxe.inf
警告:.fdf 里的地址和大小一定要和硬件 Flash 匹配。写错了,轻则固件不启动,重则把 Flash 刷成砖。我建议你每次改完 .fdf,先用模拟器跑一遍。

3.4 .dec 文件——包声明文件

.dec 文件,全称是 Package Declaration File。

它的作用很简单:声明这个包里有哪些公开的接口、PCD(平台配置数据)、GUID、协议等。

你可以把它理解成「目录索引」。其他模块想用这个包里的东西,就得先看 .dec 文件。

举个例子,MdePkg/MdePkg.dec 里会定义:

[Defines]
  DEC_SPECIFICATION              = 0x00010005
  PACKAGE_NAME                   = MdePkg
  PACKAGE_GUID                   = ...
  PACKAGE_VERSION                = 1.0

[Includes]
  Include

[LibraryClasses]
  UefiLib|Include/Library/UefiLib.h
  BaseLib|Include/Library/BaseLib.h

[PcdsFixedAtBuild]
  gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF|UINT8|0x00000001

你看,[Includes] 告诉编译器头文件在哪,[LibraryClasses] 公开了库的接口,[PcdsFixedAtBuild] 定义了编译时可配置的参数。

小技巧:如果你自己写了一个包,记得在 .dec 里把接口都声明清楚。不然别人引用你的包时,编译会报「找不到头文件」之类的错误。我刚开始写包时就吃过这个亏。

3.5 三者的关系——一张图看懂

说了这么多,你可能有点晕。这三兄弟到底怎么配合的?

我画了一张图,你看完就明白了:

.dsc 文件 平台描述 定义模块和库 .fdf 文件 固件布局 定义 Flash 分布 .dec 文件 包声明 公开接口和PCD 引用模块 引用库和PCD 引用GUID 编译流程 .dsc 决定编译哪些模块 → .fdf 决定模块在 Flash 中的位置 .dec 提供模块之间的接口和配置参数

你看,这三者缺一不可。.dsc 是「菜单」,.fdf 是「摆盘方案」,.dec 是「食材清单」。编译工具把它们组合起来,最终生成一个完整的 UEFI 固件镜像。

总结一下:
  • .dsc:定义平台要编译哪些模块和库。
  • .fdf:定义固件在 Flash 中的布局。
  • .dec:声明包对外提供的接口和配置。

搞懂这三个文件,EDK2 的工程结构你就掌握了七成。

嗯,今天就先聊到这。记住,别急着背目录,先动手打开一个 .dsc 文件看看,对照着我上面讲的,一行一行读下去。读懂了,你就算入门了。

可可精品资料库