本文涉及的代码都可以在https://gitee.com/jiangwei0512/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的,所以它在所有的驱动里面都可以使用,这个可以理解。
但是似乎没有办法模仿,在我们的代码中没有办法使用!
那么只能想其它的办法。
对应普通数据,比如说前面提到的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并解析其中的数据。
不过好像没有看到有其它地方在这么用。
先研究研究。