UEFI原理与编程(十):UEFI的基础服务-系统表

UEFI的基础服务-系统表

一、前言

  对UEFI应用程序和驱动程序开发人员来讲,系统表是最重要的数据结构之一,它是用户空间通往内核空间的通道。有了它,UEFI应用程序和驱动才可以访问UEFI内核、硬件资源和I/O设备。
  (1)在应用程序和驱动中访问系统表
  计算机系统进入DXE阶段后系统表被初始化,因而系统表只能用于DXE阶段以及以后的应用程序和驱动中。系统表是UEFI内核的一个全局结构体,其指针作为程序映像(Image)入口函数的参数传递到用户空间。程序映像(包括UEFI应用程序、DXE驱动程序以及UEFI驱动程序)的入口函数有统一的格式,其函数原型如下:

typedef
EFI_STATUS
(EFIAPI *EFI_IMAGE_ENTRY_POINT) (
    IN EFI_HANDLE ImageHandle,        //程序映像(Image)的句柄
    IN EFI_SYSTEM_TABLE *SystemTable  //系统表指针
);
返回值描述
EFI_SUCCESSThe driver was initialized
EFI_OUT_OF_RESOURCESThe request could not be completed due to a lack of resources

  (2)系统表指针从内核传递到用户空间的过程
  通常,程序映像的入口函数是_ModuleEntryPoint(当一个Image被启动服务的StartImage服务启动时,执行的就是这个入口函数)。当应用程序或驱动加载到内存形成Image后(ImageHandle是这个Image的句柄),_ModuleEntryPoint函数地址被赋值给Image对象的EntryPoint,然后Image->EntryPoint(ImageHandle,SystemTable)会被执行,最终会从Image的入口函数_ModuleEntryPoint执行到模块的入口函数(模块的入口函数是通过.inf文件中ENTRY_POINT指定的那个函数)。

二、系统表的构成

  系统表可分为如下6部分:

  • 表头:包括表的版本号、表的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;
    EFI_HANDLE  StandardErrorHandle; //标准错误控制台设备
    EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *StdErr;
    EFI_RUNTIME_SERVICES  *RuntimeServices; //运行时服务表
    EFI_BOOT_SERVICES  *BootServices; //启动时服务表
    UINTN  NumberOfTableEntries;  // CongratulationTable数组的大小
    EFI_CONFIGURATION_TABLE  *ConfigurationTable; //系统配置表数组
} EFI_SYSTEM_TABLE;

  简要介绍系统表重要组成部分:
  (1) 表头 EFI_TABLE_HEADER
  UEFI中的表通常都是以EFI_TABLE_HEADER开头,EFI_TABLE_HEADER的数据结构如下:

typedef struct {
    UINT64 Signature;
    UINT32 Revision;
    UINT32 HeaderSize;
    UINT32 CRC32;
    UINT32 Reserved;
} EFI_TABLE_HEADER;

  UEFI中的Signature为64位的无符号整数,为了帮助开发者的使用,EDK2提供了宏SIGNATURE_64(A,B,C,D,E,F,G,H),它用于将ASCII码串转化为64位的无符号整数。例如,EFI_SYSTEM_TABLE的Signature为SIGNATURE_64(‘I’,’B’,’I’,’ ‘,’S’,’Y’,’S’,’T’)。
  HeaderSize是整个表的长度,对系统表来讲,就是sizeof(EFI_SYSTEM_TABLE)。
  CRC32是标的校验码。计算CRC32校验码时,首先将数据结构中CRC32域清零,然后计算整张表(表大小为HeaderSize)的CRC32码,计算后将校验码填充到CRC32域。
  (2)标准的输入控制台、标准的输出控制台和标准错误控制台。
  系统表还提供了三个控制台设备以及操作三个控制台的Protocol。ConIn用于从输入控制台ConsoleInHandle读取字符,通常输入控制台为键盘。ConOut用于向输出控制台ConsoleOutHandle输出字符串,通常输出控制台为屏幕。stdErr用于向标准错误控制台StandardErrorHandle输出字符串,通常这个标准错误控制台为屏幕。这三个控制台设备以及ConIn、ConOut、stdErr三个Protocol在驱动ConSplitterDxe中被初始化。
  (3)系统配置表
  ConfigurationTable是系统配置表,它指向EFI_CONFIGURATION_TABLE数组,数组中的每一项是一个表。
  EFI_CONFIGURATION_TABLE的数据结构:

typedef struct{
    EFI_GUID VendorGuid; //配置表标识符
    VOID *VendorTable;   //指向配置表的数据
} EFI_CONFIGURATION_TABLE;

  例如,ConfigurationTable通常会包含APCI(Advance Configuration and Power Interface)表,ACPI在系统配置表中可以表示为:{gEfiAcpiTableGuid,EFI_ACPI_3_0_ROOT_SYSTEM_DESCRIPTION_POINTER*}

三、使用系统表

  系统表是UEFI内核的全局数据结构,应用程序运行在用户空间。那么用户空间是如何获得内核空间的系统表指针的?其实在UEFI中只有一个地址空间,所有的程序都运行在RING0优先级,应用程序地址空间占用UEFI地址空间的一部分。既然用户空间和内核空间是一个整体,在应用程序内也就可以直接使用内核空间的任何地址了,那么在应用程序内,只要得到了系统表的地址,就可以使用系统表了。
  (1)在用户空间使用系统表
  系统表的地址可以通过模块的入口函数的参数得到。

#include<Uefi.h>
EFI_Status
UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable)
{
    EFI_STATUS Status;
    UINTN Index;
    EFI_INPUT_KEY Key;
    CHAR16 StrBuffer[3] = {0};
    SystemTable->BootServices->WaitForEvent(1,&SystemTable->ConIn->WaitForKey,&Index);
    Status = SystemTable->ConIn->ReadKeyStroke(SystemTable->ConIn,&Key);
    StrBuffer[0] = Key.UnicodeChar;
    StrBuffer[1] = '\n';
    SystemTable->ConOut->OutputString(SystemTable->ConOut,StrBuffer);
    return EFI_SUCCESS;
}

代码解析:

  • SystemTable->BootServices指向系统的启动服务表。
  • SystemTable->ConIn 指向安装在标准输入设备上的EFI_SIMPLE_TEXT_INPUT_PROTOCOL.
  • SystemTable->ConOut 指向安装在标准输出设备上的EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.
      该示例首先用WaitForEvent等待键盘事件,然后调用ConIn的ReadKeyStroke读取键盘,最后调用ConOut的OutputString服务将按键显示到屏幕。
      启动服务BootServices提供的EFI_STATUS WaitForEvent(IN UINTN NumberOfEvents, IN EFI_EVENT * Event,OUT UINTN *Index)服务用于等待Event数组中任一事件的发生。Event数组(NumberOfEvent是数组大小)中任一事件被触发时该函数返回,Index返回被触发事件在Event数组的下标。该函数是阻塞函数。
      EFI_STATUS ReadKeyStroke(EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,OUTEFI_INPUT_KEY *Key)用于读取按键,它是ConInProtocol的成员函数,第一个参数是This指针,第二个参数用于返回按键。
      EFI_STATUS OutputString(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,CHAR16 String)用于向屏幕打印字符串,它是ConOut Protocol的成员函数,第一个参数同样是This指针,第二个参数是打印到屏幕的字符串。
      (2)在用户空间使用gST访问系统表
      上面(1)中的示例中的模块入口函数UefiMain中使用传入参数SystemTable访问系统表。EDK2为了方便开发者,提供了UefiBootServicesTableLib,在UefiLib定义了全局变量gST(指向SystemTable),gBS(指向SystemTable->BootServices)和gImageHandle(ImageHandle)。这三个全局变量在函数UefiBootServicesTableLibConstructor中被初始化,这个函数是库UefiBootServicesTableLib的构造函数,在AutoGen.c中的ProcessLibraryConstructorList被调用。而ProcessLibraryConstructorList是在UefiMain之前被调用的。
      构造函数UefiBootServicesTableLibConstructor源码:
///\EDK2\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c
EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
      //
      // Cache the Image Handle
      //
      gImageHandle = ImageHandle;
      ASSERT (gImageHandle != NULL);

      //
      // Cache pointer to the EFI System Table
      //
      gST = SystemTable;
      ASSERT (gST != NULL);

      //
      // Cache pointer to the EFI Boot Services Table
      //
      gBS = SystemTable->BootServices;
      ASSERT (gBS != NULL);

      return EFI_SUCCESS;
}

  gST变量定义在用户空间,它指向的系统表定义在UEFI内核中。在应用程序或驱动工程文件的[LibraryClasses]里引用UefiBootServicesTableLib后,就可以使用gST访问系统表了。

/// 示例:使用了gST
#include<Uefi.h>
#include<Library/UefiBootServicesTableLib.h>
EFI_Status
UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable)
{
    EFI_STATUS Status;
    UINTN Index;
    EFI_INPUT_KEY Key;
    CHAR16 StrBuffer[4] = {0};
    gST -> ConOut -> OutputString(gST -> ConOut,L"Please enter any key\n");
    gBS->WaitForEvent(1,&gST->ConIn->WaitForKey,&Index);
    Status = gST->ConIn->ReadKeyStroke(gST->ConIn,&Key);
    StrBuffer[0] = Key.UnicodeChar;
    StrBuffer[1] = '\n';
    gST->ConOut->OutputString(gST->ConOut,StrBuffer);
    return EFI_SUCCESS;
}

  从代码上看,其实就是使用gST和gBS替换掉SystemTable和SystemTable -> BootServices。

四、总结

  本文主要系统表的构成、系统表从内核空间传递到用户空间的过程,以及在用户空间访问系统表的两种方法。得到系统表就可以在用户空间访问UEFI内核了,应用程序和驱动对内核的控制是通过两个核心成员BootServices和RuntimeServices。EDK2在用户空间分配了全局变量gBS和gRT指代这两个服务。

参考资料

1.《UEFI原理与编程》戴正华 著
2. UEFI Spec 2_6
3. EDK2 GitHub 

  • 0
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: UEFI(Unified Extensible Firmware Interface)是一种固件接口标准,用于用于替换传统的BIOS(Basic Input/Output System)固件,旨在提高系统启动速度、安全性和可靠性。 UEFI原理编程完整版PDF是一份详细的技术指南,提供了UEFI开发所需的全部信息,包括如何设计UEFI驱动程序、应用程序和固件,以及如何使用UEFI的各种功能。 这份PDF文件将UEFI划分为四个主要模块:启动服务、管理服务、硬件抽象层和UEFI Shell。每个模块都有详细的描述和示例代码,此外还包含了UEFI系统架构、数据结构、驱动程序与应用程序的开发方法等内容。 此外,UEFI原理编程完整版PDF还介绍了UEFI与安全性、多核技术、嵌入式系统等方面的关系,并为读者提供了关键的UefiSpec和UefiDebugTools库文档。 总之,UEFI原理编程完整版PDF是一份权威的技术指南,涵盖了UEFI方方面面的内容,非常适合开发人员和工程师学习和参考。 ### 回答2: UEFI(Unified Extensible Firmware Interface)是一种新型的启动编程接口,它替代了旧版的BIOS(基本输入输出系统)。UEFI原理编程可以帮助开发人员理解UEFI的工作原理编程技巧。 UEFI拥有完善的安全性和可靠性,其编程方式也分灵活。与BIOS相比,UEFI可以支持更多的硬件平台和更大的硬盘容量,同时也可以优化启动速度和程序的执行效率。 UEFI原理编程pdf完整版可以帮助开发人员更好地了解UEFI的概念和架构,并提供丰富的代码示例,帮助开发人员快速掌握UEFI编程技能。除此之外,该书还介绍了UEFI的启动流程、安全策略、UEFI应用的开发和调试等内容,对于想要深入学习UEFI编程的开发人员来说是一本很有价值的参考书。 总之,UEFI原理编程pdf完整版是一本优秀的技术参考书,其内容涵盖了UEFI的方方面面,对于想要深入了解和运用UEFI的开发人员来说具有很高的参考价值。 ### 回答3: UEFI(Unified Extensible Firmware Interface)是一种新型的系统固件接口,它为操作系统与硬件之间提供了一种标准的接口。相比传统的BIOS,UEFI可以支持更多的硬件功能,同时也更加安全和灵活。 UEFI原理主要包括以下几个方面:1、UEFI接口包括了启动管理器,硬件抽象层,运行环境和应用层等模块;2、UEFI支持模块化设计和驱动加载,可以动态地加载和卸载硬件驱动,提高了系统灵活性和安全性;3、UEFI使用GUID Partition Table(GPT)替代MBR分区,支持更大的硬盘容量和更稳定的系统启动;4、UEFI还支持Secure Boot功能,可以防止恶意程序篡改启动软件和系统文件,提高了系统安全性。 关于UEFI编程,需要掌握以下几个核心内容:1、UEFI开发环境的搭建,需要熟悉UEFI规范以及系统固件接口的程序编写;2、UEFI应用程序的设计和开发,可以利用UEFI提供的应用程序接口(API)或者开发自定义的应用程序;3、UEFI驱动程序的编写,需要掌握UEFI驱动程序的架构和编程模型;4、UEFI启动管理器的开发,需要熟悉UEFI启动管理器的设计和实现。 总之,UEFI是一种新型的系统固件接口,可以为操作系统和硬件提供一个标准的接口,提高系统的安全性和灵活性。对于UEFI编程的学习和掌握,则需要对UEFI规范和程序设计有深入的了解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值