文章目录

  • 前言
  • BootService Table
  • gBS
  • EFI_BOOT_SERVICES
  • SystemTable
  • EFI_SYSTEM_TABLE
  • SystemTable
  • mBootServices
  • RunTimeService Table
  • 总结


前言

在实现一个基础的driver的文章中,在进行protocol安装的时候,我们用到了一个函数InstallProtocolInterface,同时也应该注意到了其使用的方式: gBS->InstallProtocolInterface,这里的gBS,在UEFI中可以说非常关键,在代码中处处可以看到他的身影。在介绍protocol相关的函数之前,我们先来整理一下gBS,还有与其重要性不相上下的gST,gRT.
既然我们是从gBS引出这些问题的,那么也从gBS开始逐步扩展进深入学习相关的内容。
介绍的过程中可能还会有一些我已经理解没有介绍、我没有理解但是需要的地方,没有介绍的会一一介绍,没有了解的慢慢学习。但是我觉得并不影响大局。

BootService Table

首先需要明确的是代码中你能看到的gBS,其指代的是UEFI 的BootService,就是UEFI提供的已经初始化完成的能够实现相应功能的服务或者说函数 被以表的形式被组织起来提供给你方便使用的东东。就是说你可以直接使用这个表中已有的函数来实现相应的功能。
上述这是我自己的理解,可以看一下书中给BootService的一个相对正式的解释说明:

BootService,即启动服务表,是UEFI的核心数据结构。系统进入DXE阶段时启动服务表被初始化,最终通过SystemTable指针将启动服务表传递给UEFI应用程序或者驱动程序。UEFI应用和驱动可以通过gST->BootService或者gBS访问这个表。1

抽象的说的太多意义不算大 还是在代码中一点一点看最有效。前面说到了gBS又说到了的BootService ,那么先看一下这两者在代码中是如何定义的,又是如何联系起来的?

gBS

gBS的初始定义在 UefiBootServicesTableLib.c 中找到,可以看到gBS初始的时候被定义为一个空指针,而指针的赋值在下面的函数 UefiBootServicesTableLibConstructor中可以看到

// UefiBootServiceTableLib.c
EFI_HANDLE         gImageHandle = NULL;
EFI_SYSTEM_TABLE   *gST         = NULL;
EFI_BOOT_SERVICES  *gBS         = NULL;


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

可以看到,gBS被定义为一个全局的指针,一开始定义为NULL,指针的类型为EFI_BOOT_SERVICES
在函数UefiBootServicesTableLibConstructor里面,给gBS进行了一个赋值SystemTable->BootServices,我们继续追踪SystemTable这个结构体,就能看到其成员BootServices的类型也是 EFI_BOOT_SERVICES,此时gBS不再为空指针,这也就标志后续就可以使用对应的服务了。


同时这个文件中还定义了另一个指针gSTgST代表了就是SystemTable 这个文件定义了在UEFI中使用最为广泛的两个指针,这两个指针又都和SystemTable相关,说明这个指针也是非常重要的,不要着急马上就会说到 暂时先pass


现在我们知道了gBS指向的内容是什么了,接下来就出现了两个问题

  1. EFI_BOOT_SERVICES类型是什么样子的?
  2. SystemTable->BootServices是什么样子的?

EFI_BOOT_SERVICES

EFI_BOOT_SERVICES这个类型并不复杂,在文件 UefiSpec.h中可以找到其定义:

ROG Bios UEFI密码_uefi

这是一个非常大的结构体,结构体中的成员都是重新定义过的。我们找一个熟悉成员InstallProtocolInterface来看一下具体的情况
可以看到这个成员的类型是EFI_INSTALL_PROTOCOL_INTERFACE,查看定义:

// UefiSpec.h
typedef
EFI_STATUS
(EFIAPI *EFI_INSTALL_PROTOCOL_INTERFACE)(
  IN OUT EFI_HANDLE               *Handle,
  IN     EFI_GUID                 *Protocol,
  IN     EFI_INTERFACE_TYPE       InterfaceType,
  IN     VOID                     *Interface
  );

可以看到这是一个函数指针类型的成员,也就是说最终这个指针会指向一个函数,输入参数为以上四个参数。
结构体中的其他成员都是同样的情况,类型均为函数指针(UEFI中的protocol都是这样的)想要知道实际上指向什么 ,下面就需要看一下SystemTable->BootServices的具体内容。
而想要知道具体这个实体是什么样子的,首先要追踪一下SystemTable是什么样子的。

SystemTable

前面说gBS的时候,说到了一个文件叫做UefiBootServiceTableLib.c,在这个文件中我们不仅可以看到gBS的初始化情况,同样的我们还能够看到gST初始化的具体情况。
一言以蔽之,和gBS情况几乎完全相同,首先定义了一个类型为EFI_SYSTEM_TABLE 的空指针gST,然后在函数中,将指针赋值为SystemTable,(类型仍为EFI_SYSTEM_TABLE)。
这里我们想要深入了解,同样产生了两个问题

  1. EFI_SYSTEM_TABLE类型是什么样子的?
  2. SystemTable是什么样子的?

EFI_SYSTEM_TABLE

上述的第一个问题是很容易回答的,在代码UefiSpec.h中稍加查找就能发现其原型

ROG Bios UEFI密码_初始化_02

上面就是EFI_SYSTEM_TABLE的结构体,可以看到,这个结构体仍然是由多种类型的结构体成员组成的,其中有变量的形式还有指针的形式。还包括了我们之前讨论的BootServices和我们将要讨论的RuntimeServices。可以看到这两个都是指针。也就是最终我们将这个指针指向的位置就是结构体的实现位置。
(这个表中的内容是非常值得我们深入挖掘的,但是本篇文章主要讨论的就是 基础服务所以不对Console等做过多的讨论。)

SystemTable

讨论完结构体的定义之后,我们来到最最最关键的问题SystemTable是什么样子的?
之所以说他关键,

  1. 是因为第一这是这一节的关键
  2. 是之前讨论gBS的时候还遗留了一个问题SystemTable->BootServices是什么样子的?
  3. 如果我们后续要讨论gRT,也是与此相关的
    在之前的函数中,SystemTable似乎都是以一种入参的形式出现的(不信的话自己去看前面的HelloWorld Driver ,甚至在初始化’gST’的时候都是这样的),但是作为一个坚定的唯物主义战士,我坚信不可能有无缘无故的数据出现,只不过是我才疏学浅还没有解决SystemTable是从哪里来、怎么来的问题。
    为了解决这一问题,我详细的看了一下《UEFI编程与原理》1一书的第五章 UEFI基础服务。从这里找到了一些蛛丝马迹:

系统进入DXE阶段后系统表会被初始化,因而系统表只能用于DXE阶段以及以后的应用程序和驱动中。
这句话告诉我们一个重要的信息:SystemTable是在DXE阶段被初始化的。

DXE阶段,我们首先就应该去DxeMain函数里面去看有没有我们想要的东西,果不其然,在这个函数中我们看到这样一部分:

//MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c
//
// DXE Core Global Variables for the EFI System Table, Boot Services Table,
// DXE Services Table, and Runtime Services Table
//

EFI_SYSTEM_TABLE  *gDxeCoreST = NULL;
EFI_RUNTIME_SERVICES  *gDxeCoreRT         = &mEfiRuntimeServicesTableTemplate;

// ...
// ...

**/
VOID
EFIAPI
DxeMain (
  IN  VOID  *HobStart
  )
{
	
 //...

  // Allocate the EFI System Table and EFI Runtime Service Table from EfiRuntimeServicesData
  // Use the templates to initialize the contents of the EFI System Table and EFI Runtime Services Table
  //
  gDxeCoreST = AllocateRuntimeCopyPool (sizeof (EFI_SYSTEM_TABLE), &mEfiSystemTableTemplate);
  ASSERT (gDxeCoreST != NULL);

  gDxeCoreRT = AllocateRuntimeCopyPool (sizeof (EFI_RUNTIME_SERVICES), &mEfiRuntimeServicesTableTemplate);
  ASSERT (gDxeCoreRT != NULL);

  gDxeCoreST->RuntimeServices = gDxeCoreRT;


 //...
}

UEFI开发者通过注释告诉我们,这几行代码就是在为System TableRuntime Service Table分配内存,并且用临时的内容初始化了这两个表。
从代码中变量的名称其实可以推测出来,gDxeCoreST代表的就是System Table,它临时被mEfiSystemTableTemplate初始化了。

EFI_SYSTEM_TABLE  mEfiSystemTableTemplate = {
  {
    EFI_SYSTEM_TABLE_SIGNATURE,                                           // Signature
    EFI_SYSTEM_TABLE_REVISION,                                            // Revision
    sizeof (EFI_SYSTEM_TABLE),                                            // HeaderSize
    0,                                                                    // CRC32
    0                                                                     // Reserved
  },
  NULL,                                                                   // FirmwareVendor
  0,                                                                      // FirmwareRevision
  NULL,                                                                   // ConsoleInHandle
  NULL,                                                                   // ConIn
  NULL,                                                                   // ConsoleOutHandle
  NULL,                                                                   // ConOut
  NULL,                                                                   // StandardErrorHandle
  NULL,                                                                   // StdErr
  NULL,                                                                   // RuntimeServices
  &mBootServices,                                                         // BootServices
  0,                                                                      // NumberOfConfigurationTableEntries
  NULL                                                                    // ConfigurationTable
};

gDxeCoreRT代表Runtime Service Table,临时被mEfiRuntimeServicesTableTemplate初始化了。(这个部分后面在详细讨论) ^7dc49d


刚刚初始化完成的SystemTable成员大多是都为0或者为空指针,(BootServices指针除外 很重要!!!!后面会再来讨论这个问题)这就表示在代码中还有其他地方对SystemTable进行了赋值操作,这才有了我们经常使用的SystemTable
既然找到了初级形态的gDxeCoreST,那么只需要在整个代码中继续搜索gDxeCoreST,看看什么位置用到了他,就可以知道赋值操作到底发生在什么位置了。在代码中我们能够找到的使用这个参数的位置有很多 ,但是我们重点关注的应该是DxeMain.c函数中调用的的下面两个函数

Status = CoreInitializeImageServices (HobStart);
  // ... 
  CoreDispatcher ();

这两个函数分别实现了初始化ImageService和Dispatcher Driver,
如果一层一层的去查找这两个函数就会发现这样一条语句

Image->Info.SystemTable = gDxeCoreST;

就这样,gDxeCoreST被赋值给了我们平时使用的SystemTable
(DxeMain函数我觉得可以单独拿出来说一下 这个函数我觉得还蛮重要的 包括SystemTable中的服务都是怎么具体实现的都是可以深挖的东西 等我搞明白再说 先挖坑…)
至此,我觉得我已经将SystemTable从哪里来的讲的大致清楚了。如果还要详细说,这中间还有几个问题没有说清楚:

  1. SystemTable中的服务是在哪里 、如何被一步步构建起来的
  2. Image->Info.SystemTable = gDxeCoreST中的Image具体代表的是什么?
  3. 为什么在执行ApplicationDriver的时候都会将Image作为参数传递进去?
    这几个问题我觉得可能大家都有自己的大致概念,我也是,但是想要准确的说出这些事情的来龙去脉可能真的有点难。这里我们还是先回归这边文章的重点继续沿着这条线向下

mBootServices

前面在第二节介绍SystemTable我们就已经看到,在初始化的时候,BootServices指针就已经被指向了一个结构体mBootServices。第一节中,我们推断过,SystemTable->BootServices就是gBS的实体。这二者结合我们就能够回答之前提出的问题:SystemTable->BootServices是什么样子的?

接下来就是最最激动人心的时刻了,一起来看一下mBootServices的庐山真面目(其实就是在DxeMain.c文件的开始部分)

ROG Bios UEFI密码_ROG Bios UEFI密码_03

在这里我们就可以看到每一个成员已经被初始化成了一个货真价实的函数了。找一下我们之前说过的InstallProtocolInterface的本体是什么样子的吧。

EFI_STATUS
EFIAPI
CoreInstallProtocolInterface (
  IN OUT EFI_HANDLE      *UserHandle,
  IN EFI_GUID            *Protocol,
  IN EFI_INTERFACE_TYPE  InterfaceType,
  IN VOID                *Interface
  )
{
  return CoreInstallProtocolInterfaceNotify (
           UserHandle,
           Protocol,
           InterfaceType,
           Interface,
           TRUE
           );
}

emm … 还是很简单的关于这个函数具体什么意思 下一篇在详细的去说啦。这一篇还是主要为了找清楚这些我们平时经常使用的函数是怎么来的。这不就找到了吗。这样下次想要知道想用的函数原型是什么样子的时候就不会再一脸懵逼。
反正我自己刚开始看UEFI代码的时候,我就是由于找不到函数原型一脸懵逼的 ,大佬肯定不会了
到这里 BootService的介绍基本完成。注意一下,我这里介绍的都是代码中能看到的,至于顺序是怎么样的,在哪一个阶段才能够使用,我想要留到介绍UEFI启动阶段的时候再说。但是这个阶段我自己现在还是没有完全搞得十分清楚,已经在学习了。。。有机会或者说下次一定?

RunTimeService Table

在最开始的时候,说了三个常用的重要的指针gBT,gST,gRT,已经说完了前两个了,还剩下最后一个gRT。现在就来说一下。
其实第二节在讨论SystemTable的时候已经说过gDxeCoreRT的初始化情况,如果不记得了再写一遍帮助你回想一下:

//MdeModulePkg\Core\Dxe\DxeMain\DxeMain.c
//
// DXE Core Global Variables for the EFI System Table, Boot Services Table,
// DXE Services Table, and Runtime Services Table
//

EFI_SYSTEM_TABLE  *gDxeCoreST = NULL;
EFI_RUNTIME_SERVICES  *gDxeCoreRT         = &mEfiRuntimeServicesTableTemplate;

// ...
// ...

**/
VOID
EFIAPI
DxeMain (
  IN  VOID  *HobStart
  )
{
	
 //...

  // Allocate the EFI System Table and EFI Runtime Service Table from EfiRuntimeServicesData
  // Use the templates to initialize the contents of the EFI System Table and EFI Runtime Services Table
  //
  gDxeCoreST = AllocateRuntimeCopyPool (sizeof (EFI_SYSTEM_TABLE), &mEfiSystemTableTemplate);
  ASSERT (gDxeCoreST != NULL);

  gDxeCoreRT = AllocateRuntimeCopyPool (sizeof (EFI_RUNTIME_SERVICES), &mEfiRuntimeServicesTableTemplate);
  ASSERT (gDxeCoreRT != NULL);

  gDxeCoreST->RuntimeServices = gDxeCoreRT;


 //...
}

此时 gDxeCoreRT已经被初始化成为了mEfiRuntimeServicesTableTemplate ,接下来看一下mEfiRuntimeServicesTableTemplate的情况:

// DxeMain.c
EFI_RUNTIME_SERVICES  mEfiRuntimeServicesTableTemplate = {
  {
    EFI_RUNTIME_SERVICES_SIGNATURE,                               // Signature
    EFI_RUNTIME_SERVICES_REVISION,                                // Revision
    sizeof (EFI_RUNTIME_SERVICES),                                // HeaderSize
    0,                                                            // CRC32
    0                                                             // Reserved
  },
  (EFI_GET_TIME)CoreEfiNotAvailableYetArg2,                       // GetTime
  (EFI_SET_TIME)CoreEfiNotAvailableYetArg1,                       // SetTime
  (EFI_GET_WAKEUP_TIME)CoreEfiNotAvailableYetArg3,                // GetWakeupTime
  (EFI_SET_WAKEUP_TIME)CoreEfiNotAvailableYetArg2,                // SetWakeupTime
  (EFI_SET_VIRTUAL_ADDRESS_MAP)CoreEfiNotAvailableYetArg4,        // SetVirtualAddressMap
  (EFI_CONVERT_POINTER)CoreEfiNotAvailableYetArg2,                // ConvertPointer
  (EFI_GET_VARIABLE)CoreEfiNotAvailableYetArg5,                   // GetVariable
  (EFI_GET_NEXT_VARIABLE_NAME)CoreEfiNotAvailableYetArg3,         // GetNextVariableName
  (EFI_SET_VARIABLE)CoreEfiNotAvailableYetArg5,                   // SetVariable
  (EFI_GET_NEXT_HIGH_MONO_COUNT)CoreEfiNotAvailableYetArg1,       // GetNextHighMonotonicCount
  (EFI_RESET_SYSTEM)CoreEfiNotAvailableYetArg4,                   // ResetSystem
  (EFI_UPDATE_CAPSULE)CoreEfiNotAvailableYetArg3,                 // UpdateCapsule
  (EFI_QUERY_CAPSULE_CAPABILITIES)CoreEfiNotAvailableYetArg4,     // QueryCapsuleCapabilities
  (EFI_QUERY_VARIABLE_INFO)CoreEfiNotAvailableYetArg4             // QueryVariableInfo
};

看起来似乎这个结构提已经被完全的初始化了,但是只要你动动手指,随便点一个函数进去,就能发现这个结构体里面的函数其实都空的!!!这是因为,运行时服务的内容是在之后DXE跑起来之后慢慢填充的,此时这些看起来正经的函数其实并不是正经函数,而是用来占位的。
具体是怎么初始化的,我拿时间相关的函数来举例:

//EmbeddedPkg\RealTimeClockRuntimeDxe\RealTimeClock.c

EFI_STATUS
EFIAPI
InitializeRealTimeClock (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  UINTN       Size;

  Status = LibRtcInitialize (ImageHandle, SystemTable);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Size   = sizeof (mTimeSettings);
  Status = EfiGetVariable (
             (CHAR16 *)mTimeSettingsVariableName,
             &gEfiCallerIdGuid,
             NULL,
             &Size,
             (VOID *)&mTimeSettings
             );
  if (EFI_ERROR (Status) ||
      !IsValidTimeZone (mTimeSettings.TimeZone) ||
      !IsValidDaylight (mTimeSettings.Daylight))
  {
    DEBUG ((
      DEBUG_WARN,
      "%a: using default timezone/daylight settings\n",
      __FUNCTION__
      ));

    mTimeSettings.TimeZone = EFI_UNSPECIFIED_TIMEZONE;
    mTimeSettings.Daylight = 0;
  }

  SystemTable->RuntimeServices->GetTime       = GetTime;
  SystemTable->RuntimeServices->SetTime       = SetTime;
  SystemTable->RuntimeServices->GetWakeupTime = GetWakeupTime;
  SystemTable->RuntimeServices->SetWakeupTime = SetWakeupTime;

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &mHandle,
                  &gEfiRealTimeClockArchProtocolGuid,
                  NULL,
                  NULL
                  );

  return Status;
}

这里才是真正初始化时间函数的位置。现在知道了函数初始化的位置。接下来就是怎么将RuntimeServicegRT联系起来。这个问题其实其实巨巨巨简单,看下面

//MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c

EFI_STATUS
EFIAPI
UefiRuntimeServicesTableLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  //
  // Cache pointer to the EFI Runtime Services Table
  //
  gRT = SystemTable->RuntimeServices;
  ASSERT (gRT != NULL);
  return EFI_SUCCESS;
}

看到没 就是这么轻松

总结

整体上从代码的角度介绍了常用的UEFI 基础服务的初始化以及赋值流程,为接下来的protocol函数深入研究铺垫了一下。

写笔记真的比学习都难 ,很佩服那些坚持记录的人,真的很不方便,最近正在研究有没有什么比较好用的软件进行记录。可能找到之后就上传图片或者PDF,这样也能省去调整格式的时间。


  1. UEFI原理与编程 ↩︎ ↩︎