4. UEFI 应用与驱动模型
在 UEFI 固件体系中,模块并不是“可执行文件”这一种单一形态。按照运行阶段、服务依赖和是否参与设备管理,UEFI 模块主要分为 UEFI Application、DXE Driver 以及遵循 UEFI Driver Model 的驱动。理解它们的入口点、模块类型与加载流程,是进行协议实现和平台服务开发的基础。
一、核心原理:三类模块的定位差异
UEFI Application 是运行在 UEFI 环境下的应用程序,通常由 Shell 或 Boot Manager 加载,运行在 Boot Services 仍然可用的阶段。它可以直接调用 Boot Services、Runtime Services 和已安装的协议,适合做诊断工具、固件更新程序、配置工具或操作系统加载器。
DXE Driver 是 DXE 阶段被 DXE Dispatcher 调度的驱动模块。它的核心任务是初始化硬件、安装协议、产生 Handle,并在需要时消费其他驱动提供的协议。DXE Driver 不一定要绑定具体设备,它可以是一个“服务提供者”,例如安装一个自定义协议供后续模块使用。
UEFI Driver Model 则是在 DXE Driver 基础上进一步规范化的驱动模型。它要求驱动实现 DriverBinding 协议,通过 Supported()、Start()、Stop() 三个入口管理设备。驱动不再直接“写死”某个设备,而是由总线驱动枚举设备后,通过 Driver Binding 动态匹配并启动。这种模型让驱动具备可插拔、可组合、可热插拔管理的能力。
关键区别:UEFI Application 面向“执行任务”;DXE Driver 面向“安装协议、初始化服务”;UEFI Driver Model 面向“管理设备并绑定驱动”。三者入口点、生命周期和依赖服务均不同。
二、入口点与模块类型
UEFI 模块的入口点由模块类型决定。Application 和 DXE Driver 通常使用 UefiMain 或 ProcessModuleEntryPointList 作为入口;而 UEFI Driver Model 驱动通常使用 InitializeDriver 作为入口,并在其中安装 EFI_DRIVER_BINDING_PROTOCOL。
在 EDK II 中,模块类型通过 INF 文件的 MODULE_TYPE 声明。常见类型包括 UEFI_APPLICATION、DXE_DRIVER、UEFI_DRIVER、DXE_RUNTIME_DRIVER、DXE_SMM_DRIVER 等。模块类型决定了构建系统如何链接、如何生成入口点、以及模块被哪个调度器加载。
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = MyUefiApp
FILE_GUID = 12345678-1234-1234-1234-1234567890AB
MODULE_TYPE = UEFI_APPLICATION
ENTRY_POINT = UefiMain
[Sources]
MyUefiApp.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
对于 UEFI Driver Model 驱动,INF 中通常声明 MODULE_TYPE = UEFI_DRIVER,并在源文件中定义 EFI_DRIVER_BINDING_PROTOCOL 实例。入口函数 InitializeDriver 负责安装该协议,之后由 DXE Core 或总线驱动触发 Supported() 和 Start()。
三、加载流程:从镜像到运行
UEFI 模块的加载流程可以概括为“发现镜像 → 加载到内存 → 重定位 → 解析入口 → 调用入口 → 安装协议/执行任务”。对于 Application,通常由 Shell 通过 LoadImage() 和 StartImage() 完成;对于 DXE Driver,则由 DXE Dispatcher 根据依赖关系调度;对于 UEFI Driver Model,则在 Driver Binding 被安装后,由总线驱动或平台策略触发绑定。
四、案例与场景:自定义协议驱动的加载
假设我们要开发一个平台服务,向后续模块提供“平台信息查询协议”。可以创建一个 DXE Driver,在入口点中安装 EFI_PLATFORM_INFO_PROTOCOL。该驱动不绑定具体设备,因此不需要实现 Driver Binding,只需在入口点调用 InstallProtocolInterface() 即可。
EFI_STATUS
EFIAPI
PlatformInfoInit (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HANDLE Handle = NULL;
Status = gBS->InstallProtocolInterface (
&Handle,
&gEfiPlatformInfoProtocolGuid,
EFI_NATIVE_INTERFACE,
&gPlatformInfo
);
return Status;
}
如果该服务需要管理一个 PCI 设备,则应改为 UEFI Driver Model:实现 Supported() 判断设备是否匹配,Start() 初始化设备并安装协议,Stop() 释放资源。这样,当设备被热插拔或重新枚举时,驱动可以自动响应。
五、常见误区与复盘
- 误区一:把 UEFI Application 当作 DXE Driver 使用。Application 运行在 Boot Services 可用阶段,不能假设自己会在 DXE 调度时被自动加载。
- 误区二:认为所有 DXE Driver 都必须实现 Driver Binding。只有需要管理设备的驱动才需要;纯服务型驱动只需安装协议。
- 误区三:忽略入口点与模块类型的匹配。INF 中
MODULE_TYPE与源文件入口点不一致,会导致构建失败或运行异常。 - 误区四:在
Supported()中做耗时初始化。Supported()应快速判断,真正初始化应放在Start()中。
| 模块类型 | 典型入口点 | 加载者 | 核心职责 |
|---|---|---|---|
| UEFI Application | UefiMain | Shell / Boot Manager | 执行任务、调用协议 |
| DXE Driver | 入口点函数 | DXE Dispatcher | 安装协议、初始化服务 |
| UEFI Driver Model | InitializeDriver | 总线驱动 / DXE Core | Supported/Start/Stop 管理设备 |
| DXE Runtime Driver | 入口点函数 | DXE Dispatcher | 提供运行时服务 |
实践建议:开发前先明确模块类型:是“一次性任务”还是“长期服务”?是否绑定设备?是否需要运行时可用?答案会直接决定 INF 的 MODULE_TYPE、入口点形式和是否实现 Driver Binding。
掌握 UEFI Application、DXE Driver 与 UEFI Driver Model 的差异,本质上是在掌握“模块生命周期”与“服务提供方式”的设计选择。入口点决定模块如何被调用,模块类型决定构建与调度方式,加载流程决定模块何时、由谁、以何种依赖顺序进入系统。只有把这三者统一起来,才能写出结构清晰、可维护、可扩展的 UEFI 模块。