读书笔记仅摘取对个人有用的部分,详细内容请阅读原著
目前更新至第七章,因个人仅需要引导程序,故其余部分未介绍,详细内容请阅读原著
书及其资料均已放置在本人资源中,如需下载请移步资源模块
2021-11-11
BIOS全称为“基本输入/输出系统”,主要负责
BIOS程序运行在16位实模式下,实模式下最大的寻址范围是 1MB,0xOCO000 ~ Ox0FFFFF保留给BIOS使用。开机后,CPU跳到OxOFFFFO处执行,一般这里是一条跳转指令,跳到真正的BIOS入口处执行。BIOS代码首先做的是“加电自检”( Power On Self Test,POST),主要是检测关机设备是否正常工作,设备设置是否与CMOS中的设置一致。如果发现硬件错误,则通过喇叭报警。POST检测通过后初始化显示设备并显示显卡信息,接着初始化其他设备。设备初始化完毕后开始检查CPU和内存并显示检测结果。内存检测通过以后开始检测标准设备,例如硬盘、光驱、串口设备、并口设备等。然后检测即插即用设备,并为这些设备分配中断号、I/O端口和DMA通道等资源。如果硬件配置发生变化,那么这些变化的配置将更新到CMOS中。随后,根据配置的启动顺序从设备启动,将启动设备主引导记录的启动代码通过BIOS 中断读入内存,然后控制权交到引导程序手中,最终引导进入操作系统。
缺点
UEFI (Unified Extensible Firmware Interface,统一可扩展固件接口)
UEFI提供给操作系统的接口包括启动服务(Boot Services,BS)和运行时服务(RuntimeService,RT)以及隐藏在BS之后的丰富的Protocol
从操作系统加载器( OS Loader)被加载,到OS Loader执行ExitBootServices()的这段时间,是从UEFI环境向操作系统过渡的过程。在这个过程中,OS Loader可以通过BS和RT使用UEFI提供的服务,将计算机系统资源逐渐转移到自己手中,这个过程称为TSL(Transient System Load)。
当OS Loader完全掌握了计算机系统资源时,BS也就完成了它的使命。OS Loader调用ExitBootServices()结束BS并回收BS占用的资源,之后计算机系统进入UEFI Runtime阶段。
在TSL阶段,系统资源通过BS管理,BS提供服务如下
优点
SEC(安全验证)→PEI(EFI前期初始化)→DXE(驱动执行环境)
→BDS(启动设备选择)→TSL(操作系统加载前期)
→RT (Run Time)
→AL(系统灾难恢复期)
流程
加电→Reset Vector→SEC入口函数→PEI函数
其中Reset Vector阶段流程为
进入固件入口。
从实模式转换到32位平坦模式(包含模式)。
定位固件中的 BFV (Boot Firmware Volume)。
定位BFV中的SEC映像。
若是64位系统,从32位模式转换到64位模式。
调用SEC入口函数。
UEFI的一个重要特点是其模块化的设计。模块载入内存后生成Image。Image的入口函数为_ModuleEntryPoint。PEI也是一个模块,PEI Image的入口函数_ModuleEntryPoint,位于MdePkg/Library/PeimEntryPoint/PeimEntryPoint.c。_ModuleEntryPoint最终调用PEI模块的入口函数PeiCore,位于MdeModulePkg/Core/Pei/PeiMain/PeiMain.c。进入 PeiCore后,首先根据从SEC阶段传入的信息设置Pei Core Services,然后调用PeiDispatcher-执行系统中的PEIM,当内存初始化后,系统会发生栈切换并重新进入PeiCore。重新进入PeiCore后使用的内存为我们所熟悉的内存。所有PEIM都执行完毕后,调用PeiServices 的LocatePpi服务得到DXE IPL PPI,并调用DXE IPL PPI的 Entry服务,这个Entry服务实际上是DxeLoadCore,它找出 DXE Image的入口函数,执行DXE Image的人口函数并将HOB列表传递给DXE
DXE (Driver Execution Environment)阶段执行大部分系统初始化工作,进入此阶段时,内存已经可以被完全使用,因而此阶段可以进行大量的复杂工作
DXE提供的基础服务包括系统表、启动服务、Run Time Services
DXE驱动之间通过Protocol通信。Protocol是一种特殊的结构体,每个Protocol对应一个GUID,利用系统BootServices的 OpenProtocol,并根据GUID来打开对应的Protocol,进而使用这个Protocol提供的服务。
当所有的Driver都执行完毕后,系统完成初始化,DXE通过EFI_BDS_ARCH_PROTOCOL找到BDS并调用BDS的人口函数,从而进入BDS阶段。从本质上讲,BDS是一种特殊的DXE阶段的应用程序。
初始化控制台设备
加载必要的设备驱动
根据系统设置加载和执行启动项
如果加载启动项失败,系统将重新执行DXE dispatcher 以加载更多的驱动,然后重新尝试加载启动项
BDS策略通过全局NVRAM变量配置。这些变量可以通过运行时服务的GetVariable()读取,通过SetVariable()设置
用户选中某个启动项(或系统进入默认的启动项)后,OS Loader启动,系统进入 TSL 阶段
TSL ( Transient System Load)是操作系统加载器(OS Loader)执行的第一阶段,在这一阶段OS Loader作为一个UEFI应用程序运行,系统资源仍然由UEFI内核控制。当启动服务的ExitBootServices()服务被调用后,系统进入 Run Time阶段。
TSL阶段之所以称为临时系统,在于它存在的目的就是为操作系统加载器准备执行环境。虽然是临时系统,但其功能已经很强大,已经具备了操作系统的雏形,UEFI Shell是这个临时系统的人机交互界面。正常情况下,系统不会进入UEFI Shell,而是直接执行操作系统加载器,只有在用户干预下或操作系统加载器遇到严重错误时才会进入 UEFI Shell。
系统进入RT (Run Time)阶段后,系统的控制权从UEFI内核转交到OS Loader手中,UEFI占用的各种资源被回收到OS Loader,仅有UEFI运行时服务保留给OS Loader和 OS使用。随着OS Loader的执行,OS最终取得对系统的控制权。
在RT阶段,如果系统(硬件或软件)遇到灾难性错误,系统固件需要提供错误处理和灾难恢复机制,这种机制运行在AL ( After Life)阶段。UEFI 和UEFI PI标准都没有定义此阶段的行为和规范
详情请阅读《UEFI原理与编程》或DellaOS引导程序篇:https://blog.csdn.net/gyfghh/article/details/121261968
模块类型 | 说明 |
---|---|
标准应用模块 | 在DXE阶段运行的应用程序(Shell状态下也可以) |
ShellAppMain应用程序工程模块 | Shell环境下运行的应用程序 |
main应用程序工程模块 | Shell环境下运行的应用程序,并连接了StdLib库 |
UEFI驱动模块 | 符合UEFI驱动模型的驱动,在BS期间有效 |
库模块 | 作为静态库被其它模块调用 |
DXE驱动模块 | DXE环境下运行的驱动,不符合UEFI驱动模型的驱动 |
DXE运行时驱动模块 | 进入运行期仍有效的驱动 |
DXE SLA驱动模块 | 仅对安腾CPU有效的驱动 |
DXE SMN驱动模块 | 系统管理模式驱动,模块被加载到系统管理内存区。系统进入运行期该驱动仍然有效 |
PEIM | PEI阶段的模块 |
SEC模块 | 固件的SEC阶段 |
PEI_CORE模块 | 固件的PEI阶段 |
DXE_CORE模块 | 固件的DXE阶段 |
标准应用程序工程模块是其他应用程序工程模块的基础,也是UEFI中常见的一种应用程序工程模块。每个工程模块由两部分组成:工程文件和源文件,标准应用程序工程模块也不例外。源文件包括C/C+文件、.asm汇编文件,也可以包括.uni(字符串资源文件)和.vfr(窗体资源文件)等资源文件。下面以一个简单的标准应用程序工程模块为例来介绍它的格式。
一个简单的标准应用程序工程模块包含一个C程序源文件(Main.c)以及一个工程文件(Main.inf)
Main.c
#include
EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable)
{
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"Hello World\n");
return EFI_SUCCESS;
}
向标准输出设备打印字符串是通过SystemTable的ConOut提供的服务完成的。ConOut是EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 的一个实例。而EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL的主要功能是控制字符输出设备。向输出设备打印字符串是通过ConOut提供的OutputString服务完成的。该服务(函数)的第一个参数是This指针,指向EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL实例(此处为ConOut)本身;第二个参数是Unicode字符串。关于Protocol和This指针将在第4章详细介绍。简而言之,这条打印语句的意义就是通过SystemTable → ConOut →OutputString服务将字符串L“HelloWorld”打印到SystemTable→ ConOut所控制的字符输出设备。
Main.inf
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = Main
FILE_GUID = 6987936E-f301-4a63-9661-fc6030dcc899
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
Main.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
[Protocols]
[Guids]
必需块 | 块描述 |
---|---|
[Defines] | 定义本模块的属性变量及其他变量,这些变量可在工程文件其他块中引用 |
[Sources] | 列出本模块的所有源文件及资源文件 |
[Packages] | 列出本模块引用到的所有包的包声明文件。可能引用到的资源包括头文件、GUID、Protocol等,这些资源都声明在包的包声明文件.dec中 |
[LibraryClasses] | 列出本模块要链接的库模块 |
非必需块 | 块描述 |
---|---|
[Protocols] | 列出本模块用到的Protocols |
[Guids] | 列出本模块用到的GUID |
[BuildOptions] | 指定编译和链接选项 |
[Pcd] | Pcd全称为平台配置数据库(Platform Configuration Database)。[Pcd]用于列出本模块用到的Pcd变量,这些Pcd变量可被整个UEFI系统访问 |
[PcdEx] | 用于列出本模块用到的Pcd变量,这些Pcd变量可被整个UEFI系统访问 |
[FixedPcd] | 用于列出本模块用到的Pcd编译期常量 |
[FeaturePcd] | 用于列出本模块用到的Pcd常量 |
[PatchPcd] | 列出的Pcd变量仅本模块可用 |
语法格式
属性名 = 属性值
必选属性
变量名 | 功能描述 |
---|---|
INF_VERSION | INF标准的版本号,通常将INF_VERSION设置为0x00010005即可 |
BASE_NAME | UEFI程序的名字,不能包含空格 |
FILE_GUID | 所有EDK2的INF文件都必须包含GUID值(格式:8-4-4-4-12) |
MODULE_TYPE | 程序类型(详情请查阅《UEFI原理与编程 戴正华(著)》) |
VERSION_STRING | 模块的版本号字符串 |
ENTRY_POINT | 模块的入口函数 |
语法格式
块内每一行表示一个文件,文件使用相对路径,根路径是工程文件所在的目录
示例1
[Sources]
UefiMain.c
示例2
[Sources]
UefiMain.c
[Sources .IA32]
Cpu32.c
[Sources .x64]
Cpu64.c
语法格式
块内每一行列出一个文件,文件使用相对路径,若[Sources]块内列出了源文件,则在[Packages]块必须列出MdePkg/MdePkg.dec,并将其放在本块的首行
示例
[Packages]
MdePkg/MdePkg.dec
语法格式
块内每一行声明一个要链接的库
应用程序工程模块必须链接UefiApplicationEntryPoint库;驱动模块必须链接UefiDriverEntryPoint库
示例
[LibraryClasses] UefiApplicationEntryPoint
可转至DellaOS引导程序篇——UEFI下的 ”Hello,world!“
EDK2 为了方便开发者,提供了 UefiBootServicesTableLib,在 UefiLib 定义了全局变量 gST 、gBS、gImageHandle。这三个全局变量在函数 UefiBootServicesTableLibConstructor 中被初始化,下面是 gST 、gBS、gImageHandle的初始化代码
EFI_HANDLEgImageHandle = NULL;
EFI_SYSTEM_TABLE *gST = NULL;
EFI_BOOT_SERVICES *gBS = NULL;
EFI_STATUS EFIAPI UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable)
{
gImageHandle = ImageHandle;
gST = SystemTable;
gBS = SystemTable->BootServices;
return EFI_SUCCESS;
}
要使用Protocol,首先要根据GUID找到Protocol对象,启动服务提供了OpenProtocol(…)、HandleProtocl(…)和LocateProtocol(…)三种服务用于找出指定的Protocol。OpenProtocol用于打开指定句柄上的Protocol。HandleProtocol是OpenProtocol的简化版。LocateProtocol用于找出指定的Potocol在系统中的第一个实例。使用完Protocol后还要关闭打开的 Protocol,否则可能会造成内存泄漏
OpenProtocol用于查询指定的Handle中是否支持指定的Protocol,如果支持,则打开该Protocol,否则返回错误代码
//openProtocol
//retval EFISUCCESS 成功打开Protocol,打开信息添加到Protocol的打开列表中
//retval EFI_UNSUPPORTED Handle不支持Protocol
//@retval EFI_INVALID_PARAMETER 无效参数
//retval EFI_ACCESS_DENIED Attribute不被当前环境支持
//retval EFI_ALREADYSTARTED Protocol已经被同一AgentHandle打开
typedef EFI_STATUS (EFIAPI*EFI_OPEN_PROTOCOL)(
INEFI_HANDLE Handle, //指定的Handle,将查询并打开此Handle中安装的 Protocol
IN EFI_GUID *Protocol, //要打开的Protoco1(指向该Protocol GUID的指针)
OUT VOID **Interface,OPTIONAL //返回打开的 Protoco1对象
IN EFI_HANDLE AgentHandle, //打开此Protocol的Image
IN EFI_HANDLE ControllerHandle, //使用此Protocol的控制器
IN UINT32 Attributes //打开Protocol的方式
);
OpenProtocol功能很强大,但使用起来也比较复杂,除了要提供Handle和 Protocol的GUID,还要提供AgentHandle、ControllerHandle和 Attributes。大部分情况下,开发者只是想通过Protocol 的GUID得到Protocol对象,并不关心打开方式的细节。为了方便开发者使用Protocol,启动服务提供了HandleProtocol 以简化打开Protocol
//HandleProtocl
//retval EFI_SUCCESS 成功打开指定的Protocol
//@retval EFI_UNSUPPORTED 指定的设备(Handle)不支持该Protocol
//retval EFI_INVALID_PARAMETER 参数Handle , Protocol或Interface是NULL
typedef EFI_STATUS(EFIAPI *EFT_HANDLE_PROTOCOL)(
IN EFI_HANDLE Handle, //查询该Handle是否支持Protocol
IN EFI_GUID*Protocol, //带查询的Protocol
OUT VOID * *Interface //返回待查询的 Protocol
);
OpenProtocol和 HandleProtocol用于打开指定设备上的某个Protocol,要使用这两个函数,首先要得到这个设备的句柄,而找出这个设备的句柄并不是很容易。有时开发者并不关心Protocol在哪个设备上,尤其是系统仅有一个该Protocol的实例时。启动服务提供了LocateProtocol(…)服务,它可以从UEFI内核中找出指定Protocol的第一个实例
//LocateProtocol
//retval EFI_sUCCESS 成功找到匹配的 Protocol
//retval EFI_NOT_ FOUND 系统中无法找到匹配的Protocol
//retval EFIT_INVALID_PARAMETER Interface为空
typedef EFI_STATUS (EFIAPI *EFI_LOCATE_PROTOCOL)(
IN EFI_GUID *Protocol, //待查询的Protocol
IN VOID *Registration, //可选参数,从 RegisterProtocolNotify()获得的Key
OUT VOID **Interface //返回系统中第一个匹配到的Protocol实例
);
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* SimpleFs;
gBS->LocateProtocol(&gEfiSimpleFileSystemProtocolGuid,NULL,(VOID **)&SimpleFs);
有时候开发者需要找出支持某个Protocol的所有设备,例如找出系统中的所有安装了BlockIo的设备,LocateHandleBuffer和LocateHandle服务提供了这个功能
待补
Protocol使用完毕后要通过CloseProtocol关闭打开的Protocol
通过HandleProtocol和 LocateProtocol打开的Protocol因为没有指定AgentHandle,所以无法关闭。如果一定要关闭它,则要调用OpenProtocolInformation()获得 AgentHandle和ControllerHandle,然后关闭它
EFI_STATUS CloseProtocol
(
IN EFI_HANDLE Handle, //已打开的协议接口
IN EFI_GUID *Protocol, //协议的GUID值
IN EFI_HANDLE AgentHandle, //镜像文件的句柄
IN EFI_HANDLE ControllerHandle //控制器的句柄
);
作为操作系统和硬件之间的接口,UEFI的最主要功能就是为操作系统加载器准备软件和硬件资源,接口是以启动服务和运行时服务的形式提供给操作系统和UEFI用在厅使用的。应用程序只有通过系统表才能获得启动服务和运行时服务。得到了系统表、启动服务和运行时服务也就控制了整个计算机系统。
对UEFI应用程序和驱动程序开发人员来讲,系统表是最重要的数据结构之一,它是用户空间通往内核空间的通道。有了它,UEFI应用程序和驱动才可以访问UEFI内核、硬件资源和输入/输出设备。
计算机系统进入DXE阶段后系统表被初始化,因而系统表只能用于DXE阶段以及以后的应用程序和驱动中。系统表是UEFI内核的一个全局结构体,其指针作为程序映像(Image)入口函数的参数传递到用户空间。程序映像(包括UEFI应用程序、DXE 驱动程序以及UEFI驱动程序)的入口函数有统一的格式
typedef EFI_STATUS (EFIAPI *EFI_IMAGE_ENTRY_POINT)(
INEFI_HANDLE ImageHandle,
INEFI_SYSTEM_TABLE *SystemTable
);
typedef struct {
EFI_TABLE_HEADER Hdr; //标准UEFI表头
CHAR16 *Firmwarevendor; //固件提供商
UINT32 FirmwareRevision; //固件版本号
EFI_HANDLE ConsoleInHandle; //输入控制台设备句柄
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn; //
EFI_HANDLE ConsoleoutHandle; //输出控制台设备句柄
EFI_SIMPLE_TEXT_OUTPUT_PROTOCoL *Conout; //
EFT_HANDLE StandardErrorHandle; //标准错误控制台设备
EFI_SIMPLE_TEXT_OUTPUT_PROTOCoL *StdErr; //
EFI_RUNTIME_SERVICES *RuntimeServices; //运行时服务表
EFI_BOOT_SERVICES *BootServices; //启动时服务表
UINTN NumberOfTableEntries; //ConfigurationTable数组大小
EFI_CONFIGURATION_TABLE *ConfigurationTable; //系统配置表数组
}EFI_SYSTEM_TABLE;
EFI_TABLE_HEADER(标准UEFI表头)
typedef struct {
UINT64 signature;
UINT32 Revision;
UINT32 Headersize;
UINT32 CRC32;
UINT32 Reserved;
}EFI_TABLE_HEADER;
EFI_CONFIGURATION_TABLE(系统配置表)
typedef struct {
EFI_GUID VendorGuid;
VOID *vendorTable;
}EFI_CONFIGURATION_TABLE;
EDK2为了方便开发者,提供了UefiBootServicesTableLib,在UefiLib定义了全局变量gST (指向SystemTable)、gBS(指向SystemTable->BootServices)和gImageHandle ( ImageHandle)。这三个全局变量在函数UefiBootServicesTableLibConstructor中被初始化
EFI_HANDLEgImageHandle = NULL;
EFI_SYSTEM_TABLE*gST= NULL;
EFI_BOOT_SERVICES*gBS= NULL;
EFT_STATUS EFIAPI UefiBootServicesTableLibConstructor ( IN EFI_HANDLE ImageHandle,IN EFI_sYSTEM_TABLE* SystemTable)
{
gImageHandle = ImageHandle;gST =SystemTable;
gBS = systemTable->Bootservices;return EFI_SUCCESS;
}
在系统启动过程中,系统资源通过启动服务提供的服务来管理。系统进入DXE阶段时启动服务表被初始化,最终通过SystemTable指针将启动服务(BS)表传递给UEFI应用程序或驱动程序。UEFI应用程序和驱动程序可以通过gST->BootServices或gBS访问启动服务表。
启动服务是UEFI的核心数据结构,有了它,我们才可以使用计算机系统内的资源,它提供的服务可以分为以下几类。
启动服务也称启动服务表,它由UEFI表头和表项组成。表中每一项是一个函数指针,这个函数用于提供一项服务。开发UEFI应用和驱动离不开启动服务,深入理解启动服务提供的每一个服务是开发者一项必不可少的任务。上文提到过,启动服务中的服务大致可以分为8类:UEFI事件服务、内存管理服务、Protocol管理服务、Protocol使用类服务、驱动管理服务、Image管理服务、ExitBootServices 及其他服务。本节将简单介绍启动服务中的每一类服务的作用,并详细介绍内存管理相关的服务。
函数名 | 作用 |
---|---|
CreateEvent | 生成一个事件对象 |
CreateEventEx | 生成一个事件对象并将该事件加入到一个组内 |
CloseEvent | 关闭事件对象 |
SignalEvent | 触发事件对象 |
WaitForEvent | 等待事件数组中的任一事件被触发 |
CheckEvent | 检查事件状态 |
SetTimer | 设置定时器属性 |
RaiseTPL | 提升任务优先级 |
RestoreTPL | 恢复任务优先级 |
内存管理服务 | 作用 |
---|---|
AllocatePool | 分配内存 |
FreePool | 释放内存 |
GetMemoryMap | 获得当前内存映射(物理地址<→虚地址) |
AllocatePages | 分配内存页 |
FreePages | 释放内存页 |
Protocol管理服务 | 作用 |
---|---|
InstallProtocolInterface | 安装 Protocol到设备上 |
UninstallProtocolInterface | 从设备上卸载Protocol |
ReinstallProtocolInterface | 重新安装Protocol |
RegisterProtocolNotify | 为指定的Protocol注册通知事件,当这个Protocol安装时,该事件触发 |
InstallMultipleProtocolInterfaces | 安装多个Protocol到设备上 |
UninstallMultipleProtocolInterfaces | 从设备上卸载多个Protocol |
Protocol使用类服务 | 作用 |
---|---|
OpenProtocol | 打开 Protocol |
HandleProtocol | 打开 Protocol,OpenProtocol的简化版 |
LocateProtocol | 找出系统中指定Protocol的第一个实例 |
LocateHandleBuffer | 找出支持指定Protocol的所有Handle。系统负责分配内存,调用者负责释放内存 |
LocateHandle | 找出支持指定Protocol的所有Handle,调用者负责分配和释放内存 |
OpenProtocolInformation | 返回指定Protocol的打开信息 |
ProtocolsPerHandle | 找出指定Handle 上安装的所有Protocol |
CloseProtocol | 关闭Protocol |
LocateDevicePath | 在指定的设备路径下找出支持给定Protocol的设备,并返回离指定的设备路径最近的设备 |
驱动管理服务 | 作用 |
---|---|
ConnectController | 将驱动安装到指定的设备控制器 |
DisconnectController | 将驱动从指定的设备控制器卸载 |
lmage管理服务 | 作用 |
---|---|
LoadImage | 加载.efi文件至内存并生成Image |
StartImage | 启动Image,也就是调用Image的入口函数 |
Exit | 退出Image |
UnloadImage | 卸载Image |
服务名 | 作用 |
---|---|
ExitBootServices | 结束启动服务 |
服务名 | 作用 |
---|---|
InstallConfigurationTable | 管理(增加、更新、删除)系统配置表项 |
GetNextMonotonicCount | 获得系统单调计数器的下一个值 |
Stall | 暂停CPU指定的微秒数 |
SetWatchdogTimer | 设置“看门狗”定时器,即在指定的时间内若系统无反应,则重启系统 |
CalculateCrc32 | 计算CRC32校验码 |
CopyMem | 复制内存 |
SetMem | 设置指定内存区域的值 |
内存管理服务的5个服务可以分为3组
AllocatePool/FreePool
//AllocatePool
//retval EFI_SUCCESS 成功分配所请求的内存
//retval EFI_OUT_OF_RESOURCES 资源耗尽
//retval EFI_INVALID_PARAMETER 参数非法
typedef EFI_STATUS (EFIAPI *EFI_ALLOCATE_POOL)(
INEFI_MEMORY_TYPE PoolType, //内存类型
IN UINTN Size, //需分配的内存字节数
OUT VOID **Buffer //返回分配的内存首地址
);
//FreePool
//retval EFI_SSCCESS 内存成功被系统回收
//retval EFI_INVALID_PARAMETER Buffer无效
typedef EFI_STATUS (EFIAPI *EFI_FREE_POOL)(
IN VOID *Buffer //待释放内存
);
数值 | 类型名 | 功能描述 |
---|---|---|
0 | EfiReservedMemoryType | 保留 |
1 | EfiLoaderCode | 分配给OS加载器的代码 |
2 | EfiLoaderData | 分配给OS加载器的数据,应用程序分配内存的默认类型 |
3 | EfiBootServicesCode | 引导服务程序的代码区 |
4 | EfiBootServicesData | 引导服务程序的数据区,引导服务驱动分配内存的默认类型 |
5 | EfiRuntimeServicesCode | 运行时服务程序的代码区 |
6 | EfiRuntimeServicesData | 运行时服务程序的数据区,运行时服务驱动分配内存的默认类型 |
7 | EfiConventionalMemory | 可分配内存 |
8 | EfiUnusableMemory | 内存区域存在错误,不能使用 |
9 | EfiACPIReclaimMemory | 用于存放ACPI表 |
10 | EfiACPIMemoryNVS | 保留给固件使用 |
11 | EfiMemoryMappedIO | MMIO内存,可被运行时服务使用 |
12 | EfiMemoryMappedIOPortSpace | MMIO端口,被CPU用于转换内存周期到IO周期 |
13 | EfiPalCode | 保留给固件使用 |
14 | EfiPersistentMemory | 作为EfiConventionalMemory工作的内存区域 |
AllocatePages/FreePages
//AllocatePages
//retval EFI_sUCCESS 成功分配页面
//retval EFI_INVALID_PARAMETER 参数不合法
//retval EFI_OUT_OF_RESOURCES 资源耗尽
//retval EFI_NOT_FOUND 请求的物理页不存在
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 //输入:物理地址或NULL;输出:分配到的页面的虚拟地址
);
//FreePages
typedef EFI_STATUS(EFIAPI *EFI_FREE_PAGES)(
IN EFI_PHYSICAL_ADDRESS Memory, //要释放的页面的起始物理地址
IN UINTN Pages //要释放的页面数
);
typedef enum {
AllocateAnyPages, //分配任何可用的页面
AllocateMaxAddress, //分配到的页面的末地址必须不超过指定的地址
AllocateAddress, //分配指定物理地址上的页面
MaxAllocateType
}EFI_ALLOCATE_TYPE;
GetMemoryMap
//GetMemoryMap
//retval EFI_SUCCESS 成功返回当前内存映射
//retval EFI_BUFFER_TOO_SMALL 缓冲区太小,MemoryMapsize将返回需要的大小
//retval EFI INVALID PARAMETER 非法参数
typedef EFI_STATUS (EFIAPI *EFT_GET_MEMORY_MAP)(
IN OUT UINTN *MemoryMapsize, //输出缓冲区
IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, //输出缓冲区
OUT UINTN*MapKey, //当前内存映射的Key
OUT UINTN *Descriptorsize, //EFI MEMORYDESCRIPTOR大小
OUT UINT32 * DescriptorVersion //EFIMEMORYDESCRIPTOR版本
);
typedef struct {
UINT32 Type; //EFI_MEMORY_TYPE,内存类型
EFI_PHYSICAL_ADDRESS Physicalstart; //首字节物理地址,必须按4KB对齐
EFI_VIRTUAL_ADDRESS virtualstart; //首字节的虚拟地址,必须按4KB对齐
UINT64 NumberofPages; //此区域的页面数
UINT64 Attribute; //内存属性
}EFI_MEMORY_DESCRIPTOR;
从启动服务可以看出其功能之强大,通过它可以完全掌控整个计算机系统,其功能与操作系统提供的功能有很多重叠之处。同时它也占用了大量的计算机系统资源。设计启动服务的主要目的是帮助操作系统加载器初始化计算机系统,当操作系统加载器彻底取得对计算机系统的控制权之后,启动服务也就完成了它的使命,启动服务占用的系统资源也需要转交给操作系统加载器。此时需要调用gBS->ExitBootServices,其作用正是结束启动服务、释放启动服务占用的资源以及使用启动服务分配到的启动期资源,将控制权交给操作系统加载器。也就是说,启动服务的生存期在 DxeMain与gBS->ExitBootServices之间。
从进入DXE 阶段运行时服务被初始化,直到操作系统结束,运行时服务都一直存在并向上层(操作系统、操作系统加载器、UEFI应用程序或UEFI驱动)提供服务。
运行时服务(RT)也称为运行时服务表,其结构同启动服务表类似,由表头和表项组成。每一个表项是一个函数指针,表示一个服务。
//GetTime
typedef EFI_STATUS (EFIAPI *EFI_GET_TIME)(
OUT EFI_TIME *Time, //当前时间
OUT EFT_TIME_CAPABILITIES *Capabilities OPTIONAL //时钟硬件的性能
) ;
//SetTime
//retval EFI_SUCCESS 成功设置时钟
//retval EFI_INVALID_PARAMETER Time超出范围
//retval EFI_DEVICE_ERROR RTC硬件返回错误
typedef EFI STATUS(EFIAPI *EFT_SET_TIME)(IN EFI_TIME *Time);
EFI_TIME
typedef struct {
UINT16 Year; //1900 ~ 9999
UINT8 Month; //1 ~ 12
UINT8 Day; //1 ~ 31
UINT8 Hour; //0 ~ 23
UINT8 Minute; //0~59
UINT8 Second; //0~ 59
UINT8 Padl; //填充
UINT32 Nanosecond; //o ~ 999
INT16 Timezone; //1440 ~ 1440
UINT8 Daylight; //夏时制
UINT8 Pad2; //填充
}EFI_TIME;
EFT_TIME_CAPABILITIES
GetWakeupTime用于读取唤醒定时器状态,SetWakeupTime用于启用或禁用唤醒定时器
//GetWakeupTime
typedef EFI_STATUS (EFIAPI *EFI_GET_WAKEUP_TIME)(
OUT BOOLEAN *Enabled, //返回定时器启用或禁用状态
OUT BOOLEAN *Pending, //定时器信号是否处于Pending状态
OUT EFI_TIME *Time //返回定时器设置
);
//SetWakeupTime
typedef EFI_STATUS(EFIAPI *EFT_SET_WAKEUP_TIME)(
IN BOOLEAN Enable, //启用或禁用唤醒定时器
IN EFI_TIME *Time OPTIONAL //启用时,定时器的设置
);
UEFI系统变量服务包括读取变量的GetVariable、更新或创建或删除变量的SetVariable,以及用于遍历系统变量的GetNextVariableName(获取下一个系统变量,通过这个服务可以遍历系统中的变量)
//Getvariable
//retval EFI_SUCCESS 成功返回
//retval EFI_NOT_ FOUND 变量不存在
//retval EFI_BUFFER_ TO0_SMALL 缓冲区大小DataSize太小
//retval EFI_INVALID_PARAMETER variableName/vendorGuid/ Datasize/ Data为空
//retval EFI_DEVICE_ERROR 设备返回错误
//retval EFI_SECURITY_VIOLATION 该变量需要身份验证,但身份验证没通过
typedef EFI_STATUS (EFIAPI *EF工_GET_VARIABLE)(
IN CHAR16 *variableName, //变量名字
IN EFI_GUID *vendorGuid, //变量所有者GUID
OUT UINT32 *Attributes,OPTIONAL //变量属性
OUT VOID *Data //返回变量的值
);
//SetVariable(Attributes为0表删除)
typedef EFI_STATUS (EFIAPI *EFI_SET_VARIABLE)(
IN CHAR16 *variableName, //变量名字
IN EFI_GUID *vendorGuid, //变量所有者GUID
IN UINT32 *Attributes, //变量属性
IN UINTN Datasize, //缓冲区Data大小
IN VOID * Data //变量的值
);
//GetNextVariableName
typedef EFI_STATUS (EFIAPI *EFI_GET_NEXT_VARIABLE_NAME)(
IN OUT UINTN *VariableNamesize,
IN OUT CHAR16 *VariableName,
IN OUT EFI_GUID*vendorGuid
);
待补
UEFI不再支持中断(准确地说,UEFI不再为开发者提供中断支持,但在UEFI内部还是使用了时钟中断),所有的异步操作都要通过事件(Event)来完成。事件的重要性是不言而喻的
函数名 | 作用 |
---|---|
CreateEvent | 生成一个事件对象 |
CreateEventEx | 生成一个事件对象并将该事件加入到一个组内 |
CloseEvent | 关闭事件对象 |
SignalEvent | 触发事件对象 |
WaitForEvent | 等待事件数组中的任一事件被触发 |
CheckEvent | 检查事件状态 |
SetTimer | 设置定时器属性 |
RaiseTPL | 提升任务优先级 |
RestoreTPL | 恢复任务优先级 |
等待Event数组内任一事件被触发
//WaitForEvent
//retval EFI_SUCCESS 下标为*index的事件被触发
//retval EFI__UNSUPPORTED 当前的TPL不是TPL_APPLICATION
//retval EFI_INVALID_PARAMETER 下标为*index的事件类型为EVT_NOTIFY_SIGNAL
typedef EFI_STATUS (EFIAPI *EFI_WAIT_FOR_EVENT)(
IN UINTN NumberOfEvents, //Event数组内Event的个数
IN EFI_EVENT *Event, //Event数组
OUT UINTN * Index //返回处于触发态的事件在数组内的下标
);
WaitForEvent是阻塞操作,直到Event数组内任一事件被触发,或任一事件导致错误出现,WaitForEvent才返回。WaitForEvent 从前到后依次检查Event数组内的事件,发现有被触发的事件或遇到错误则返回,如果所有事件都没有被触发,则从头开始重新检查
当检查到某个事件处于触发态时,*Index赋值为该事件在Event数组中的下标,返回前该事件将重置为非触发态
当检查到某个事件是EVT_NOTIFY_SIGNAL类型时,Index赋值为该事件在Event数组中的下标,并返回EFI_INVALID_PARAMETER
WaitForEvent必须运行在TPL_APPLICAION级别,否则将返回EFI_UNSUPPORTED
WaitForEvent没有超时属性,如果想让WaitForEvent 只等待一定的时间,则需要在事件等待数组加入定时器事件
typedef EFI_STATUS (EFIAPI *EFI_CREATE_EVENT)(
IN UINT32 Type, //事件类型
INEFI_TPL NotifyTpl, //事件Notification函数的优先级
IN EFI_EVENT_NOTIFY NotifyFunction, //事件Notification函数
IN VOID *NotifyContext, //传给事件Notification函数的参数
OUT EFI_EVENT *Event //生成的事件
);
1、事件类型
#define EVT_TIMER 0x80000000
#define EVT_RUNTIME 0x40000000
#define EVT_NOTIFY_WAIT 0x00000100
#define EVT_NOTIFY_SIGNAL 0x00000200
#define EVT_SIGNAL_EXIT_BOOT_SERVICES 0x00000201
#define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE 0x60000202
#define EVT_RUNTIME_CONTEXT 0x20000000
事件类型 | 事件特征 |
---|---|
EVT_TIMER | 定时器事件。普通Timer事件,没有Notification函数。生成事件后需调用SetTimer服务设置时钟属性 通过SetTimer()设置等待事件 到期后通过SignalEvent()触发 通过WaitForEvent()等待事件被触发 通过CheckEvent()检查状态 |
EVT_NOTIFY_WAIT | 普通事件。这个事件有一个Notification函数,当这个事件通过CheckEvent()检查状态或通过WaitForEvent()等待时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中 |
EVT_NOTIFY_SIGNAL | 普通事件。这个事件有一个Notification函数,当这个事件通过SignalEvent()被触发时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中等待执行 |
0x00000000 | 普通事件。此类事件没有Notification函数 通过SignalEvent()被触发 通过WaitForEvent()等待事件被触发 通过CheckEvent(检查状态 |
EVT_TIMER|EVT_NOTIFY_WAIT | 带Notification函数的定时器事件。此类事件除了具有EVT_TIMER的特性外,还有EVT_NOTIFY_WAIT的特性,即到期后通过SignalEvent()触发。 当事件通过CheckEvent(检查状态或通过WaitForEvent()等待时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中 |
EVT_TIMER|EVT_NOTIFY_SIGNAL | 带Notification函数的定时器事件。此类事件除了具有EVT_TIMER的特性外,还有EVT_NOTIFY_WAIT的特性,即到期后通过SignalEvent()触发。 当事件通过SignalEvent()被触发时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中 |
**EVT_SIGNAL_EXIT_BOOT_SERVICES:**此类事件是一种特殊的EVT_NOTIFY_SIGNAL,实际上它是EVT_NOTIFY_SIGNAL和0x00000001的组合。当ExitBootServices()执行时,事件被触发。EVT_SIGNAL_EXIT_BOOT_SERVICES不能和其他类型混合使用。它的Notification函数和子函数不能使用启动服务中的内存分配服务﹔在 Notification函数执行前所有的定时器服务都已失效,因而在Notificaiton函数中也不能使用定时器服务
**EVT_SIGNAL_VRTUAL_ADDRESS_CHANGE:**它是EVT_RUNTIME_CONTEXT、EVT_RUNTIME、EVT_NOTIFY_SIGNAL和O0x00000002的组合。它不能和这4种类型之外的类型组合使用。当SetVirtualAddressMap()被调用时触发此类事件
2、优先级
#define TPL_APPLICATION 4
#define TPL_CALLBACK 8
#define TPL_NOTIFY 16
#define TPL_HIGH_LEVEL 31
任务优先级 | 用法 | 函数 |
---|---|---|
TPL_APPLICATION | 这是预定义的4个级别中最低的一个优先级。应用程序运行(包括Boot Manager和 OS Loader)在这个级别。当程序运行在这个级别时,任务队列中没有任何处于就绪状态的事件Notification函数 | 下列函数运行在此级别: ExitBootServices()、WaitForEvent()、UserManager Protocol/Identify()、Form Browser2Protocol/SendForm 下列函数运行在此级别或更低级别:Simple Input Protocol |
TPL_CALLBACK | 比较耗时的操作通常在这个优先级执行,如文件系统、磁盘操作等 | 下列函数运行在此级别或更低级别: Exit();Serial l/O Protocol、UnloadImage()、Variable Services、NetWork Service Binding 、vetwork Protocol 下列函数运行在低于8的级别:LoadImage()、StartImage( |
TPL_NOTIFY | 运行在这个级别的程序不允许阻塞,必须尽快执行完毕并且返回。如果需要更多操作,则需要使用Event由内核重新调度。通常,底层的IO操作允许在这个级别,例如UEFI内核中读取键盘状态的代码。大部分Event的Notification函数允许在这个级别 | 下列函数运行在此级别或更低级别: Protocol Handler Services . Memory AllocationServices、Simple Text Output Protocol、ACPI TableProtocol、User Manager Protocol、User CredentialProtocol、User Info Protocol、Authentication Info、Device Path Utilities、Device Path From Text、EDID Discovered、EDID Active、Graphics OutputEDID Override、isCSI Initiator Name、Tape IO、Deferred Image Load Protocol、HII Protocols、Driver Health 下列函数运行在低于16的级别:ACPI Table Protocol |
TPL_HIGH_LEVEL | 优先级最高级别。在此级别,中断被禁止。UEFI内核全局变量的修改需要允许在这个级别 | 下列函数运行在此级别或更低级别:SignalEvent()、Stall( 下列函数运行在低于31的级别: CheckEvent()、CloseEvent()、CreateEvent()、SetTimer()、Event Notification Levels运行在(TPL_APPLICATION,TPL_HIGH_LEVEL)区间的优先级上 |
CreateEvent的第三个参数NotifyFunction是EFI_EVENT_NOTIFY类型的函数指针
typedef vOID (EFIAPI *EFI_EVENT_NOTIFY)(
IN EFI_EVENT Event, //拥有此函数的事件
IN VOID *Context //上下文指针,此指针在CreateEvent时设置
) ;
如果事件的类型是EVT_NOTIFY_WAIT,则EFI_EVENT_NOTIFY函数会在等待此事件的过程中调用;如果事件的类型是EVT_NOTIFY_SIGNAL,则EFI_EVENT_NOTIFY函数会在事件触发时调用。既没有EVT_NOTIFY_WAIT属性也没有EVT_NOTIFY_SIGNAL属性的事件,Notification参数将被忽略
CreateEventEx服务用于生成事件并将事件加入事件组
typedef EFI_STATUS (EFIAPI *EFI_CREATE_EVENT)(
IN UINT32 Type, //事件类型
INEFI_TPL NotifyTpl, //事件Notification函数的优先级
IN EFI_EVENT_NOTIFY NotifyFunction, //事件Notification函数
IN VOID *NotifyContext, //传给事件Notification函数的参数
IN CONST EFI_GUID *EventGroup OPTIONAL, //事件组
OUT EFI_EVENT *Event //生成的事件
);
由CreateEventEx生成的事件会加入到EventGroup中。当EventGroup中的任一事件被触发后,组中的所有其他事件都会被触发,进而同组内所有的Notification 函数都将被加入到待执行队列。同组内NotifyTpl(优先级)高的Notification函数会先被执行
如果输入参数EventGroup为NULL,则CreateEventEx退化为CreateEvent
Type不能是EVT_SIGNAL_EXTT_BOOT_SERVICES或EVT_SIGNAL_VIRTUAL_ADDRESSCHANGE,因为这两种类型有各自对应的Group
检查事件是否处于触发态
typedef EFI_STATUS (EFIAPI *EFT_CHECK_EVENT) ( IN EFT_EVENT Event);
SignalEvent用于将事件的状态设置为触发态。如果事件类型为EVT_NOTIFY_SIGNAL,则将其Notification函数添加到就绪队列准备执行。如果该事件属于一个组,则将该组内所有事件都设置为触发态,并将组内所有EVT_NOTIFY_SIGNAL事件的Notification 函数添加到就绪队列准备执行
typedef EFI_STATUs signalEvent ( IN EFI_EVENT Event);
事件使用完毕后,必须调用CloseEvent 关闭这个事件
typedef EFI_STATUS CloseEvent ( IN EFI_EVENT Event);
//SetTimer
//retval EFI_SUCCESS 属性设置成功
//retval EFT_INVALID_PARAMETER 参数Event不是EVT_TIMER,或参数Type非法
typedef EFI_STATUS (EFIAPI *EFI_SET_TIMER)(
IN EFI_EVENT Event, //Timer事件
IN EFI_TIMER_DELAY Type, //定时器类别
IN UINT64TriggerTime //定时器过期时间,100ns为一个单位
);
Type
Type取值 | 作用 |
---|---|
TimerCancel | 用于取消定时器触发时间。设置后定时器不再触发 |
TimerPeriodic | 重复型定时器。每 TriggerTime*100ns,定时器触发一次 |
TimerRelative | —次性定时器。TriggerTime*100ns 时触发 |
RaiseTPL ( NewTpl)用于提升当前任务的任务优先级至 NewTpl,该函数的返回值为原来的任务优先级。RestoreTPL用于恢复(通常是降低)任务优先级至原来的优先级
RaiseTPL和 RestoreTPL必须成对出现,执行了RaiseTPL后,必须尽快调用RaiseTPL将任务优先级恢复到原来的值
当任务优先级提升至TPL_HIGH_LEVEL 时,将关闭中断。当任务优先级从TPL_HIGH_LEVEL恢复到原来的(比 TPL_HIGH_LEVEL低的)值时,中断被重新打开
在任务优先级恢复到原优先级之前,所有高于原优先级的触发态事件的 Notification函数都要执行完毕
待补
待补
GPT硬盘仍然将硬盘划分为扇区。所有扇区统一编址,地址从О开始编号,扇区地址称为LBA (Logic Block Address)。0号扇区是保护性MBR,用于兼容MBR硬盘;1号扇区是GPT头,描述了GPT硬盘的结构;2~33号分区,共32个扇区,是GPT硬盘的分区表。硬盘末尾的33个分区用于备份分区表和GPT头
0号扇区称为保护性MBR,同MBR硬盘的0号扇区格式相同。其主要作用是兼容MBR硬盘时代的工具,防止这些工具因不识别GPT格式而破坏硬盘。虽然格式与MBR硬盘的0号扇区格式相同,但对各个域有不同的要求:启动代码域(0 ~439字节)对UEFI系统无效,将被UEFI系统忽略;分区表项的第一个项必须包含整个硬盘,并且BootIndicator必须为0,OSType标志为0xEE(此类型称为保护性GPT);其他分区表项必须为0﹔标志位(510 ~511字节)必须为OxAA55;其他字节必须为0
硬盘最后一个扇区用于备份GPT头。当计算机系统读取硬盘GPT头时,会计算表头的CRC32校验码,如果计算出的校验码与硬盘中存储的校验码不一致,则系统会尝试从备份表头恢复GPT头
系统中的每个设备都有一个唯一的路径。例如,每次进入Shell时,都会打印出系统中的硬盘设备及设备路径
待补
因为硬盘是一种块设备,所以每个硬盘设备(硬盘设备包括分区设备)控制器都安装有一个 Blocklo实例、一个 Blocklo2实例。Blocklo提供了访问设备的阻塞函数,Blocklo2提供了访问设备的异步函数
每个硬盘设备控制器还安装一个DiskIo实例、一个 DiskIo2实例。DiskIo提供了访问硬盘的阻塞函数,DiskIo2提供了访问硬盘的异步函数
Blocklo 与 DiskIo 的区别是,BlockIo只能按块(扇区)读写设备,而 DiskIo可以从任意偏移处(以字节为单位)读写磁盘,并且可以读取任意字节数
UEFI也会为每个分区生成一个控制器。如果这个分区的属性Bit 1值为0,则为这个分区控制器安装 Blocklo、BlockIo2、DiskIo、Disklo2
typedef struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision; //Protocol版本号
EFI_BLOCK_IO_MEDIA *Media; //设备信息
EFI_BLOCK_RESET Reset; //重置设备
EFI_BLOCK_READ ReadBlocks; //读扇区
EFI_ BLOCK_WRITEwriteBlocks; //写扇区
EFI_ BLOCK_FLUSH FlushBlocks; //将缓冲中的数据写入扇区
}EFI_BLOCK_IO_ PROTOCOL;
EFI_BLOCK_IO_MEDIA
typedef struct {
UINT32 MediaId;
BOOLEAN RemovableMedia;
BOOLEAN MediaPresent; //设备中是否有介质
BOOLEAN LogicalPartition; //该介质是分区.(TRUE),或者整个设备(FALSE)
BOOLEAN Readonly;
BOOLEAN writecaching; //是否用cache方式写硬盘
UINT32 BlockSize;
UINT32 IoAlign; //读写硬盘时缓冲区地址对齐字节数,0或2”。0和1表示可以是任意地址
EFI_LBA LastBlock; //设备最后一个块的LBA地址
EFI_LBA LowestAlignedLba;
UINT32 LogicalBlocksPerPhysicalBlock;
UINT32 optimalTransferLengthGranularity;
}EFI BLOCK IOMEDIA;
ReadBlocks
ReadBlocks用于读取块设备,它只能按块读取设备。
从块LBA开始读取Buffersize(块大小的整数倍)字节。阻塞操作,读取成功或失败后返回
typedef EFT_STATUS (EFIAPI *EFT_BLOCK_READ)(
IN EFI_BLOCK_IO_PROTOCoL*This,
IN UINT32 MediaId, //设备中的介质号
IN EFI_LBA LBA, //读取设备(或分区)起始块的LBA地址
IN UINTN Buffersize, //读取的字节数,必须是块大小的整数倍
OUT vOID *Buffer //读取的数据存到此缓冲区,调用者负责管理此内存
);
WriteBlocks
WriteBlocks用于按块写设备
typedef EFI_STATUS (EFIAPI *EFI_BLOCK_WRITE)(
IN EFI_BLOCK_IO_PROTOCOL *This,
IN UINT32 MediaId, //设备中的介质号
IN EFI_LBA LBA, //写设备(或分区)起始块的LBA地址
IN UINTN Buffersize, //写字节数,必须是块大小的整数倍
OUT VOID *Buffer //从此缓冲区写数据到设备,调用者负责管理此内存
);
FlushBlocks
FlushBlocks用于将设备缓存中修改过的数据全部更新到介质中
typedef EFI_STATUS(EFIAPI *EFI_BLOCK_FLUSH)(IN EFT_BLOCK_IO_PROTOCOL*This) ;
IO操作通常非常消耗时间。ReadBlocks、WriteBlocks 及 FlushBlocks这种阻塞操作通常会浪费大量时间在等待上。为了提高性能,UEFI提供了异步读写块设备的Protocol,这便是BlockIo2。BlockIo2是EFI_BLOCK_IO2_PROTOCOL的简称
通常异步操作需要事件的支持,还需要上下文用于传递数据。在UEFI规范中,异步操作的事件和上下文封装起来称为令牌(Token),不同的异步操作有不同类型的令牌。一个拥有令牌的异步操作也称为一个事务
typedef struct {
EFT_EVENT Event; //事务对应的事件
EFI_STATUS Transactionstatus; //事务状态(成功/失败)
}EFI_BLOCK_IO2_TOKEN;
ReadBlocksEx
ReadBlockEx函数向设备发出读指令后立刻返回。设备完成操作后会触发Token中的事件。如果函数返回错误,则表示指令没有发送到设备,事件将不会触发。如果Token为NULL或Token->Event为 NULL,则该函数将退化为阻塞函数,功能与Blocklo 的ReadBlocks完全相同。Token由调用者负责创建和删除
typedef EFI_STATUS (EFIAPI *EFI_BLOCK_READ_EX)(
IN EFI BLOCK_IO2_PROTOCOL * This,
IN UINT32 MediaId, //设备中的介质号
IN EFI_LBA LBA, //读取设备(或分区)起始块的LBA地址
IN OUT EFI_BLOCK_I02_TOKEN *Token, //此事务对应的Token,调用者负责生成、关闭它
IN UINTN Buffersize, //读取的字节数,必须是块大小的整数倍
OUT VOID *Buffer //读取的数据存到此缓冲区,调用者负责管理此内存
);
WriteBlocksEx
WriteBlocksEx用于异步写数据到硬盘扇区。代码清单7-14是WriteBlocksEx的函数原型,该函数执行后立刻返回,设备完成操作后会触发Token中的 Event,这个操作也称为一个事务,每个事务都应该有一个Token。如果函数返回错误,则表示指令没有发送到设备,Event将不会触发。如果Token为NULL或Token->Event为NULL,则该函数将退化为阻塞函数,功能与 Blocklo的 WriteBlocks完全相同
typedef EFI_STATUS(EFIAPT *EFI_BLOCK_WRITE_EX)(
IN EFI_ BLOCK_I02_PROTOCOL *This,
IN UINT32 MediaId, //设备中的介质号
IN EFI_LBA LBA, //写设备(或分区)起始块的 LBA地址
IN OUT EFI_BLOCK_IO2_TOKEN *Token, //此事务对应的Token,调用者负责生成、关闭Token
IN UINTN Buffersize, //写字节数,必须是块大小的整数倍
OUT VOID *Buffer //从此缓冲区写数据到设备,调用者负责管理此内存
) ;
FlushBlocksEx
FlushBlocksEx是 FlushBlock的异步版,用于将设备缓存中修改过的数据全部更新到介质中。代码清单7-15展示了FlushBlocksEx函数的原型,该函数执行后立刻返回,设备完成操作后会触发Token中的Event,这个操作也是一种事务,每个事务都应该有一个Token。如果函数返回错误,则表示指令没有发送到设备,Event将不会触发。如果Token为NULL或Token->Event为NULL,则该函数将退化为阻塞函数,功能与Blocklo的FlushBlocks完全相同
typedef EFI_STATUS(EFIAPI *EFI_BLOCK_FLUSH_EX)(
IN EFI_BLOCK_I02_PROTOCoL* This,
IN OUT EFI_BLOCK_I02_TOKEN *Token //事务所拥有的 Token
);
Blocklo提供了按块访问设备的功能。虽然有很好的性能,但也让我们操作磁盘变得烦琐。为了方便对磁盘的操作,UEFI提供了Disklo(EFI_DISK_IO_PROTOCo)利用DiskIo我们可以从磁盘任意地址读写任意长度的数据。DiskIo建立在BlockIo基础之上。硬盘设备和分区设备都会安装一个DiskTo实例
typedef struct _EFI._DISK_IO_PROTOCOL{
UINT64 Revision; //版本号
EFI_DISK_READ ReadDisk; //读磁盘
EFI_DISK_WR工TE writeDisk; //写磁盘
}EFI_DISK_IO_PROTOCOL;
ReadDisk
typedef EFI_STATUS (EFIAPI *EFI_DISK_READ)(
IN EFI_DISK_IO_PROTOCoL * This,
IN UINT32 MediaId, //介质号
IN UINT64Offset, //从偏移offset(以字节为单位)处开始读数据
IN UINTNBufferSize, //读取数据的长度
OUT VOID *Buffer //数据读取到此缓冲区,调用者负责管理此内存
);
WriteDisk
typedef EFI_STATUS (EFIAPI *EFI_DISK_WRITE)(
IN EFI_DISK_IO_PROTOCoL* This,
IN UINT32 MediaId,
IN UINT64 offset,
IN UINTN Buffersize,
IN VOID *Buffer
);
typedef struct_EFI_DISK_I02_PROTOCOL {
UINT64 Revision;
EFIDISK_CANCEL_EX Cancel; //取消磁盘设备上处于等待状态的事务
EFI_DISK_READ_EX ReadDiskEx; //异步读磁盘
EFI_DISK_WRITE_EX writeDiskEx; //异步写磁盘
EFI_DISK_FLUSH_EX FlushDiskEx; //异步Flush磁盘
}EFI_DISK_I02_PROTOCOL;
异步操作需要令牌,DiskIo2中的异步操作也不例外
typedef struct {
EFT_EVENT Event; //事务对应的事件,无论成功失败,都会触发Event
EFI_STATUS Transactionstatus; //事务状态(成功/失败)
}EFI_DISK_IO2_TOKEN;
ReadDiskEx
typedef EFI_STATUS (EFIAPI *EFI_DISK_READ_EX)(
IN EFT_DISK_IO_PROTOCOL * This,
IN UINT32 MediaId, //介质号
IN UINT64 offset, //从offset(以字节为单位)处开始读数据
IN OUT EFI_DISK_Io2_TOKEN *Token,
IN UINTN Buffersize, //读取数据的长度
OUT VOID *Buffer //l/数据读取到此缓冲区,调用者负责管理此内存
);
WriteDiskEx
typedef EFI_STATUS (EFIAPI *EFI_DISK_WRITE_EX)(
IN EFI_ DISK_Io2_PROTOCoL *This,
IN UINT32 MediaId,
IN UINT64 offset,
IN OUT EFI_DISK_IO2_TOKEN *Token,
IN UINTN Buffersize,
IN VOID *Buffer
);
FlushDiskEx
typedef EFI_STATUS(EFIAPI *EFI_DISK_FLUSH_EX)(
IN EFI_DISK_Io2_PROTOCOL *This,
IN OUT EFI_DISK_IO2_TOKEN *Token
);
Cancel
Cancel用于终止所有已经向设备发出但未完成的异步请求
//retval EFI_SUCCESS
//retval EFI_DEVICE_ERROR
typedef EFT_STATUS(EEIAPI *EFT_DISK_CANCEL_EX)(IN EFI_DISK_I02_PROTOCOL *This);
虽然Blocklo和 DiskIo极大地方便了我们操作磁盘,但是其功能十分有限。通过Blocklo和 Disklo,我们只能对硬盘设备进行读、写、Flush操作,如果想对硬盘进行更多的操作,则需要通过PassThrough向硬盘发送命令
UEFI标准中定义了两种 PassThrough :一种用于ATA(Advanced Technology Attachment)硬盘,另一种用于SCSI硬盘和ATAPI(AT Attachment Program Interface)硬盘
待补。。。。。。
通常,每个UEFI系统至少有一个ESP(EFI System Partition)分区,在这个分区上存放了启动文件。既然操作系统加载器以文件的形式仔放在ESP分TA内,UEF就需要有读写文件的功能。文件的读写与管理必须通过文件系统来完成,要文持读与文T,uPr对欢件先能操作ESP上的文件系统。ESP主要用来存放操作系统加载益相大的又Y,四mN人TT系统的要求比较简单,所以采用FAT文件已经可以满足需求。UEFI 内直」EFI_SMAPLE_FILE_SYSTEM_PROTOCOL(简称FileSystemlo)用于操作FAT文件系统
typedef struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL{
UINT64 Revision;
EFT_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME OpenVolume; //打开卷并获得根目录句柄
}EFI_SIMPLE_FILE_SYSTEM_ PROTOCOL;
通过EFI_SIMPLE_FILE_SYSTEM_PROTOCOL中的OpenVolume,我们就可以获得FAT文件系统上的根目录句柄,目录句柄(EFI_FILE_PROTOCOL)包含了操作该目录里文件的文件操作接口
在得到该分区文件系统上的根目录EFI_FILE_PROTOCOL实例的指针后,就可以操作该分区上的文件了
typedef struct_EFI_FILE_PROTOCoL{
UINT64 Revision;
EFI_FILE_OPEN Open;
EFI_FILE_CLOSE Close;
EFI_FILE_DELETE Delete;
EFI_FILE_READ Read;
EFI_FILE_WR工TE write;
EFT_FILE_GET_POSTTION GetPosition;
EFT_FILE_SET_POSITION SetPosition;
EFI_FILE_GET_INFO GetInfo; //获得文件属性
EFI_FILE_SET_INFO SetInfo; //设置文件属性
EFI_FILE_FLUSH Flush; //将文件中修改过的内容全部更新到设备
EFI_FILE_OPEN_EX OpenEx;
EFI_FILE_READ_EX ReadEx;
EFI_FILE_WRITE_EX writeEx;
EFT_FILE_FLUSH_EX FlushEx;
}EFI_FILE_PROTOCOL;
Filelo的Open函数用于打开文件或目录
typedef EFI_STATUS (EFIAPI *EFI_FILE_OPEN)(
IN EFT_FILE_PROTOCOL *This, //EFI_FILE_PROTOCOL实例,通常它是一个目录的句柄
OUT EFI_FILE_PROTOCOL**NewHandle, //返回打开文件的句柄
IN CHAR16 *FileName, //文件名,以 NULL结尾的Unicode字符串
IN UINT64 OpenMode, //打开模式,表明打开文件用于读、写、创建,或者三种的组合
IN UINT64 Attributes //文件属性,仅在产生文件时有效
);
在Open 函数中,This指针是一个目录的句柄,如果FileName指定的文件路径是相对路径(以非\字符开头),那么这个相对路径起始于This对应的目录;如果FileName指定的文件路径是绝对路径(以\开头),那么这个文件路径起始于This所在文件系统的根目录,在FileName路径中,“.”表示当前目录,“…”表示父目录
文件打开模式
OpenMode是打开模式,表示文件用于读、写、创建或者三种的组合
文件打开模式 | 用途 |
---|---|
EFI FILE_MODE_READ | 文件用于读 |
EFI_FILE_MODE_WRITE | 文件用于写 |
EFI_FILE_MODE_CREATE | 若文件不存在,则创建 |
目前,EDK2仅支持三种打开模式,其余的打开模式将会返回EFI_INVALID_PARAMETER。这三种有效的打开模式或组合是:EFI_FILE_MODE_READ、EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE和 EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE|EFI_FILE_MODE_CREATE。创建文件时,可以指定文件属性
文件属性
文件属性 | 功能 |
---|---|
EFI_FILE_READ_ONLY | 只读文件 |
EFI_FILE HIDDEN | 隐藏文件 |
EFI FILE_SYSTEM | 系统文件 |
EFI_FILE_RESERVED | 保留 |
EFI_FILE_DIRECTORY | 目录 |
EFI_FILE_ARCHIVE | 归档文件 |
EFI_FILE_VALID_ATTR | 有效属性位 |
打开成功后,系统为文件(或目录)生成文件对象FAT_IFILE,并返回文件对象中的EFI_FILE_PROTOCOL 指针作为文件句柄。文件对象FAT_IFILE是FAT文件系统的内部数据结构,不会暴露给开发者
typedef EFI_STATUS (EFIAPI *EFI_FILE_READ)(
IN EFI_ FILE_PROToCOL *This, //文件句柄
IN OUT UINTN *Buffersize, //输入:要读出的数据长度;输出:实际读出的数据长度
OUT VOID *Buffer //读缓冲区
);
This指定的句柄可以是文件句柄,也可以是目录句柄。如果This指向文件,则 Read函数将从文件当前位置处读BufferSize个字节到Buffer缓冲区。如果从文件当前位置到文件末尾小于BufferSize,则读至文件末尾。返回后,BufferSize存放实际读取的字节数
typedef EFI_STATUS (EFIAPI *EFI_FILE_WRITE)(
IN EFI_FILE_PROTOCOL*This, //文件句柄
IN OUT UINTN *Buffersize, //输入:要写入的数据长度;输出:实际写入的数据长度
IN VOID *Buffer //待写入数据
);
Write只能写数据到文件,不能写数据到目录。通常,Write函数会写BufferSize指定的字节数到文件中(实际写的字节数等于指定要写的字节数),仅在遇到错误时(例如,卷上没有多余空间时)会写部分数据到文件,此时BufferSize返回实际写的字节数
文件操作完毕后,要关闭文件句柄,Close函数很简单,只有一个参数,即要关闭的文件句柄
typedef EFI STATUS (EFIAPI *EFT FILE CLOSE)(IN EFT_FILE_PROTOCOL *This)
文件的创建、打开、读、写、关闭是比较常用的几种文件操作,除此之外,还有一些其他的文件操作,如设定或读取文件的读写位置,设定或读取文件属性、信息,删除文件,Flush文件等
文件读写位置
文件打开后,系统为文件生成FAT_IFILE数据结构,这个结构中保存了文件位置等信息,每次读写都从当前位置处开始,读写后自动更新FAT_IFILE内的文件位置。EFI_FILE_PROTOCOL提供了GetPosition用于获取文件的当前位置;SetPosition用于设置文件的当前位置
/**GetPosition返回文件的当前读写位置(以字节为单位)
@retval EFI_SUCCESS 成功获取当前位置
@retval EFI_UNSUPPORTED This指向目录句柄,无法获得目录的当前位置
@retval EFI_DEVICE_ERROR This指向的文件已经删除或其他设备错误
**/
typedef EFI_STATUS(EFIAPI *EFI_FILE_GET_POSITION)(
IN EFI_FILE_PROTOCOL*This, //文件句柄
OUT UINT64 * Position //返回文件的当前位置(从文件开头算起)
);
/**SetPosition设置文件的当前读写位置(以字节为单位)
@retval EFI_sUCCESS 成功设置文件位置
@retval EFI_UNSUPPORTED This指向目录句柄,无法设置目录的当前位置
@retval EFI_DEVICE_ERROR This指向的文件已经删除或其他设备错误
**/
typedef EFI_STATUS(EFIAPI *EFI_FILE_SET_POSITION)(
IN EFI_FILE_PROTOCOL *This, //文件句柄
IN UINT64 Position //要设定的文件位置(从文件开头算起)
);
读写文件信息
有时我们希望能得到文件的属性等信息,而不是文件的内容。例如,在读文件之前,我们希望能根据文件大小准备好读缓冲区。EFI_FILE_PROTOCOL提供了GetInfo用于读取文件信息;SetInfo用于设置文件信息
//GetInfo获取文件或文件系统的相关信息
typedef EFI_STATUS(EFIAPI *EFI_FILE_GET_INFO)(
IN EFI_FILE_PROTOCOL *This, //文件(或目录)句柄
IN EFI_GUID *InformationType, //要获取信息的类型标识符
IN OUT UINTN *BufferSize, //输入:缓冲区大小;输出:返回数据的长度
OUT VoID *Buffer //读缓冲区,数据类型由InformationType 决定
);
//SetInfo设置文件(或文件系统)信息
typedef EFI_STATUS(EFIAPI *EFI_FILE_SET_INFO)(
IN EFI_FILE_PROTOCOL *This, //文件(或目录)句柄
IN EFI_GUID * InformationType, //要设置信息的类型标识符
IN UINTN Buffersize, //InformationType指定的数据的字节数
IN VOID *Buffer //InformationType指定的数据
);
InformationType
信息标识 | 数据类型 | GUID |
---|---|---|
EFI_FILE_INFO_ID | EFI_FILE_INFO | gEfiFileInfoGuid |
EFI_FILE_SYSTEM_INFO_ID | EFI_FILE_SYSTEM_INFO | gEfiFileSystemInfoGuid |
EFI_FILE_SYSTEM_VOLUME_LABEL_ID | EFI_FILE_SYSTEM_VOLUME_LABEL | gEfiFileSystemVolumeLabelInfoIdGuid |
删除文件
Delete函数用于删除文件
/**关闭文件句柄(This)并从介质上删除文件
@param This 要删除文件(或目录)的句柄
@retval EFI_suCCESS 成功删除。句柄被关闭
@retval EFI_WARN_DELETE_FAILURE 文件句柄被关闭,但文件没有被删除
**/
typedef EFI_STATUS(EFIAPI *EFI_FILE_DELETE)( IN EFT_FILE_PROTOCOL *This);
Flush文件
Flush用于将文件所有改变的内容更新到介质中
typedef EFT_STATUS(EFIAPI *EFT_FILE_FLUSH)(IN EFI_PILE_PROTOCOL *This);
异步文件操作需提供一个EFI_FILE_ IO_TOKEN类型的令牌
typedef struct {
EFI_EVENT Event; //此事务对应的事件
EFI_STATUS status; //Event触发后,事务的状态
//下面两个域仅用于ReadEx和writeEx,对OpenEx和FlushEx无效
UINTN BufferSize;
VOID *Buffer;
}EFI_FILE_IO_TOKEN;
异步打开
/** OpenEx函数异步打开或创建文件**/
typedef EFI_STATUS (EFIAPI *EFI_FILE_OPEN_EX)(
IN EFT_FILE_PROTOCOL *This, //EFT_FILE_PROTOCOL实例,通常它是一个目录的句柄
OUT EFI_FILE_PROTOCOL **NewHandle, //返回打开文件的句柄
IN CHAR16*FileName, //文件名,以 NULL结尾的Unicode字符串
IN UINT64 OpenMode, //打开模式,表明打开文件用于读、写、创建,或者三种的组合
IN UINT64 Attributes, //文件属性,仅在产生文件时有效
IN OUT EFI_FILE_IO_TOKEN *Token //每个事务都需要一个有效的Token
);
异步读/写文件
ReadEx用于异步读文件;WriteEx用于异步写文件。
//ReadEx函数,用于异步读文件
typedef EFI_STATUS (EFIAPI *EFI_FILE_READ_EX)(
IN EFI_ FILE_ PROTOCOL*This, //文件(或目录)句柄
IN OUT EFI_FILE_IO_TOKEN *Token //事务令牌
);
//writeEx函数,用于异步写数据到文件
Typedef EFI_STATUS(EFIAPI *EFI_FILE_WRITE_EX)(
IN EFI_FILE_PROTOCoL * This, //文件(或目录)句柄
IN OUT EFI_FILE_IO_TOKEN*Token //事务Token
);
异步Flush
//FlushEx函数,用于将指定文件中修改过的内容全部更新到设备
typedef EFI_STATUS (EFIAPI *EFI_FILE_FLUSH_EX)(
INEFI_FILE_PROTOCOL *This, //文件(或目录)句柄
IN OUT EFI_FILE_IO_TOKEN*Token //事务令牌
);
在UEFI中还有一组接口用于操作文件,那便是EFI_SHELL_PROTOCOL提供的文件操作函数。EFI_SHELL_PROTOCOL中的文件操作接口对EFI_FILE_PROTOCOL进行了封装和扩充,除了提供基本的文件访问函数之外,还提供了相对目录的操作方式,以及stdin、stdout、stderr 及文件查找函数。上文讲过,只有Shell环境下运行的程序才能使用EFI_SHELL_PROTOCOL,因而只有Shell下的应用程序才能使用EFI_SHELL_PROTOCOL中的文件操作函数
待补。。。。。。