【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

智能推荐

什么是前端开发?什么是后端开发?-程序员宅基地

文章浏览阅读1.9w次,点赞29次,收藏46次。什么是前端开发?概念我们通常所说的前端开发通常指的是网站的创建网站是一个用户界面,也就是你在网站上看到的的文本、按钮、图像还有视图。那么创建这些可交互界面的实践就叫做WEB界面的开发就像是你在银行里取钱不是直接往库存里伸手去拿,而是通过ATM这个银行的前端去进行一些操作尽管我们说的前端开发尽管通常指网站开发,但是现如今的前端开发已经是全平台的开发了,例如手机APP、微信小程序等。所以准确的来说前端开发,应该是直接给予用户的可交互式界面的开发,是创建用户界面的实践让用户以安全且友好的方式与数据_后端开发

Binary XML file line #40: Error inflating class ImageView_binary xml file line #40: binary xml file line #40-程序员宅基地

文章浏览阅读1.6k次。ImageView中src的图片放到mipmap中基本就好了_binary xml file line #40: binary xml file line #40: error inflating class an

MATLAB算法实战应用案例精讲-【图像处理】缺陷检测(附python和matlab实现代码)_matlab缺陷检测代码-程序员宅基地

文章浏览阅读7.6k次。缺陷检测通常是指对物品表面缺陷的检测,表面缺陷检测是采用先进的机器视觉检测技术,对工件表面的斑点、凹坑、划痕、色差、缺损等缺陷进行检测。缺陷检测是工业上非常重要的一个应用,由于缺陷多种多样,传统的机器视觉算法很难做到对缺陷特征完整的建模和迁移,复用性不大,要求区分工况,这会浪费大量的人力成本。深度学习在特征提取和定位上取得了非常好的效果,越来越多的学者和工程人员开始将深度学习算法引入到缺陷检测领域中这种测量方法是在线非接触式检测设备,轮廓测量仪适用于轧制中的长材检测,如圆钢、方钢、螺纹钢、T型钢等,一般的表_matlab缺陷检测代码

CDH大数据平台 rm: cannot remove ‘/var/run/cloudera-scm-agent/process’: Device or resource busy_rm: cannot remove ‘cloudera-scm-agent/process’: de-程序员宅基地

文章浏览阅读309次,点赞7次,收藏6次。rm: cannot remove ‘/var/run/cloudera-scm-agent/process’: Device or resource busy_rm: cannot remove ‘cloudera-scm-agent/process’: device or resource busy

多线程实时数据采集MFC VISUAL C++ /C++_c++多线程数据采集-程序员宅基地

文章浏览阅读3.3k次。美国国家仪器公司为用户提供了许多高性能、高速度、高分辨率的数据采集卡,而与之接口的软件大多采用 Labview,Labview是一种图形化编程软件,你只需拖动控件到容器,使用起来确实比较方便。但用户无法知 道底层的东西,这样针对具体的应用,还要自己去理解封装得很深的函数或程序,费时、费力。因此,c++/MFC还是一种 比较好的选择。如何在MFC中实现实时数据采集呢? 首..._c++多线程数据采集

图像平滑处理_图像平滑处理几种滤波器原理-程序员宅基地

文章浏览阅读2.8k次。文章目录一.均值滤波二.方框滤波三.高斯滤波四.中值滤波五.双边滤波2D卷积图像平滑处理,又叫做平滑滤波,或模糊滤波,能有效的过滤掉图像内部的噪声。其基本原理是图像像素点的值处理为邻域内其他像素点的均值。取近似的方法,即平滑滤波的方法多种多样。在空间滤波中,事先定义一个掩模,然后将掩模逐步移过像素点,掩模中心即当前像素点的值为掩模内所有像素点的值通过某种方式计算得到,掩模大小可以根据情况选择。但对于边界的点,由于其掩模有一部分在图像外围,所以此时要对其做一些特殊处理,常见的处理方法:(1)控制掩模,._图像平滑处理几种滤波器原理

随便推点

占位式插件化一Activity的跳转_activity跳转 插件-程序员宅基地

文章浏览阅读329次。原理宿主APP安装在手机中的APP,并且通过该APP加载插件中的Activity插件APP没有安装的apk,通过宿主直接打开其内部Activity标准(协议)宿主APP和插件APP通信的桥梁。宿主APP通过一个空壳Activity(代理Activity)加载插件app中的Activity,实际上插件app中的Activity并没有入栈,也没法入栈,因为插件app没有安装,没有上下文和..._activity跳转 插件

PTA 剥洋葱(C语言 + 详细注释 + 代码超简单)_c语言pta怎么使用-程序员宅基地

文章浏览阅读1.0k次,点赞9次,收藏13次。输入格式:一行,一个整数,即图形的层数输出格式:如上述图形输入样例:3输出样例:AAAAAABBBAABCBAABBBAAAAAA//打印图形题关键是找规律,一般只需两重循环(行循环、列循环)#include<stdio.h>#include<string.h>int main() { int i, n; char ..._c语言pta怎么使用

docker配置国内镜像源_docker国内镜像源-程序员宅基地

文章浏览阅读3.3w次,点赞9次,收藏25次。刚开始学习docker,发现下载镜像非常的慢。如果不经过,docker的镜像下载都来源于国外,因此需要配置国内的镜像源。Docker中国区官方镜像。_docker国内镜像源

Unity中怎么播放视频_unity 播放视频-程序员宅基地

文章浏览阅读1.9w次,点赞40次,收藏209次。一.首先在场景中新建UI中的Raw Image可以按住Alt再点击下图红色箭头所示将Raw Image铺满游戏全屏(也可以自己调整大小)二.给Raw Image添加Video Player组件三.在Assets或者自己想要的文件夹中创建Render Texture四.将准备好的视频(这里用到的视频格式是mp4)拖入项目中并做如下修改这里我把新建的Render Texture命名为2,拖入的视频也命名为2(随便命的,不要在意)这里我们看到这个Render Te..._unity 播放视频

使用BOOTICE 恢复系统启动项_bootice保存后没用-程序员宅基地

文章浏览阅读9.7k次,点赞2次,收藏9次。使用BOOTICE 恢复系统启动项我在安装deepin 系统的时候,经常遇到重启进不去系统,每次重启都会进入windows 系统,这让我感到特别头疼,试了好多次都不成功,有些情况是,成功后再次重启又回到了windows系统。后来终于在PE中利用一款叫做BOOT ICE的工具成功解决。BOOTICE— 引导扇区维护工具简介BOOTICE 是一个启动相关的维护的小工具,主要用于安装、修复、备份和恢复磁盘_bootice保存后没用

文本分类与SVM_svm分类-程序员宅基地

文章浏览阅读9.5w次,点赞54次,收藏202次。之前做过一些文本挖掘的项目,比如网页分类、微博情感分析、用户评论挖掘,也曾经将libsvm进行包装,写了一个文本分类的开软软件Tmsvm。所以这里将之前做过一些关于文本分类的东西整理总结一下。1 基础知识1. 1 样本整理文本分类属于有监督的学习,所以需要整理样本。根据业务需求,确定样本标签与数目,其中样本标签多为整数。在svm中其中如果为二分类,样本标签一般会设定为-1和_svm分类