Boot Services是由可能在 UEFI 环境中运行的接口函数的代码定义的。这些代码包括设备管理和扩展性的 protocol, 以及在预启动环境中运行的应用和 OS loader 。
服务分为两类:
- BootServices---在EFI_BOOT_SERVICES.ExitBootServices(). 调用前可以使用
- RuntimeServices---任何时候都可以使用
UEFI application 必须通过 boot service 来访问设备以及内存管理,使用 boot service 时通过一个全局指针(gBS etc) 。BootServices 是由 OS loader 加载EFI_BOOT_SERVICES.ExitBootServices() 来结束使用的,而 OSloader 本质上也是一个 UEFI application,调不调用EFI_BOOT_SERVICES.ExitBootServices()是看该 UEFI application 是否继续使用 Boot services 或者 boot service 的环境。
包括如下的 services:
• Event, Timer, and Task Priority Services (Section 7.1)
• Memory Allocation Services (Section 7.2)
• Protocol Handler Services (Section 7.3)
• Image Services (Section 7.4)
• Miscellaneous Services (Section 7.5)
7.1 Event, Timer, and Task Priority Services
引导服务环境中的执行发生在不同的任务优先级级别(tpl)上。引导服务环境只向UEFI应用程序和驱动程序展示了这些级别中的三个:
• TPL_APPLICATION**,** the lowest priority level
• TPL_CALLBACK, an intermediate priority level
• TPL_NOTIFY, the highest priority level
以高优先级执行的任务可能会中断以低优先级执行的任务。例如,在TPL_NOTIFY级别运行的任务可能会中断在TPL_APPLICATION或TPL_CALLBACK级别运行的任务。虽然TPL_NOTIFY是向引导服务应用程序公开的最高级别,但固件可能有它处理的更高任务优先级项。例如,固件可能必须处理更高优先级的任务,如计时器刻度和内部设备。因此,有第四个TPL, TPL_HIGH_LEVEL,专为固件使用而设计。
执行代码可以通过调用EFI_BOOT_SERVICES.RaiseTPL()函数暂时提高其优先级级别。在调用EFI_BOOT_SERVICES.RestoreTPL()函数将优先级降低到低于挂起事件通知的优先级之前,这样做会屏蔽以相同或更低优先级运行的代码的事件通知。在许多UEFI服务功能和协议接口功能可以执行的TPL级别上有限制。限制说明如表7-3所示。
7.2 Memory Allocation Services
主要提供对应的接口用于内存分配和释放内存,以及获取系统的内存信息。
在预启动的过程中,所有固件组成必须使用 BootService 的接口来对内存进行使用和释放,同时 Boot Service 也会动态更新维护内存,防止出现内存泄漏等问题。同时 EFI image 只能使用自己分配的内存,并且在 image 执行完毕之后需要将分配的内存释放,包括这包括所有 memory page、分配的 Pool、打开的 handle 等。
如下函数实例:在 HandleProtocol 调用获取所有安装该 protocol 的 handle 保存到 buffer 之后,需要使用 FreePool 来释放。
for (Index = 0; Index < HandleCount; Index++) {
Status = EfiTestChildHandle (HandleBuffer[Index], ChildHandle, ProtocolGuid);
if (!EFI_ERROR (Status)) {
Status = gBS->HandleProtocol (HandleBuffer[Index], ProtocolGuid, (VOID **)&Interface);
if (!EFI_ERROR (Status)) {
gBS->FreePool (HandleBuffer);
return Interface;
}
}
}
gBS->FreePool (HandleBuffer);
内存分配时会传入内存类型,不同类型是对于不同的内容。同事根据ExitBootServices() 的调用也会有不同的类型使用:
ExitBootServices 调用前
ExitBootServices 调用后
一个调用ExitBootServices()的映像(即UEFI OS Loader)首先调用
EFI_BOOT_SERVICES.GetMemoryMap()获取当前内存映射。在调用 ExitBootServices()之后,映像隐式地拥有映射中所有未使用的内存。这 包括内存类型EfiLoaderCode, EfiLoaderData, EfiBootServicesCode, EfiBootServicesData和eficonconventional memory。UEFI操作系统Loader和操作系统需要 保留标记为EfiRuntimeServicesCode和EfiRuntimeServicesData的内存。
AllocatePages 与 FreePages
用于从系统上获取内存区域,和释放内存,单位 k 是 4K,也就是对应是 4K 的也就是一个 Page.
typedef
EFI_STATUS
(EFIAPI *EFI_ALLOCATE_PAGES) (
IN EFI_ALLOCATE_TYPE Type,
IN EFI_MEMORY_TYPE MemoryType,
IN UINTN Pages,
IN OUT EFI_PHYSICAL_ADDRESS *Memory
)
typedef
EFI_STATUS
(EFIAPI *EFI_FREE_PAGES) (
IN EFI_PHYSICAL_ADDRESS Memory,
IN UINTN Pages
);
GetMemoryMap
获取当前内存 map 也就是内存使用情况。
typedef
EFI_STATUS
(EFIAPI *EFI_GET_MEMORY_MAP) (
IN OUT UINTN *MemoryMapSize,
OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
OUT UINTN *MapKey,
OUT UINTN *DescriptorSize,
OUT UINT32 *DescriptorVersion
)
AllocatePool 与FreePool
分配一个池内存,size 是以 byte 的,可以自己输入大小而不需要对齐。
typedef
EFI_STATUS
(EFIAPI *EFI_ALLOCATE_POOL) (
IN EFI_MEMORY_TYPE PoolType,
IN UINTN Size,
OUT VOID **Buffer
)
typedef
EFI_STATUS
(EFIAPI *EFI_FREE_POOL) (
IN VOID *Buffer
)
7.3 Protocol Handler Services
在 UEFI 抽象出了由一个全局唯一的 GUID 以及 protocol 实体的 protocol 结构体,结构中包含访问 device 的函数以及所需要的数据。
应用程序可以在 device handle 上面安装 protocol,以及通过 Handle service 来对 handle 上的 Protocol 进行操作,以及确认是否支持某个 protocol.
Driver Model Boot Services
解决方案是在句柄数据库本身中跟踪协议接口的使用情况。为此, 每个协议接口都包含一个正在使用该协议接口的代理列表。图7-2 显示了一个带有这些新代理列表的句柄数据库示例。代理由一个图像句柄、一个 控制器句柄和一些属性组成。图像句柄标识正在 使用协议接口的驱动程序或应用程序。控制器句柄标识正在使用 协议接口的控制器。由于驱动程序可以管理多个控制器,驱动程序的 图像句柄和控制器的控制器句柄的组合唯一地标识正在使用 协议接口的代理。这些属性显示了如何使用协议接口。