4. 模块类型与生命周期
在 EDK II 中,模块(Module)是构建与集成的基本单位,但“模块”并不是同质概念。根据模块被链接进哪个固件阶段、以何种方式被调度、是否直接产生协议或服务,EDK II 将模块划分为 PEIM、DXE 驱动、UEFI 驱动、UEFI 应用和库模块等类型。理解这些类型的差异与执行时机,是正确划分功能边界、选择模块入口点、配置 INF 与 DSC 的前提。
一、五类模块的定位与执行时机
PEIM(Pre-EFI Initialization Module)运行于 PEI 阶段,此时内存尚未完全初始化,可用资源极为有限。PEIM 通过 PEI 核心分派的 EFI_PEI_PPI_DESCRIPTOR 发布 PPI(PEIM-to-PEIM Interface),供后续 PEIM 消费。其典型任务是内存初始化、芯片组早期配置、恢复路径准备等。PEIM 的执行时机由 PEI 调度器根据依赖关系决定,通常以 PEI_CORE_ENTRY_POINT 或 EFI_PEI_PEIM_ENTRY_POINT 为入口。
DXE 驱动运行于 DXE 阶段,此时系统内存已可用,DXE 核心提供启动服务、运行时服务和 DXE 服务表。DXE 驱动通过 EFI_DXE_ENTRY_POINT 入口,使用 gBS->InstallProtocolInterface() 等安装协议,或通过 gDS-> 访问 DXE 服务。DXE 驱动的调度依赖 DEPEX(依赖表达式),只有其依赖的协议或 PPI 就绪后才会被派发。
UEFI 驱动与 DXE 驱动在代码结构上非常接近,但它的设计目标是遵循 UEFI 规范,通常用于提供 UEFI 运行时服务、启动项管理、控制台设备等。UEFI 驱动同样在 DXE 阶段执行,但其入口点常声明为 EFI_STATUS EFIAPI UefiDriverEntryPoint(...),并通过 gBS 安装 EFI_DRIVER_BINDING_PROTOCOL 来管理设备绑定与启动。
UEFI 应用运行于 BDS 阶段之后,由启动管理器或 Shell 加载执行。它不安装系统级协议,而是以 EFI_APPLICATION_ENTRY_POINT 为入口,使用 gBS、gRT 和 gST 完成用户交互、诊断、刷写等任务。应用执行完毕后通常返回或调用 gBS->Exit() 退出。
库模块(Library)不独立执行,也不直接参与调度。它是一组可被其他模块链接的函数集合,通过 LIBRARY_CLASS 声明其所属库类,由 DSC 中的 [LibraryClasses] 段解析到具体实例。库模块的生命周期依附于调用它的宿主模块,宿主在哪个阶段执行,库代码就在哪个阶段被调用。
二、模块类型对比清单
| 模块类型 | 执行阶段 | 入口点 | 核心交互对象 | 是否独立调度 |
|---|---|---|---|---|
| PEIM | PEI | PEI_CORE_ENTRY_POINT | PPI、PEI 服务表 | 是,由 PEI 调度器派发 |
| DXE 驱动 | DXE | EFI_DXE_ENTRY_POINT | Protocol、gBS、gDS | 是,由 DXE 调度器按 DEPEX 派发 |
| UEFI 驱动 | DXE | UefiDriverEntryPoint | Driver Binding、Protocol | 是,由 DXE 调度器派发 |
| UEFI 应用 | BDS 之后 | EFI_APPLICATION_ENTRY_POINT | gBS、gRT、gST | 是,由启动管理器或 Shell 加载 |
| 库模块 | 依附宿主 | 无独立入口 | 宿主模块传入的参数 | 否,随宿主链接 |
三、典型场景:从需求到模块类型选择
假设需要实现一个“在启动早期读取 GPIO 状态并决定是否进入恢复模式”的功能。该功能必须在内存初始化之前完成,因此应选择 PEIM。PEIM 通过 EFI_PEI_PPI_DESCRIPTOR 发布一个自定义 PPI,供后续 PEIM 或 DXE 驱动消费。若该功能改为“在 DXE 阶段根据 GPIO 状态安装一个厂商协议”,则应选择 DXE 驱动,入口点改为 EFI_DXE_ENTRY_POINT,并通过 gBS->InstallProtocolInterface() 安装协议。
若需要实现一个“在 Shell 中手动触发的诊断工具”,则应选择 UEFI 应用。它不安装系统协议,而是通过 gBS->LocateProtocol() 查找已有协议,执行诊断逻辑后返回。若多个模块都需要访问同一套 GPIO 操作函数,则应将这些函数封装为库模块,通过 LIBRARY_CLASS 声明,并在 DSC 中为不同阶段选择不同实例。
# 库模块 INF 片段示例
[Defines]
LIBRARY_CLASS = GpioLib|PEIM DXE_DRIVER UEFI_APPLICATION
[Sources]
GpioLib.c
# DSC 中解析库实例
[LibraryClasses]
GpioLib|Silicon/GpioLib/GpioLib.inf
四、常见误区与复盘
复盘时建议检查三点:第一,模块入口点是否与目标阶段匹配;第二,模块使用的服务是否在该阶段可用;第三,库模块的 LIBRARY_CLASS 是否覆盖了所有需要它的模块类型。只有这三者一致,模块才能在正确的时机以正确的方式执行。
[Components] 的不同段或通过 MODULE_TYPE 区分;库模块则通过 [LibraryClasses] 解析。模块类型不是标签,而是决定编译选项、链接方式和调度行为的核心属性。