UEFI实战——在库中使用全局变量

本文涉及的代码都可以在https://gitee.com/jiangwei0512/edk2-UDK2017中找到。

一个不怎么好的测试代码

有两个驱动,NullDxeDriverOne.inf和NullDxeDriverTwo.inf,它们做的事情只有一件,就是调用一个库函数:

EFI_STATUS
EFIAPI
NullDxeDriverOneEntry (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS         Status;
  Status             = EFI_SUCCESS;
  
  DEBUG ((EFI_D_ERROR, "[beni]NullDxeDriverOneEntry Start.\n"));
  PrintGlobalVar ();
  DEBUG ((EFI_D_ERROR, "[beni]NullDxeDriverOneEntry End.\n"));
  
  return Status;
}
下面是PrintGlobalVar()函数的实现:
/**
  Print the address of the global variables.
  
  @param   NA
  
  @retval EFI_SUCCESS     Executed successfully.
  @retval Others          Error happened.

**/
EFI_STATUS
EFIAPI
PrintGlobalVar (
  VOID
  )
{
  if (NULL == gBuffer) {
    gBuffer = AllocatePool (128);
  }
  DEBUG ((EFI_D_ERROR, "[beni]gBuffer addr: 0x%p.\n", gBuffer));
  DEBUG ((EFI_D_ERROR, "[beni]&Data addr: 0x%p.\n", &Data));

  return EFI_SUCCESS;
}

这里的gBuffer和Data是两个全局的变量。

这里的本意是,我们从其它设备上获取到一部分数据,存放在gBuffer对应的缓冲区去,之后就不需要在每次调用都去访问设备。

但是实际的情况如下:

[beni]NullDxeDriverOneEntry Start.
[beni]gBuffer addr: 0x7191218.
[beni]&Data addr: 0x7922390.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7191340
Loading driver at 0x0000791D000 EntryPoint=0x0000791D380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7191698
[beni]NullDxeDriverTwoEntry Start.
[beni]gBuffer addr: 0x7191798.
[beni]&Data addr: 0x791F390.
[beni]NullDxeDriverTwoEntry End.

可以看到实际上还是有两份gBuffer(Data是一个整型,它也有两份),也就是说还是需要访问两次设备。

这是因为不同的模块是分开编译的,实际上都是独立的存放了库函数,也就分开存放了这些全局变量。

如果这个库函数被多次的调用,那么多少会影响到启动时间。


代码修正

首先在最开始可以想到的是gBS,gRT等,它们是否是整个BIOS启动过程中都是独一份的,证明也很容易,在上面提到的两个模块中增加如下的打印:

  DEBUG ((EFI_D_ERROR, "[beni]SystemTable: 0x%p.\n", SystemTable));
  DEBUG ((EFI_D_ERROR, "[beni]gBS: 0x%p.\n", gBS));
然后查看结果:
[beni]NullDxeDriverOneEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]gBuffer addr: 0x7191218.
[beni]&Data addr: 0x7922410.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7191340
Loading driver at 0x0000791D000 EntryPoint=0x0000791D380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7191698
[beni]NullDxeDriverTwoEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]gBuffer addr: 0x7191798.
[beni]&Data addr: 0x791F410.
[beni]NullDxeDriverTwoEntry End.

可以看到SystemTable和gBS的值在两个驱动都是一样的。

SystemTable是驱动入口的参数,实际上gBS也是来自SystemTable中的,具体的赋值位置在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;
}

而整个SystemTable作为参数传入驱动的位置在Image.c中CoreStartImage()函数:

Image->Status = Image->EntryPoint (ImageHandle, Image->Info.SystemTable);

上面代码中的Image->Info.SystemTable在Image.c中的CoreLoadImageCommon()函数:

  Image->Info.SystemTable  = gDxeCoreST;

gDxeCoreST是在DxeMain.c中的一个全局变量,而所有的驱动都是在DxeMain.c这个模块里面Dispatch的,所以它在所有的驱动里面都可以使用,这个可以理解。

但是似乎没有办法模仿,在我们的代码中没有办法使用!

那么只能想其它的办法。


PCD数据

对应普通数据,比如说前面提到的Data整型,可以保存成PCD数据。

比如在dec里面声明一个PCD,如下所示:

[PcdsDynamic]
  gUefiOemPkgTokenSpaceGuid.PcdOemVersion|0xFFFFFFFF|UINT32|0x40000001

注意这里的类型是Dynamic的,这样就可以在启动过程中设置。

比如在之前的Lib中加入如下的代码:

EFI_STATUS
EFIAPI
GlobalDataTestLibConstructor (
  IN  EFI_HANDLE               ImageHandle,
  IN  EFI_SYSTEM_TABLE         *SystemTable
  )
{
  DEBUG ((EFI_D_ERROR, "[beni]CustomizedDisplayLibConstructor.\n"));
  if (0xFFFFFFFF == PcdGet32 (PcdOemVersion)) {
    DEBUG ((EFI_D_ERROR, "[beni]PcdSet.\n"));
    PcdSet32 (PcdOemVersion, 0x00000001);
  }
  return EFI_SUCCESS;
}

这个构造器在每个驱动第一次调用该库中的函数的时候都会调用。

之后在BIOS运行的时候上面的两个驱动的打印如下:

[beni]CustomizedDisplayLibConstructor.
[beni]PcdSet.
[beni]NullDxeDriverOneEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]gBuffer addr: 0x7191218.
[beni]Version: 0x1.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7191340
Loading driver at 0x0000791D000 EntryPoint=0x0000791D380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7191698
[beni]CustomizedDisplayLibConstructor.
[beni]NullDxeDriverTwoEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]gBuffer addr: 0x7191798.
[beni]Version: 0x1.
[beni]NullDxeDriverTwoEntry End.

在第一个驱动运行的时候,PcdOemVersion的值被修改成了1,第二个驱动运行的时候,PcdOemVersion的值已经被修改了,就不会再进入了,然后第二个驱动也能打印PcdOemVersion的值为1。也就是说PcdOemVersion的值再全局都能够正常使用了。


变量

对于上文提到的一般的数据类型,比如UINT32之类的,可以通过PCD来保存。

但是对于一段数据区域,就不太方便,这个时候可以使用变量。

UEFI里面的Runtime Service里面提供了变量的操作函数GetVariable()和SetVariable()。

注意它不能用来PEI阶段,因为只有在DXE阶段相关的驱动安装之后(也并不是一开始就能用,PEI阶段有一种叫做HOB的东西有类似的作用,这里先不讲)才能使用变量操作。

还是以上文的代码为例,为了保存gBuffer的数据并能够在BIOS启动的过程中都可以使用(其实也并没有都可以),这里新建了一个模块,用来初始化gBuffer对应的数据:(新模块名为GlobalDataInstall.inf)

EFI_STATUS
EFIAPI
GlobalDataInstallEntry (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS         Status;
  DEBUG ((EFI_D_ERROR, "[beni]GlobalDataInstallEntry start.\n"));
  Status = VariableMethod ();
  DEBUG ((EFI_D_ERROR, "[beni]GlobalDataInstallEntry end.\n"));
  return Status;
}
其中VariableMethod()的实现如下:
EFI_STATUS
EFIAPI
VariableMethod (
  VOID
  )
{
  EFI_STATUS    Status;
  VOID          *Buffer;
  
  Buffer = AllocateZeroPool (128);
  if (NULL == Buffer) {
    DEBUG ((EFI_D_ERROR, "[beni]AllocatePool failed.\n"));
    return EFI_OUT_OF_RESOURCES;
  }
  *((UINT32 *)Buffer) = OEM_DATA_MAGIC;
  
  Status = gRT->SetVariable (
                  OEM_DATA_NAME,
                  &gEfiOemGlobalDataGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  128,
                  Buffer
                  );

  FreePool (Buffer);
  
  return Status;
}

其实就是一个简单的调用SetVariable()的过程。

这里因为是测试,所以没有设计到Buffer里面的具体的数据,只是申请了一个128字节的数据区,然后放了个魔术字在最前面。

当SetVariable()之后,系统会保存一份Buffer数据,原来的可以释放掉。

上述的操作其实也不需要再一个新的驱动里面,也可以放在库函数的构造函数中,这种方式已经在前面PCD数据的时候使用过,所以这里采用了新的方式。

之后在库函数中添加如下的代码来获取全局的变量:

EFI_STATUS
EFIAPI
PrintGlobalVar (
  VOID
  )
{
  EFI_STATUS         Status;
  VOID               *Buffer;
  UINTN              Size;
  
  Size   = 0;
  Buffer = NULL;
  Status = gRT->GetVariable (
                  OEM_DATA_NAME,
                  &gEfiOemGlobalDataGuid,
                  NULL,
                  &Size,
                  Buffer
                  );
  if (EFI_ERROR (Status)) {
    if (EFI_BUFFER_TOO_SMALL == Status) {
      DEBUG ((EFI_D_ERROR, "[beni]Size : %d.\n", Size));
      Buffer = AllocatePool (Size);
      if (NULL == Buffer) {
        DEBUG ((EFI_D_ERROR, "[beni]AllocatePool failed.\n"));
        return EFI_OUT_OF_RESOURCES;
      }
      Status = gRT->GetVariable (
                      OEM_DATA_NAME,
                      &gEfiOemGlobalDataGuid,
                      NULL,
                      &Size,
                      Buffer
                      );
      if (EFI_ERROR (Status)) {
        DEBUG ((EFI_D_ERROR, "[beni]GetVariable failed 0. - %r\n", Status));
        return Status;
      }
    } else {
      DEBUG ((EFI_D_ERROR, "[beni]GetVariable failed 1. - %r\n", Status));
      return Status;
    }
  }
  
  if (OEM_DATA_MAGIC == *((UINT32 *)Buffer)) {
    DEBUG ((EFI_D_ERROR, "[beni]I got the data.\n"));
  }
  
  DEBUG ((EFI_D_ERROR, "[beni]Version: 0x%x.\n", PcdGet32(PcdOemVersion)));
  
  if (NULL != Buffer) {
    FreePool (Buffer);
    Buffer = NULL;
  }
  
  return EFI_SUCCESS;
}
同样在NullDxeDriverOne.inf和NullDxeDriverTwo.inf两个模块当中调用上述的库函数,结果如下:
[beni]CustomizedDisplayLibConstructor.
[beni]NullDxeDriverOneEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]Size : 128.
[beni]I got the data.
[beni]Version: 0x1.
[beni]NullDxeDriverOneEntry End.
Loading driver F555F2BF-E141-43B7-A5EA-635F757FC774
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 718C2C0
Loading driver at 0x0000791A000 EntryPoint=0x0000791A380 NullDxeDriverTwo.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 718CC98
[beni]CustomizedDisplayLibConstructor.
[beni]NullDxeDriverTwoEntry Start.
[beni]SystemTable: 0x7A95018.
[beni]gBS: 0x7B26C10.
[beni]Size : 128.
[beni]I got the data.
[beni]Version: 0x1.
[beni]NullDxeDriverTwoEntry End.

可以看到能够正常获取到数据。

这种方式可以使用到一段数据中,当然也能用在普通的数据中。

不过有个问题,可以在上面的代码中看到,需要在库函数中返回的申请内存和释放内存,当多次调用这个库函数的时候,还是会浪费一些时间(当然对于普通数据没有这种烦恼,但是普通数据显然用PCD更方便)。


其它

上述两种是最常见的方式,理论上还应该有其它的方法。

比如将数据放在Protocol中,然后安装这个Protocol,在其它地方获取这个Protocol并解析其中的数据。

不过好像没有看到有其它地方在这么用。

先研究研究。

猜你喜欢

转载自blog.csdn.net/jiangwei0512/article/details/78992276