将EFI内存映射转换为E820映射

时间:2013-07-11 10:45:50

标签: linux x86 bootloader bios efi

我是Linux新手并且学习了Linux如何了解可用的物理内存。我知道有一些BIOS系统调用int 0x15,它将为你提供E20内存映射。

现在我找到了一段代码,其中说明了将EFI内存映射转换为E820内存映射的定义。上面的意思是什么?

这意味着主板固件是否基于EFI,但由于此代码在x86上运行,我们需要将其转换为E820内存映射

如果是这样,x86是否只知道E820的内存映射?

E820和EFI内存映射有什么区别?

期待得到详细的答案。

1 个答案:

答案 0 :(得分:5)

在这两种情况下,您所拥有的是您的固件(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获取内存映射的相关部分是here

其他要点

由于多种原因,操作系统需要了解哪些内存映射到哪里。一种是避免使用固件服务所在的物理内存,而另一种是与使用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的通用格式并不是非常困难。