【UEFI实战】在库中使用全局变量_uefibootservicestablelibconstructor-程序员宅基地

技术标签: UEFI开发基础  uefi  

说明

本文涉及的代码都可以在vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.中找到。

一个不怎么好的测试代码

有两个驱动,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并解析其中的数据。

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

先研究研究。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jiangwei0512/article/details/78992276

智能推荐

hive使用适用场景_大数据入门:Hive应用场景-程序员宅基地

文章浏览阅读5.8k次。在大数据的发展当中,大数据技术生态的组件,也在不断地拓展开来,而其中的Hive组件,作为Hadoop的数据仓库工具,可以实现对Hadoop集群当中的大规模数据进行相应的数据处理。今天我们的大数据入门分享,就主要来讲讲,Hive应用场景。关于Hive,首先需要明确的一点就是,Hive并非数据库,Hive所提供的数据存储、查询和分析功能,本质上来说,并非传统数据库所提供的存储、查询、分析功能。Hive..._hive应用场景

zblog采集-织梦全自动采集插件-织梦免费采集插件_zblog 网页采集插件-程序员宅基地

文章浏览阅读496次。Zblog是由Zblog开发团队开发的一款小巧而强大的基于Asp和PHP平台的开源程序,但是插件市场上的Zblog采集插件,没有一款能打的,要么就是没有SEO文章内容处理,要么就是功能单一。很少有适合SEO站长的Zblog采集。人们都知道Zblog采集接口都是对Zblog采集不熟悉的人做的,很多人采取模拟登陆的方法进行发布文章,也有很多人直接操作数据库发布文章,然而这些都或多或少的产生各种问题,发布速度慢、文章内容未经严格过滤,导致安全性问题、不能发Tag、不能自动创建分类等。但是使用Zblog采._zblog 网页采集插件

Flink学习四:提交Flink运行job_flink定时运行job-程序员宅基地

文章浏览阅读2.4k次,点赞2次,收藏2次。restUI页面提交1.1 添加上传jar包1.2 提交任务job1.3 查看提交的任务2. 命令行提交./flink-1.9.3/bin/flink run -c com.qu.wc.StreamWordCount -p 2 FlinkTutorial-1.0-SNAPSHOT.jar3. 命令行查看正在运行的job./flink-1.9.3/bin/flink list4. 命令行查看所有job./flink-1.9.3/bin/flink list --all._flink定时运行job

STM32-LED闪烁项目总结_嵌入式stm32闪烁led实验总结-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏6次。这个项目是基于STM32的LED闪烁项目,主要目的是让学习者熟悉STM32的基本操作和编程方法。在这个项目中,我们将使用STM32作为控制器,通过对GPIO口的控制实现LED灯的闪烁。这个STM32 LED闪烁的项目是一个非常简单的入门项目,但它可以帮助学习者熟悉STM32的编程方法和GPIO口的使用。在这个项目中,我们通过对GPIO口的控制实现了LED灯的闪烁。LED闪烁是STM32入门课程的基础操作之一,它旨在教学生如何使用STM32开发板控制LED灯的闪烁。_嵌入式stm32闪烁led实验总结

Debezium安装部署和将服务托管到systemctl-程序员宅基地

文章浏览阅读63次。本文介绍了安装和部署Debezium的详细步骤,并演示了如何将Debezium服务托管到systemctl以进行方便的管理。本文将详细介绍如何安装和部署Debezium,并将其服务托管到systemctl。解压缩后,将得到一个名为"debezium"的目录,其中包含Debezium的二进制文件和其他必要的资源。注意替换"ExecStart"中的"/path/to/debezium"为实际的Debezium目录路径。接下来,需要下载Debezium的压缩包,并将其解压到所需的目录。

Android 控制屏幕唤醒常亮或熄灭_android实现拿起手机亮屏-程序员宅基地

文章浏览阅读4.4k次。需求:在诗词曲文项目中,诗词整篇朗读的时候,文章没有读完会因为屏幕熄灭停止朗读。要求:在文章没有朗读完毕之前屏幕常亮,读完以后屏幕常亮关闭;1.权限配置:设置电源管理的权限。

随便推点

目标检测简介-程序员宅基地

文章浏览阅读2.3k次。目标检测简介、评估标准、经典算法_目标检测

记SQL server安装后无法连接127.0.0.1解决方法_sqlserver 127 0 01 无法连接-程序员宅基地

文章浏览阅读6.3k次,点赞4次,收藏9次。实训时需要安装SQL server2008 R所以我上网上找了一个.exe 的安装包链接:https://pan.baidu.com/s/1_FkhB8XJy3Js_rFADhdtmA提取码:ztki注:解压后1.04G安装时Microsoft需下载.NET,更新安装后会自动安装如下:点击第一个傻瓜式安装,唯一注意的是在修改路径的时候如下不可修改:到安装实例的时候就可以修改啦数据..._sqlserver 127 0 01 无法连接

js 获取对象的所有key值,用来遍历_js 遍历对象的key-程序员宅基地

文章浏览阅读7.4k次。1. Object.keys(item); 获取到了key之后就可以遍历的时候直接使用这个进行遍历所有的key跟valuevar infoItem={ name:'xiaowu', age:'18',}//的出来的keys就是[name,age]var keys=Object.keys(infoItem);2. 通常用于以下实力中 <div *ngFor="let item of keys"> <div>{{item}}.._js 遍历对象的key

粒子群算法(PSO)求解路径规划_粒子群算法路径规划-程序员宅基地

文章浏览阅读2.2w次,点赞51次,收藏310次。粒子群算法求解路径规划路径规划问题描述    给定环境信息,如果该环境内有障碍物,寻求起始点到目标点的最短路径, 并且路径不能与障碍物相交,如图 1.1.1 所示。1.2 粒子群算法求解1.2.1 求解思路    粒子群优化算法(PSO),粒子群中的每一个粒子都代表一个问题的可能解, 通过粒子个体的简单行为,群体内的信息交互实现问题求解的智能性。    在路径规划中,我们将每一条路径规划为一个粒子,每个粒子群群有 n 个粒 子,即有 n 条路径,同时,每个粒子又有 m 个染色体,即中间过渡点的_粒子群算法路径规划

量化评价:稳健的业绩评价指标_rar 海龟-程序员宅基地

文章浏览阅读353次。所谓稳健的评估指标,是指在评估的过程中数据的轻微变化并不会显著的影响一个统计指标。而不稳健的评估指标则相反,在对交易系统进行回测时,参数值的轻微变化会带来不稳健指标的大幅变化。对于不稳健的评估指标,任何对数据有影响的因素都会对测试结果产生过大的影响,这很容易导致数据过拟合。_rar 海龟

IAP在ARM Cortex-M3微控制器实现原理_value line devices connectivity line devices-程序员宅基地

文章浏览阅读607次,点赞2次,收藏7次。–基于STM32F103ZET6的UART通讯实现一、什么是IAP,为什么要IAPIAP即为In Application Programming(在应用中编程),一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给_value line devices connectivity line devices