在这两种情况下,您所拥有的是您的固件(BIOS 或 EFI),它负责检测实际插入了哪些内存(以及多少),以及需要以某种格式了解此信息的操作系统。
这是否意味着底层主板固件是基于 EFI 的,但由于此代码在 x86 上运行,我们需要将其转换为 E820 内存映射
您在这里的困惑是 EFI 和 x86 不兼容 - 它们不是。EFI 固件有自己的报告可用内存的机制——具体来说,您可以使用GetMemoryMap
引导服务(在调用之前ExitBootServices
)从固件中检索内存映射。但是,至关重要的是,此内存映射采用 EFI 固件希望报告的格式 ( EFI_MEMORY_DESCRIPTOR
) 而不是 E820。在这种情况下,您不会再尝试int 15h
,因为您已经拥有所需的信息。
我怀疑 Linux 内核所做的是使用 E820 格式作为 x86 架构上内存的内部表示。但是,在启动 EFI 时,内核必须使用 EFI 固件启动服务,而是选择转换它得到的答案为 E820 格式。
对于您正在编写的内核来说,这不是必要的事情。您只需要知道内存是如何映射的。
某些引导加载程序也会为您提供此信息,例如 GRUB。多重引导规范的一部分允许您指示引导加载程序它必须向您的内核提供此信息。
有关这方面的更多信息,永远有用的osdev wiki有代码示例等。从 grub 获取内存映射的相关部分在这里。
进一步的观点:
出于多种原因,操作系统需要了解哪些内存被映射到哪里。一种是避免使用固件服务所在的物理内存,另一种是用于与与 CPU 共享内存的设备进行通信。视频缓冲区就是一个常见的例子。
其次,在 EFI 中列出内存映射并不太难。如果你还没有发现它,一些固件附带的 UEFI shell 有一个memmap
命令来显示内存映射。如果你想自己实现这个,一个快速而肮脏的方法看起来像这样:
EFI_STATUS EFIAPI PrintMemoryMap(EFI_SYSTEM_TABLE* SystemTable)
{
EFI_STATUS status = EFI_SUCCESS;
UINTN MemMapSize = sizeof(EFI_MEMORY_DESCRIPTOR)*16;
UINTN MemMapSizeOut = MemMapSize;
UINTN MemMapKey = 0; UINTN MemMapDescriptorSize = 0;
UINT32 MemMapDescriptorVersion = 0;
UINTN DescriptorCount = 0;
UINTN i = 0;
uint8_t* buffer = NULL;
EFI_MEMORY_DESCRIPTOR* MemoryDescriptorPtr = NULL;
do
{
buffer = AllocatePool(MemMapSize);
if ( buffer == NULL ) break;
status = gBS->GetMemoryMap(&MemMapSizeOut, (EFI_MEMORY_DESCRIPTOR*)buffer,
&MemMapKey, &MemMapDescriptorSize, &MemMapDescriptorVersion);
Print(L"MemoryMap: Status %x\n", status);
if ( status != EFI_SUCCESS )
{
FreePool(buffer);
MemMapSize += sizeof(EFI_MEMORY_DESCRIPTOR)*16;
}
} while ( status != EFI_SUCCESS );
if ( buffer != NULL )
{
DescriptorCount = MemMapSizeOut / MemMapDescriptorSize;
MemoryDescriptorPtr = (EFI_MEMORY_DESCRIPTOR*)buffer;
Print(L"MemoryMap: DescriptorCount %d\n", DescriptorCount);
for ( i = 0; i < DescriptorCount; i++ )
{
MemoryDescriptorPtr = (EFI_MEMORY_DESCRIPTOR*)(buffer + (i*MemMapDescriptorSize));
Print(L"Type: %d PhsyicalStart: %lx VirtualStart: %lx NumberofPages: %d Attribute %lx\n",
MemoryDescriptorPtr->Type, MemoryDescriptorPtr->PhysicalStart,
MemoryDescriptorPtr->VirtualStart, MemoryDescriptorPtr->NumberOfPages,
MemoryDescriptorPtr->Attribute);
}
FreePool(buffer);
}
return status;
}
这是一个相当简单的功能。GetMemoryMap
如果你没有传入足够大的缓冲区,我们会痛苦地抱怨,所以我们会不断增加缓冲区的大小,直到我们有足够的空间。然后我们循环打印。请注意,sizeof(EFI_MEMORY_DESCRIPTOR)
实际上这不是输出缓冲区中结构之间的区别 - 使用上面显示的返回大小计算,否则您最终会得到一个比实际更大的表(并且地址空间看起来都是错误的)。
从该表中确定 E820 的通用格式并不难。