总述
使用 HOB 的原因是因为,在 PEI 阶段内存尚未完全初始化,到了 DXE 阶段才完整初始化了内存,所以无法通过简单的内存地址传递数据,并且我们仍然有一些对于内存空间存储的需求,因此发明了 HOB 机制。
HOBs may be relocated in system memory by the HOB consumer phase. HOBs must not contain pointers to other data in the HOB list, including that in other HOBs. The table must be able to be copied without requiring internal pointer adjustment.[[1]](#[1])
也就是需要注意 PEI 的 HOB 内存空间在 DXE 阶段会被DxeCore relocation,所以不要用 HOB 来存放内存地址。[[2]](#[2])
UEFI 提供了 HOB(Hand-Off-Block)
机制,即在 PEI 阶段将数据打包成数据块存放在一段连续的内存中,数据块的标识为 GUID,DXE 阶段可以通过该 GUID 在 HOB 中找到对应数据块。内存中一系列的 HOB 组成了 HOB List
。
如下图所示:
HOB的结构列表 HOB List
HOB 是非常重要数据结构,在 CAR 时期将会初始化好,UEFI 的早期的堆栈都基于HOB,
PEI 阶段会创建大量的 HOB,包含板级数据(UBA), silicon Hob(Memmap, RC resource), Setup,当进入PEI PostMem phase
后,CAR 里的 HOB 会 shadow 到 Mem, 当进入 DXE (也就是PeiMain 调用了TempPtr.DxeIpl->Entry
) 后,这些 HOB 会被 DXE driver 大量采用, 用来创建 Protocol 或者其他初始化。
由 PEI 阶段进入 DXE 阶段,PeiCore 中传递 HOB 的代码
HOB 在 PEI 阶段会被修改(写),在 DXE 和 SMM 中会被使用,原则上不推荐SMM修改 HOB。创建的单个HOB Size最大为 64K
ASSERT (DataLength <= (0xFFF8 - sizeof (EFI_HOB_GUID_TYPE)));
// Make sure that data length is not too long.
需要大数据量的HOB需要注意。
HOB 在PEI到 DXE 传送信息的过程遵循 one Producer to one Consumer 的模式,即在PEI阶段,一个 PEIM 创建一个 HOB,在 DXE 阶段,一个 DXE Driver 使用那个 HOB 并且把 HOB 相关的信息传送给其他的需要这些信息的 DXE 组件。
因此,只有 PEI Phase 才允许增加或改动这些 HOBs,当 HOB List 被传送给 DXE Phase 之后,他们就是只读的(Read Only)。
一个只读的 HOB List 的延伸就是 Handoff 信息,比如 Boot Mode,必须以别的方式来处理。比如,DXE Phase 想要产生一个 Recovery 条件,它不能 update Boot Mode,而是通过使用特殊方式的 reset call 来实现。
HOB list 是在 PEI Phase 被建立的,它存在于已经 present,initialized,tested
的Memory 中。一旦最初的 HOB List 被创建,物理内存就不能被 remapped
, interleaved
, 或者被后来的程序 moved
。
第一次进入 PeiCore 的时候,还是 CAR 阶段,内存没有初始化,InitializeMemoryServices
中调用 PeiCoreBuildHobHandoffInfoTable
开启 HOB 创建;位置在Car中;
PeiCore() 进入 InitializeMemoryServices()
InitializeMemoryServices() 进入 PeiCoreBuildHobHandoffInfoTable()
网上会找到很多资料说:
如何增加一个新的HOB到HOB List中?
PEI Phase(HOB Producer Phase)肯定包含一个指向PHIT HOB(这是HOB List的开始)的指针,然后遵循以下的步骤:
-
确定NewHobSize,即确定要创建的HOB的大小(以Byte为单位)
-
确定是否有足够的空闲内存分配给新的HOB(NewHobSize <= (PHIT->EfiFreeMemoryTop - PHIT->EfiFreeMemoryBottom))
-
在(PHIT->EfiFreeMemoryBottom)处构建HOB
-
设置PHIT->EfiFreeMemoryBottom = PHIT->EfiFreeMemoryBottom + NewHobSize~~
但是在开发中,其实我们不必操作 PHIT
中的各个堆栈指针,已经做好的有很多轮子。
代码
说了那么多,我们肯定是面向 API 编程,不用费劲心思去自己对HOB List中 PHIT的各种操作。
- 在PEI阶段中,需要写一个PEIM,来创建一个HOB。HOB不建议放内存地址,所以这里没用字符数组。

#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/PeimEntryPoint.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Pi/PiHob.h>
#include <Pi/PiPeiCis.h>
EFI_GUID gEfiMyPeimHobGuid = {0x34ad055e, 0x1c21, 0x4b06, {0x80, 0x2f, 0xcd, 0x8a, 0x34, 0x2b, 0xa8, 0xc9}};
// define a data struct for HOB Create
typedef struct _MY_PEIM_HOB
{
EFI_HOB_GUID_TYPE EfiHobGuidType;
CHAR8 c0;
CHAR8 c1;
CHAR8 c2;
CHAR8 c3;
CHAR8 c4;
CHAR8 c5;
CHAR8 c6;
CHAR8 c7;
CHAR8 c8;
CHAR8 c9;
} MY_PEIM_HOB;
// PEI Module Entry Function
EFI_STATUS
EFIAPI
MyPeimHobEntry(
IN EFI_PEI_FILE_HANDLE FileHandle,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] MyPeimHobEntry Start..\n"));
MY_PEIM_HOB *Hob = NULL;
// create HOB by PeiServices
Status = PeiServicesCreateHob (
EFI_HOB_TYPE_GUID_EXTENSION,
sizeof(MY_PEIM_HOB),
&Hob);
// fill the HOB data [Guid + 'Hello HOB!']
if (!EFI_ERROR(Status))
{
Hob->EfiHobGuidType.Name = gEfiMyPeimHobGuid;
Hob->c0 = 'H';
Hob->c1 = 'e';
Hob->c2 = 'l';
Hob->c3 = 'l';
Hob->c4 = 'o';
Hob->c5 = ' ';
Hob->c6 = 'H';
Hob->c7 = 'O';
Hob->c8 = 'B';
Hob->c9 = '!';
}
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] MyPeimHobEntry End..\n"));
return EFI_SUCCESS;
}
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyPeimHob
FILE_GUID = e1223900-9e9a-4a7d-8835-ff4f54f5cff9
MODULE_TYPE = PEIM
VERSION_STRING = 1.0
ENTRY_POINT = MyPeimHobEntry
[Sources]
MyPeimHob.c
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec
[LibraryClasses]
BaseLib
PeimEntryPoint
BaseMemoryLib
DebugLib
PeiServicesLib
[depex]
TRUE
-
在DXE阶段中,需要写一个DXE Driver,来获得之前创建好的HOB,并打印出来。
#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Pi/PiBootMode.h>
#include <Pi/PiHob.h>
#include <Library/HobLib.h>
#include <Library/UefiBootServicesTableLib.h>EFI_GUID gEfiMyPeimHobGuid = {0x34ad055e, 0x1c21, 0x4b06, {0x80, 0x2f, 0xcd, 0x8a, 0x34, 0x2b, 0xa8, 0xc9}};
typedef struct _MY_PEIM_HOB
{
EFI_HOB_GUID_TYPE EfiHobGuidType;
CHAR8 c0;
CHAR8 c1;
CHAR8 c2;
CHAR8 c3;
CHAR8 c4;
CHAR8 c5;
CHAR8 c6;
CHAR8 c7;
CHAR8 c8;
CHAR8 c9;
} MY_PEIM_HOB;EFI_STATUS
EFIAPI
MyDxeHobEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status = EFI_SUCCESS;
DEBUG ((EFI_D_ERROR , "[MyHelloWorldHob] MyDxeHobEntry Start..\n"));
MY_PEIM_HOB *Hob;// get the first Hob connected with [gEfiMyPeimHobGuid] by Guid.
Hob = GetFirstGuidHob (&gEfiMyPeimHobGuid);// parse the hob and print debug info
if ( Hob != NULL)
{
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] MyDxeHob Guid:%g\n", Hob->EfiHobGuidType.Name));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c0));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c1));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c2));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c3));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c4));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c5));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c6));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c7));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c8));
DEBUG ((EFI_D_ERROR, "[MyHelloWorldHob] %c \n", Hob->c9));
}DEBUG ((EFI_D_ERROR , "[MyHelloWorldHob] MyDxeHobEntry End..\n"));
return Status;
}[Defines]
INF_VERSION = 0x00010006
BASE_NAME = MyDxeHob
FILE_GUID = 0760c3a1-a98e-4d86-8102-160d49e29e51
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = MyDxeHobEntry[Sources]
MyDxeHob.c[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
MdeModulePkg/MdeModulePkg.dec[LibraryClasses]
UefiDriverEntryPoint
BaseLib
BaseMemoryLib
DebugLib
PrintLib
DevicePathLib
UefiBootServicesTableLib
MemoryAllocationLib
UefiLib
HobLib[depex]
TRUE -
根据PEIM和DXE Driver的类型,填写OvmfPkgX64.fdf和OvmfPkgX64.dsc,来编译写好的两个模块,具体
在OvmfPkgX64.fdf


在OvmfPkgX64.dsc
中

最后build一下,然后再在qemu目录下创建setup-qemu-x64.bat文件,里面内容
"D:\Program Files\qemu\qemu-system-x86_64.exe" -bios "D:\edk2\edk2\Build\OvmfX64\DEBUG_VS2019\FV\OVMF.fd" -M "pc" -m 256 -cpu "qemu64" -boot order=dc -serial stdio -hda fat:rw:D:\edk2\ovmf\esp -net none
其中...\ovmf\esp文件夹需要自己创建,或者改成自己想挂载的路径下。
在qemu目录下执行qemu>setup-qemu-x64.bat | findstr MyHelloWorldHob
查看结果

在这个例子里用GUID直接找HOB,实际开发中,会使用GetHobList()找到HOB头,然后循环调用GetNextHob()来找对应类型的HOB,可以参考EDK2中的原生代码,全局搜索GetNextHob()这个函数就能找到。篇幅原因,这里先Todo.......
后记:HOB 是如何跨阶段传递的?
1. 在 Cache-As-RAM 中创建
PEI 阶段在 DRAM 初始化之前运行,因此它使用 Cache-As-RAM(CAR) 作为临时内存。
HOB 就是在这个 CAR 中被创建的。
在早期 PEI 阶段,HOB 是保存在这个临时的栈空间里的。
2. 内存初始化之后,迁移 HOB
当 DRAM 初始化完成之后,PEI Core 会分配一块真实的内存区域作为 永久内存池(Permanent Memory)
。
此时,PEI Core 会把已经存在于 CAR 中的 HOB 数据复制到新分配的 DRAM 区域。
在 PeiMain 中,PeiDispatcher() 会调用一个专门用于初始化永久内存的PEIM,这个PEIM根据你所编译的Pkg不同而不同。这个PEIM会进行内存初始化。
这一步叫做 HOB 的重定位(HOB Relocation)。
内核会更新 HOB list 的起始地址指针。
3. DXE 阶段读取 HOB
当转交控制权给 DXE Loader 时,PEI 会通过 EFI_PEI_HOB_POINTER
将 HOB list 的新地址传递过去。
DXE Core 通过这个地址获取 HOB 列表,从而继续处理。