《UEFI原理与编程》读书笔记

《UEFI原理与编程》读书笔记

读书笔记仅摘取对个人有用的部分,详细内容请阅读原著
目前更新至第七章,因个人仅需要引导程序,故其余部分未介绍,详细内容请阅读原著
书及其资料均已放置在本人资源中,如需下载请移步资源模块
2021-11-11

1、UEFI概述

1.1、BIOS

BIOS全称为“基本输入/输出系统”,主要负责

  • 加电自检程序,用于开机时对硬件的检测
  • 系统初始化代码,包括硬件设备的初始化、创建BIOS中断向量等
  • 基本的外围I/O处理的子程序代码
  • CMOS设置程序(CMOS是主板上的一块可读写的RAM芯片,是用来保存BIOS的硬件配置和用户对某些参数的设定,CMOS可由主板的电池供电,即使系统掉电,信息也不会丢失,CMOSRAM本身只是一块存储器,只有数据保存功能)

BIOS程序运行在16位实模式下,实模式下最大的寻址范围是 1MB,0xOCO000 ~ Ox0FFFFF保留给BIOS使用。开机后,CPU跳到OxOFFFFO处执行,一般这里是一条跳转指令,跳到真正的BIOS入口处执行。BIOS代码首先做的是“加电自检”( Power On Self Test,POST),主要是检测关机设备是否正常工作,设备设置是否与CMOS中的设置一致。如果发现硬件错误,则通过喇叭报警。POST检测通过后初始化显示设备并显示显卡信息,接着初始化其他设备。设备初始化完毕后开始检查CPU和内存并显示检测结果。内存检测通过以后开始检测标准设备,例如硬盘、光驱、串口设备、并口设备等。然后检测即插即用设备,并为这些设备分配中断号、I/O端口和DMA通道等资源。如果硬件配置发生变化,那么这些变化的配置将更新到CMOS中。随后,根据配置的启动顺序从设备启动,将启动设备主引导记录的启动代码通过BIOS 中断读入内存,然后控制权交到引导程序手中,最终引导进入操作系统。

缺点

  • 开发效率低
  • 性能差
  • 功能扩展性差
  • 安全性差
  • 不支持硬盘2TB以上的地址引导

1.2、UEFI

UEFI (Unified Extensible Firmware Interface,统一可扩展固件接口)

UEFI提供给操作系统的接口包括启动服务(Boot Services,BS)和运行时服务(RuntimeService,RT)以及隐藏在BS之后的丰富的Protocol
《UEFI原理与编程》读书笔记_第1张图片
从操作系统加载器( 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提供服务如下

  • 事件服务
  • Protocol管理
  • Protocol使用类服务
  • 驱动管理
  • Image管理
  • 时间服务
  • 读取UEFI系统变量
  • 虚拟内存服务
  • 其他服务

优点

  • 开发效率高
  • 可扩展性高
  • 性能好
  • 安全性高

1.3、UEFI启动过程

SEC(安全验证)→PEI(EFI前期初始化)→DXE(驱动执行环境)

→BDS(启动设备选择)→TSL(操作系统加载前期)

→RT (Run Time)

→AL(系统灾难恢复期)

1.3.1、SEC阶段

《UEFI原理与编程》读书笔记_第2张图片

  • 接受并处理系统启动和重启信号
  • 初始化临时存储区域
  • 作为可信任系统的根
  • 传递系统参数给下一阶段
    • 系统当前状态
    • 可启动固件的地址和大小
    • 临时RAM区域的地址和大小
    • 栈的地址和大小

流程

加电→Reset Vector→SEC入口函数→PEI函数

其中Reset Vector阶段流程为

进入固件入口。
从实模式转换到32位平坦模式(包含模式)。
定位固件中的 BFV (Boot Firmware Volume)。
定位BFV中的SEC映像。
若是64位系统,从32位模式转换到64位模式。
调用SEC入口函数。

1.3.2、PEI阶段

《UEFI原理与编程》读书笔记_第3张图片

  • PEI内核(PEI Foundation):负责PEI基础服务和流程
  • PEIM(PEI Module)派遣器:主要功能是找出系统中的所有PEIM,并根据PEIM之间的依赖关系按顺序执行PEIM。PEI阶段对系统的初始化主要是由PEIM完成的

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

1.3.3、DXE阶段

DXE (Driver Execution Environment)阶段执行大部分系统初始化工作,进入此阶段时,内存已经可以被完全使用,因而此阶段可以进行大量的复杂工作

DXE提供的基础服务包括系统表、启动服务、Run Time Services
《UEFI原理与编程》读书笔记_第4张图片

  • DXE内核:负责DXE基础服务和执行流程
  • DXE 派遣器:负责调度执行DXE驱动,初始化系统设备

DXE驱动之间通过Protocol通信。Protocol是一种特殊的结构体,每个Protocol对应一个GUID,利用系统BootServices的 OpenProtocol,并根据GUID来打开对应的Protocol,进而使用这个Protocol提供的服务。
当所有的Driver都执行完毕后,系统完成初始化,DXE通过EFI_BDS_ARCH_PROTOCOL找到BDS并调用BDS的人口函数,从而进入BDS阶段。从本质上讲,BDS是一种特殊的DXE阶段的应用程序。

1.3.4、BDS阶段

  • 初始化控制台设备

  • 加载必要的设备驱动

  • 根据系统设置加载和执行启动项

如果加载启动项失败,系统将重新执行DXE dispatcher 以加载更多的驱动,然后重新尝试加载启动项

BDS策略通过全局NVRAM变量配置。这些变量可以通过运行时服务的GetVariable()读取,通过SetVariable()设置

用户选中某个启动项(或系统进入默认的启动项)后,OS Loader启动,系统进入 TSL 阶段

1.3.5、TSL阶段

TSL ( Transient System Load)是操作系统加载器(OS Loader)执行的第一阶段,在这一阶段OS Loader作为一个UEFI应用程序运行,系统资源仍然由UEFI内核控制。当启动服务的ExitBootServices()服务被调用后,系统进入 Run Time阶段。

TSL阶段之所以称为临时系统,在于它存在的目的就是为操作系统加载器准备执行环境。虽然是临时系统,但其功能已经很强大,已经具备了操作系统的雏形,UEFI Shell是这个临时系统的人机交互界面。正常情况下,系统不会进入UEFI Shell,而是直接执行操作系统加载器,只有在用户干预下或操作系统加载器遇到严重错误时才会进入 UEFI Shell。

1.3.6、RT阶段

系统进入RT (Run Time)阶段后,系统的控制权从UEFI内核转交到OS Loader手中,UEFI占用的各种资源被回收到OS Loader,仅有UEFI运行时服务保留给OS Loader和 OS使用。随着OS Loader的执行,OS最终取得对系统的控制权。

1.3.7、AL阶段

在RT阶段,如果系统(硬件或软件)遇到灾难性错误,系统固件需要提供错误处理和灾难恢复机制,这种机制运行在AL ( After Life)阶段。UEFI 和UEFI PI标准都没有定义此阶段的行为和规范

2、开发环境的搭建

详情请阅读《UEFI原理与编程》或DellaOS引导程序篇:https://blog.csdn.net/gyfghh/article/details/121261968

3、UEFI——工程模块

模块类型 说明
标准应用模块 在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阶段

3.1、标准应用程序工程模块

标准应用程序工程模块是其他应用程序工程模块的基础,也是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;
}
  • 头文件:所有的UEFI程序都要包含头文件Uefi.h,Uefi.h定义了UEFI基本数据类型及核心数据结构
  • 入口函数:UEFI标准应用程序的入口函数通常是UefiMain,入口函数可有开发者指定。UefiMain只是一个约定俗成的函数名。入口函数由工程文件UefiMain.inf指定。虽然入口函数的函数名可以变化,但其函数签名(即返回值类型和参数列表类型)不能变化。
    • EFI_STATUS:在UEFI程序中基本所有的返回值类型都是EFI_STATUS,它本质上是无符号长整数
    • EFI SUCCESS为预定义常量,其值为0,表示没有错误的状态值或返回值
    • .efi文件(UEFI应用程序或UEFI驱动程序)加载到内存后生成的对象称为Image(映像)。ImageHandle是 Image对象的句柄,作为模块入口函数参数,它表示模块自身加载到内存后生成的Image对象
    • SystemTable是程序同UEFI内核交互的桥梁,通过它可以获得UEFI提供的各种服务,如启动(BT)服务和运行时(RT)服务。SystemTable是UEFI内核中的一个全局结构体

向标准输出设备打印字符串是通过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]

3.2、块

  • 必需块
必需块 块描述
[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变量仅本模块可用

3.2.1、[Defines]块

语法格式

属性名 = 属性值

必选属性

变量名 功能描述
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 模块的入口函数

3.2.2、[Sources]块

语法格式

块内每一行表示一个文件,文件使用相对路径,根路径是工程文件所在的目录

示例1

[Sources]
  UefiMain.c

示例2

[Sources]
  UefiMain.c
[Sources .IA32]
	Cpu32.c
[Sources .x64]
	Cpu64.c

3.2.3、[Packages]块

语法格式

块内每一行列出一个文件,文件使用相对路径,若[Sources]块内列出了源文件,则在[Packages]块必须列出MdePkg/MdePkg.dec,并将其放在本块的首行

示例

[Packages]
  MdePkg/MdePkg.dec

3.2.4、[LibraryClasses]块

语法格式

块内每一行声明一个要链接的库
应用程序工程模块必须链接UefiApplicationEntryPoint库;驱动模块必须链接UefiDriverEntryPoint库

示例

[LibraryClasses]  UefiApplicationEntryPoint

3.3、编译运行

可转至DellaOS引导程序篇——UEFI下的 ”Hello,world!“

3.4、gST 、gBS、gImageHandle

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;
}

4、UEFI中的Protocol

要使用Protocol,首先要根据GUID找到Protocol对象,启动服务提供了OpenProtocol(…)、HandleProtocl(…)和LocateProtocol(…)三种服务用于找出指定的Protocol。OpenProtocol用于打开指定句柄上的Protocol。HandleProtocol是OpenProtocol的简化版。LocateProtocol用于找出指定的Potocol在系统中的第一个实例。使用完Protocol后还要关闭打开的 Protocol,否则可能会造成内存泄漏

4.1、OpenProtocol

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的方式
);

4.2、HandleProtocl

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

);

4.3、LocateProtocol

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);

4.4、LocateHandleBuffer和LocateHandle

有时候开发者需要找出支持某个Protocol的所有设备,例如找出系统中的所有安装了BlockIo的设备,LocateHandleBuffer和LocateHandle服务提供了这个功能

待补

4.5、CloseProtocol

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							//控制器的句柄
);

5、UEFI基础服务

作为操作系统和硬件之间的接口,UEFI的最主要功能就是为操作系统加载器准备软件和硬件资源,接口是以启动服务和运行时服务的形式提供给操作系统和UEFI用在厅使用的。应用程序只有通过系统表才能获得启动服务和运行时服务。得到了系统表、启动服务和运行时服务也就控制了整个计算机系统。

5.1、系统表

对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
);

5.1.1、系统表的构成

  • 表头:包括表的版本号、表的CRC校验码等
  • 固件信息:包括固件开发商名字字符串及固件版本号
  • 标准输入控制台、标准输出控制台、标准错误控制台
  • 启动服务表
  • 运行时服务表
  • 系统配置表
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;

5.1.2、使用系统表

  • SystemTable->BootServices指向系统的启动服务表
  • SystemTable->Conln指向安装在标准输入设备上的EFI_SIMPLE_TEXT_NPUT.PROTOCOL
  • SystemTable->ConOut指向安装在标准输出设备上的EFI_SIMPLE_TEXT_OUTPUTPROTOCOL(简称 ConOut)

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;
}

5.2、启动服务

在系统启动过程中,系统资源通过启动服务提供的服务来管理。系统进入DXE阶段时启动服务表被初始化,最终通过SystemTable指针将启动服务(BS)表传递给UEFI应用程序或驱动程序。UEFI应用程序和驱动程序可以通过gST->BootServices或gBS访问启动服务表。

启动服务是UEFI的核心数据结构,有了它,我们才可以使用计算机系统内的资源,它提供的服务可以分为以下几类。

  • UEFI事件服务:事件是异步操作的基础。有了事件的支持,才可以在UEFI系统内执行并发操作
  • 内存管理服务:主要提供内存的分配与释放服务,管理系统内存映射
  • Protocol管理服务:提供了安装Protocol与卸载Protocol的服务,以及注册Protocol通知函数(该函数在 Protocol安装时调用)的服务
  • Protocol使用类服务:包括Protocol的打开与关闭,查找支持Protocol的控制器。驱动管理服务:包括用于将驱动安装到控制器的connect服务,以及将驱动从控制器上卸载的disconnect服务
  • Image管理服务:此类服务包括加载、卸载、启动和退出UEFI应用程序或驱动。ExitBootServices:用于结束启动服务,此服务成功返回后系统进入RT期。
  • 其他服务

启动服务也称启动服务表,它由UEFI表头和表项组成。表中每一项是一个函数指针,这个函数用于提供一项服务。开发UEFI应用和驱动离不开启动服务,深入理解启动服务提供的每一个服务是开发者一项必不可少的任务。上文提到过,启动服务中的服务大致可以分为8类:UEFI事件服务、内存管理服务、Protocol管理服务、Protocol使用类服务、驱动管理服务、Image管理服务、ExitBootServices 及其他服务。本节将简单介绍启动服务中的每一类服务的作用,并详细介绍内存管理相关的服务。

5.2.1、UEFI事件服务

  • 事件(Event):事件服务用于产生、关闭、触发、等待事件和检查事件状态
  • 定时器(Timer):定时器服务用于设置定时器属性
  • 任务优先级(TPL):任务优先级服务用于提升、降低当前程序的优先级

5.2.2、启动服务——UEFI事件服务

函数名 作用
CreateEvent 生成一个事件对象
CreateEventEx 生成一个事件对象并将该事件加入到一个组内
CloseEvent 关闭事件对象
SignalEvent 触发事件对象
WaitForEvent 等待事件数组中的任一事件被触发
CheckEvent 检查事件状态
SetTimer 设置定时器属性
RaiseTPL 提升任务优先级
RestoreTPL 恢复任务优先级

5.2.3、启动服务——内存管理

内存管理服务 作用
AllocatePool 分配内存
FreePool 释放内存
GetMemoryMap 获得当前内存映射(物理地址<→虚地址)
AllocatePages 分配内存页
FreePages 释放内存页

5.2.4、启动服务——Protocol管理服务

Protocol管理服务 作用
InstallProtocolInterface 安装 Protocol到设备上
UninstallProtocolInterface 从设备上卸载Protocol
ReinstallProtocolInterface 重新安装Protocol
RegisterProtocolNotify 为指定的Protocol注册通知事件,当这个Protocol安装时,该事件触发
InstallMultipleProtocolInterfaces 安装多个Protocol到设备上
UninstallMultipleProtocolInterfaces 从设备上卸载多个Protocol

5.2.5、启动服务——Protocol使用类服务

Protocol使用类服务 作用
OpenProtocol 打开 Protocol
HandleProtocol 打开 Protocol,OpenProtocol的简化版
LocateProtocol 找出系统中指定Protocol的第一个实例
LocateHandleBuffer 找出支持指定Protocol的所有Handle。系统负责分配内存,调用者负责释放内存
LocateHandle 找出支持指定Protocol的所有Handle,调用者负责分配和释放内存
OpenProtocolInformation 返回指定Protocol的打开信息
ProtocolsPerHandle 找出指定Handle 上安装的所有Protocol
CloseProtocol 关闭Protocol
LocateDevicePath 在指定的设备路径下找出支持给定Protocol的设备,并返回离指定的设备路径最近的设备

5.2.6、启动服务——驱动管理服务

驱动管理服务 作用
ConnectController 将驱动安装到指定的设备控制器
DisconnectController 将驱动从指定的设备控制器卸载

5.2.7、启动服务——Image管理服务

lmage管理服务 作用
LoadImage 加载.efi文件至内存并生成Image
StartImage 启动Image,也就是调用Image的入口函数
Exit 退出Image
UnloadImage 卸载Image

5.2.8、ExitBootServices

服务名 作用
ExitBootServices 结束启动服务

5.2.9、其他服务

服务名 作用
InstallConfigurationTable 管理(增加、更新、删除)系统配置表项
GetNextMonotonicCount 获得系统单调计数器的下一个值
Stall 暂停CPU指定的微秒数
SetWatchdogTimer 设置“看门狗”定时器,即在指定的时间内若系统无反应,则重启系统
CalculateCrc32 计算CRC32校验码
CopyMem 复制内存
SetMem 设置指定内存区域的值

5.3、内存管理服务详细介绍

内存管理服务的5个服务可以分为3组

  • AllocatePool/FreePool

    • 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										//待释放内存
    );
    
    • EFI_MEMORY_TYPE
    数值 类型名 功能描述
    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

    • AllocatePool用于分配指定大小的内存。内核和驱动开发中经常会要求分配到的内存不得跨页,或需要分配完整的内存页,此时用AllocatePool就十分不方便。为此,启动服务提供了分配页面的服务AllocatePages
    //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										//要释放的页面数
    );
    
    • EFI_ALLOCATE_TYPE
    typedef enum {
        AllocateAnyPages,									//分配任何可用的页面			
        AllocateMaxAddress,									//分配到的页面的末地址必须不超过指定的地址
        AllocateAddress,									//分配指定物理地址上的页面
        MaxAllocateType
    }EFI_ALLOCATE_TYPE;
    
  • GetMemoryMap

    • GetMemoryMap用于取得系统中所有的内存映射,其函数原型如代码清单5-11所示。参数 MemoryMapSize作为输入参数时表示缓冲区MemoryMap字节数,作为输出参数时,若缓冲区不够大,返回需要的缓冲区大小;若缓冲区足够大,返回写入MemoryMap的字节数,输出缓冲区MemoryMap将返回EFI_MEMORY_DESCRIPTOR 数组
    //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版本
    );
    
    • EFI_MEMORY_DESCRIPTOR
    typedef struct {
        UINT32 Type;										//EFI_MEMORY_TYPE,内存类型
        EFI_PHYSICAL_ADDRESS Physicalstart;					//首字节物理地址,必须按4KB对齐
        EFI_VIRTUAL_ADDRESS virtualstart;					//首字节的虚拟地址,必须按4KB对齐
        UINT64 NumberofPages;								//此区域的页面数
        UINT64 Attribute;									//内存属性
    }EFI_MEMORY_DESCRIPTOR;
    

5.4、启动服务的生命周期

从启动服务可以看出其功能之强大,通过它可以完全掌控整个计算机系统,其功能与操作系统提供的功能有很多重叠之处。同时它也占用了大量的计算机系统资源。设计启动服务的主要目的是帮助操作系统加载器初始化计算机系统,当操作系统加载器彻底取得对计算机系统的控制权之后,启动服务也就完成了它的使命,启动服务占用的系统资源也需要转交给操作系统加载器。此时需要调用gBS->ExitBootServices,其作用正是结束启动服务、释放启动服务占用的资源以及使用启动服务分配到的启动期资源,将控制权交给操作系统加载器。也就是说,启动服务的生存期在 DxeMain与gBS->ExitBootServices之间。

5.5、运行时服务

从进入DXE 阶段运行时服务被初始化,直到操作系统结束,运行时服务都一直存在并向上层(操作系统、操作系统加载器、UEFI应用程序或UEFI驱动)提供服务。

运行时服务(RT)也称为运行时服务表,其结构同启动服务表类似,由表头和表项组成。每一个表项是一个函数指针,表示一个服务。

  • 时间服务:读取/设定系统时间。读取/设定系统从睡眠中唤醒的时间
  • 读写系统变量:读取/设置系统变量,例如 BootOrder用于指定启动项顺序
  • 虚拟内存服务:将物理地址转换为虚拟地址
  • 其他服务:包括重启系统的ResetSystem、获取系统提供的下一个单调单增值等

5.5.1、GetTime/SetTime

//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

  • Resolution(时钟分辨率)是实时时钟每秒报告的次数。实时时钟每报告一次输出一个时钟脉冲。RTC (Real-Time Clock)设备的分辨率通常为1Hz,即每一秒钟报告一次
  • Accuracy是时钟守时(timekeeping)精度,以百万分之一(ppm)为单位。实时时钟通常采用32.768KHz的晶体振荡器,其守时精度是20ppm。一天有24×60x60=86400秒,20ppm的误差意味着一天的误差是86400 × ( 20/1000000 ) =1.728秒
  • SetsToZero是布尔类型,True表示设置时钟的操作会清除(低于时钟分辨率的计数)状态;False表示设置时钟的操作不会清除(低于时钟分辨率的计数)计数状态。通常PC-AT CMOS RTC设备的值为False。例如,对于1Hz的RTC,如果SetsToZero为Ture,调用SetTime后1秒产生下一次报告;如果SetsToZero为False,调用SetTime不会影响产生下一次报告的时间

5.5.2、GetWakeupTime/SetWakeupTime

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									//启用时,定时器的设置
);

5.5.3、系统变量服务

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
);

5.5.4、虚拟内存服务

待补

6、事件

UEFI不再支持中断(准确地说,UEFI不再为开发者提供中断支持,但在UEFI内部还是使用了时钟中断),所有的异步操作都要通过事件(Event)来完成。事件的重要性是不言而喻的

函数名 作用
CreateEvent 生成一个事件对象
CreateEventEx 生成一个事件对象并将该事件加入到一个组内
CloseEvent 关闭事件对象
SignalEvent 触发事件对象
WaitForEvent 等待事件数组中的任一事件被触发
CheckEvent 检查事件状态
SetTimer 设置定时器属性
RaiseTPL 提升任务优先级
RestoreTPL 恢复任务优先级

6.1、事件函数

6.1.1、WaitForEvent

等待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 只等待一定的时间,则需要在事件等待数组加入定时器事件

6.1.2、CreateEvent

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)区间的优先级上

6.1.3、Notification

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参数将被忽略

6.1.4、CreateEventEx

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

6.1.5、CheckEvent

检查事件是否处于触发态

typedef EFI_STATUS (EFIAPI *EFT_CHECK_EVENT) ( IN EFT_EVENT Event);
  • 如果事件是EVT_NOTIFY_SIGNAL类型,则返回EFI_INVALID_PARAMETER
  • 如果事件处于触发态,则返回EFI_SUCCESS,并且在返回前,事件重置为非触发态
  • 如果事件处于非触发态并且事件无Notification函数,则返回EFI_NOT_READY
  • 如果事件处于非触发态并且事件有Notification 函数(此事件只可能是EVT_NOTIFY_WAIT类型),则执行Notification函数。然后,检查事件状态标志,若事件处于触发态,则返回EFI_SUCCESS,否则返回EFI_NOT_READY

6.1.6、SignalEvent

SignalEvent用于将事件的状态设置为触发态。如果事件类型为EVT_NOTIFY_SIGNAL,则将其Notification函数添加到就绪队列准备执行。如果该事件属于一个组,则将该组内所有事件都设置为触发态,并将组内所有EVT_NOTIFY_SIGNAL事件的Notification 函数添加到就绪队列准备执行

typedef EFI_STATUs signalEvent ( IN EFI_EVENT Event);

6.1.7、CloseEvent

事件使用完毕后,必须调用CloseEvent 关闭这个事件

typedef EFI_STATUS CloseEvent ( IN EFI_EVENT Event);

6.2、定时器事件

//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 时触发
  • 如果Type为TimerPeriodic并且 TriggerTime是0,则定时器每个时钟滴答触发一次
  • 如果Type为TimerRelative并且TriggerTime是0,则定时器在下个时钟滴答触发

6.3、任务优先级

6.3.1、提升和恢复任务优先级

RaiseTPL ( NewTpl)用于提升当前任务的任务优先级至 NewTpl,该函数的返回值为原来的任务优先级。RestoreTPL用于恢复(通常是降低)任务优先级至原来的优先级

RaiseTPL和 RestoreTPL必须成对出现,执行了RaiseTPL后,必须尽快调用RaiseTPL将任务优先级恢复到原来的值

当任务优先级提升至TPL_HIGH_LEVEL 时,将关闭中断。当任务优先级从TPL_HIGH_LEVEL恢复到原来的(比 TPL_HIGH_LEVEL低的)值时,中断被重新打开

在任务优先级恢复到原优先级之前,所有高于原优先级的触发态事件的 Notification函数都要执行完毕

6.3.2、UEFI中的时钟中断

待补

6.3.2、UEFI事件Notification函数的派发

待补

7、硬盘和文件系统

7.1、MBR分区与GPT分区

《UEFI原理与编程》读书笔记_第5张图片
GPT硬盘仍然将硬盘划分为扇区。所有扇区统一编址,地址从О开始编号,扇区地址称为LBA (Logic Block Address)。0号扇区是保护性MBR,用于兼容MBR硬盘;1号扇区是GPT头,描述了GPT硬盘的结构;2~33号分区,共32个扇区,是GPT硬盘的分区表。硬盘末尾的33个分区用于备份分区表和GPT头

7.1.1、保护性MBR

0号扇区称为保护性MBR,同MBR硬盘的0号扇区格式相同。其主要作用是兼容MBR硬盘时代的工具,防止这些工具因不识别GPT格式而破坏硬盘。虽然格式与MBR硬盘的0号扇区格式相同,但对各个域有不同的要求:启动代码域(0 ~439字节)对UEFI系统无效,将被UEFI系统忽略;分区表项的第一个项必须包含整个硬盘,并且BootIndicator必须为0,OSType标志为0xEE(此类型称为保护性GPT);其他分区表项必须为0﹔标志位(510 ~511字节)必须为OxAA55;其他字节必须为0
《UEFI原理与编程》读书笔记_第6张图片

7.1.2、GPT头

《UEFI原理与编程》读书笔记_第7张图片
硬盘最后一个扇区用于备份GPT头。当计算机系统读取硬盘GPT头时,会计算表头的CRC32校验码,如果计算出的校验码与硬盘中存储的校验码不一致,则系统会尝试从备份表头恢复GPT头

7.2、设备路径

系统中的每个设备都有一个唯一的路径。例如,每次进入Shell时,都会打印出系统中的硬盘设备及设备路径

待补

7.3、硬盘相关的Protocol

因为硬盘是一种块设备,所以每个硬盘设备(硬盘设备包括分区设备)控制器都安装有一个 Blocklo实例、一个 Blocklo2实例。Blocklo提供了访问设备的阻塞函数,Blocklo2提供了访问设备的异步函数

每个硬盘设备控制器还安装一个DiskIo实例、一个 DiskIo2实例。DiskIo提供了访问硬盘的阻塞函数,DiskIo2提供了访问硬盘的异步函数

Blocklo 与 DiskIo 的区别是,BlockIo只能按块(扇区)读写设备,而 DiskIo可以从任意偏移处(以字节为单位)读写磁盘,并且可以读取任意字节数

UEFI也会为每个分区生成一个控制器。如果这个分区的属性Bit 1值为0,则为这个分区控制器安装 Blocklo、BlockIo2、DiskIo、Disklo2

7.3.1、Blocklo

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) ;

7.3.2、Blocklo2

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
);

7.3.3、DiskIo

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
);

7.3.4、DiskIo2

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);

7.3.5、PassThrough

虽然Blocklo和 DiskIo极大地方便了我们操作磁盘,但是其功能十分有限。通过Blocklo和 Disklo,我们只能对硬盘设备进行读、写、Flush操作,如果想对硬盘进行更多的操作,则需要通过PassThrough向硬盘发送命令

UEFI标准中定义了两种 PassThrough :一种用于ATA(Advanced Technology Attachment)硬盘,另一种用于SCSI硬盘和ATAPI(AT Attachment Program Interface)硬盘

待补。。。。。。

7.4、文件系统

通常,每个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实例的指针后,就可以操作该分区上的文件了

7.5、文件操作

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;

7.5.1、打开文件

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文件系统的内部数据结构,不会暴露给开发者

7.5.2、读文件

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存放实际读取的字节数

7.5.3、写文件

typedef EFI_STATUS (EFIAPI *EFI_FILE_WRITE)(
    IN EFI_FILE_PROTOCOL*This,							//文件句柄
    IN OUT UINTN *Buffersize,							//输入:要写入的数据长度;输出:实际写入的数据长度
    IN VOID *Buffer										//待写入数据
);

Write只能写数据到文件,不能写数据到目录。通常,Write函数会写BufferSize指定的字节数到文件中(实际写的字节数等于指定要写的字节数),仅在遇到错误时(例如,卷上没有多余空间时)会写部分数据到文件,此时BufferSize返回实际写的字节数

7.5.4、关闭文件

文件操作完毕后,要关闭文件句柄,Close函数很简单,只有一个参数,即要关闭的文件句柄

typedef EFI STATUS (EFIAPI *EFT FILE CLOSE)(IN EFT_FILE_PROTOCOL *This)

7.5.5、其他文件操作

文件的创建、打开、读、写、关闭是比较常用的几种文件操作,除此之外,还有一些其他的文件操作,如设定或读取文件的读写位置,设定或读取文件属性、信息,删除文件,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);

7.5.6、异步文件操作

异步文件操作需提供一个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					//事务令牌
);

7.5.7、EFI_SHELL_PROTOcoL中的文件操作

在UEFI中还有一组接口用于操作文件,那便是EFI_SHELL_PROTOCOL提供的文件操作函数。EFI_SHELL_PROTOCOL中的文件操作接口对EFI_FILE_PROTOCOL进行了封装和扩充,除了提供基本的文件访问函数之外,还提供了相对目录的操作方式,以及stdin、stdout、stderr 及文件查找函数。上文讲过,只有Shell环境下运行的程序才能使用EFI_SHELL_PROTOCOL,因而只有Shell下的应用程序才能使用EFI_SHELL_PROTOCOL中的文件操作函数

待补。。。。。。

你可能感兴趣的:(读书笔记,UEFI)