一、MemoryMap简介
首先讲一下什么是MemoryMap?
内存映射(Memory Mapping)是一种将文件内容映射到进程的虚拟地址空间的技术。在这种机制下,文件可以视为内存的一部分,从而允许程序直接对这部分内存进行读写操作,而无需传统的I/O调用。内存映射缓冲区利用虚拟内存机制,让操作系统将一部分磁盘文件映射到进程地址空间的一块连续区域当中。操作系统负责管理内存页的加载和卸载,应用程序只需要访问这块内存区域即可,从而避免了频繁的磁盘I/O操作和多余的系统调用。
UEFI的内存映射
- UEFI内存映射是由UEFI固件提供的,主要用于在启动过程中描述系统的内存布局。它告诉操作系统和启动软件哪些内存区域是可用的,哪些是保留的。
- 实现:UEFI内存映射通常是在系统启动的早期阶段由UEFI固件创建的,它包括了系统内存的详细信息,如内存大小、类型和状态。
UEFI MemoryMap指的是UEFI运行时环境中的一个数据结构,它包含了当前系统内存的详细信息.MemoryMap记录了系统内存的布局,包括所有可用的区域、内存类型、属性以及使用情况。
内存类型:MemoryMap中的每个内存区域都有一个与之关联的类型
在UEFI中引入的内存类型由以下几种:
typedef enum {
EfiReservedMemoryType, //没有用到的
EfiLoaderCode, //已加载的UEFI应用程序的代码部分
EfiLoaderData, //已加载的UEFI应用程序的数据部分和默认数据分配
EfiBootServicesCode, //已加载的 Boot Services 驱动程序的代码部分
EfiBootServicesData, //已加载的 Boot Services 驱动程序的数据部分
EfiRuntimeServicesCode, //已加载的Runtime Services驱动的代码部分
EfiRuntimeServicesData, //已加载的Runtime Services驱动的数据部分
EfiConventionalMemory, //空闲(未分配)内存
EfiUnusableMemory, //检测到错误的内存
EfiACPIReclaimMemory, //保存ACPI表的内存
EfiACPIMemoryNVS, //为固件保留的地址空间
EfiMemoryMappedIO, //系统固件使用它来请求操作系统将内存映射的 IO 区域映射到虚拟地址,以便 EFI 运行时服务可以访问它。
EfiMemoryMappedIOPortSpace, //系统内存映射的 IO 区域,处理器用它来将内存周期转换为 IO 周期。
EfiPalCode, //固件为作为处理器一部分的代码保留的地址空间。
EfiPersistentMemory, //一个内存区域,它作为 EfiConventionalMemory 运行,但它也恰好支持字节寻址的非易失性。
EfiUnacceptedMemoryType, //描述未被接受的系统内存的内存区域。
EfiMaxMemoryType,
//
// +---------------------------------------------------+
// | 0..(EfiMaxMemoryType - 1) - Normal memory type |
// +---------------------------------------------------+
// | EfiMaxMemoryType..0x6FFFFFFF - Invalid |
// +---------------------------------------------------+
// | 0x70000000..0x7FFFFFFF - OEM reserved |
// +---------------------------------------------------+
// | 0x80000000..0xFFFFFFFF - OS reserved |
// +---------------------------------------------------+
//
MEMORY_TYPE_OEM_RESERVED_MIN = 0x70000000,
MEMORY_TYPE_OEM_RESERVED_MAX = 0x7FFFFFFF,
MEMORY_TYPE_OS_RESERVED_MIN = 0x80000000,
MEMORY_TYPE_OS_RESERVED_MAX = 0xFFFFFFFF
} EFI_MEMORY_TYPE;
内存属性:写保护、都保护、执行保护、非易失性、只读、运行时等等等等
内存区域:
使用情况:
UEFI MemoryMap的主要用途包括:
操作系统加载器:操作系统加载器在启动过程中会使用MemoryMap来确定哪些内存是可用的,以及如何配置和使用这些内存。
UEFI应用程序:UEFI应用程序可以使用MemoryMap来了解内存布局,以便它们可以请求和分配内存。
内存管理:UEFI固件使用MemoryMap来管理内存分配,确保不同组件之间不会发生内存冲突。
UEFI规范中用结构体EFI_MEMORY_DESCRIPTOR来描述内存区域,在UEFI环境中,EFI_MEMORY_DESCRIPTOR被用来标识内存映射(Memory Map)中的一个条目,即内存映射的每一项都是一个EFI_MEMORY_DESCRIPTOR结构。UEFI应用程序和驱动程序可以使用EFI_BOOT_SERVICES.GetMemoryMap服务来获取当前的内存映射,这个映射是一个EFI_MEMORY_DESCRIPTOR数组。通过便利这个数组,应用程序可以了解系统的内存布局,并据此进行内存管理操作。
EFI_MEMORY_DESCRIPTOR的代码原型为:
typedef struct {
UINT32 Type; // 描述内存的类型
EFI_PHYSICAL_ADDRESS PhysicalStart; // 内存区域的物理起始地址
EFI_VIRTUAL_ADDRESS VirtualStart; // 内存区域的虚拟起始地址(如果适用)
UINT64 NumberOfPages; // 内存区域包含的页数
UINT64 Attribute; // 内存区域的属性
} EFI_MEMORY_DESCRIPTOR;
二、获取UEFI MemoryMap
UEFI的内存服务提供了EFI_GET_MEMORY_MAP.GetMemoryMap接口来获取当前的内存映射,其代码原型为:
//返回当前的Memory Map
@retval EFI_SUCCESS The memory map was returned in the MemoryMap buffer.
内存映射已经返回到Memmorymap缓冲区
@retval EFI_BUFFER_TOO_SMALL The MemoryMap buffer was too small. The current buffer size
needed to hold the memory map is returned in MemoryMapSize.
表示MemmoryMap缓冲区太小了,目前需要用来保存内存映射的缓冲区大小已经返回到MemmoryMapSize中。
@retval EFI_INVALID_PARAMETER 1) MemoryMapSize is NULL.
2) The MemoryMap buffer is not too small and MemoryMap is
NULL.
表示MemorymapSize为空,
或memmoryMap缓冲区并不小,但是MemMorymap是空的
typedef
EFI_STATUS
(EFIAPI *EFI_GET_MEMORY_MAP)(
IN OUT UINTN *MemoryMapSize, //输入,输出参数,指向memorymap缓冲区大小的指针,单位为字节。在输入时,这是调用者分配的缓冲区大小。在输出时,如果缓冲区足够大,这是固件返回的缓冲区大小;如果缓冲区过小,则是包含映射所需要的缓冲区大小。
OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, //输出参数,指向固件存放当前内存映射的缓冲区的指针
OUT UINTN *MapKey, //输出参数,指向固件返回当前内存映射键的位置的指针
OUT UINTN *DescriptorSize, //输出参数,指向固件返回单个EFI_MEMORY_DESCRIPTOR的大小(以字节为单位)的位置的指针。
OUT UINT32 *DescriptorVersion //指向固件返回与 EFI_MEMORY_DESCRIPTOR 关联的版本号的位置的指针。
);
如果GetMemoryMap的返回值为EFI_BUFFER_TOO_SMALL,说明MemoryMap缓冲区太小,已经将用来保存内存映射的缓冲区大小返回到MemoryMapSize中了。接下来就需要使用AllocatePool重新分配缓冲区。其代码原型为
/**
Allocates a buffer of type EfiBootServicesData.
按照字节数分配EfiBootServicesData类型的内存,并返回一个指向所分配缓冲区的指针
**/
VOID *
EFIAPI
AllocatePool (
IN UINTN AllocationSize //需要分配的字节数
);
完整代码如下:
#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
EFI_STATUS
EFIAPI
MyMemMapEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN EfiMemoryMapSize;
EFI_MEMORY_DESCRIPTOR *EfiMemoryMap;
EFI_MEMORY_DESCRIPTOR *EfiMemoryMapEnd;
EFI_MEMORY_DESCRIPTOR *EfiEntry;
UINTN EfiMapKey;
UINTN EfiDescriptorSize;
UINT32 EfiDescriptorVersion;
UINT64 BootServicesDataPage = 0;
UINT64 BootServicesCodePage = 0;
UINT64 ACPIReclaimMemoryPage = 0;
UINT64 ACPIMemoryNVSPage = 0;
UINT64 ReservedMemoryTypePage = 0;
UINT64 RuntimeServicesCodePage = 0;
UINT64 RuntimeServicesDataPage = 0;
UINT64 LoaderCodePage = 0;
UINT64 LoaderDataPage = 0;
UINT64 MaxMemoryTypePage = 0;
UINT64 ConventionalMemoryPage = 0;
UINT64 UnusableMemoryPage = 0;
UINT64 MemoryMappedIOPage = 0;
UINT64 MemoryMappedIOPortSpacePage = 0;
UINT64 PalCodePage = 0;
UINT64 PersistentMemoryPage = 0;
EfiMemoryMapSize = 0;
EfiMemoryMap = NULL;
Status = gBS->GetMemoryMap ( //获取MemoryMap
&EfiMemoryMapSize,
EfiMemoryMap,
&EfiMapKey,
&EfiDescriptorSize,
&EfiDescriptorVersion
);
ASSERT (Status == EFI_BUFFER_TOO_SMALL); //过小重新分配
do {
EfiMemoryMap = AllocatePool (EfiMemoryMapSize);
if (EfiMemoryMap == NULL){
DEBUG ((EFI_D_ERROR, "ERROR!! Null Pointer returned by AllocatePool ()\n"));
ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES);
return Status;
}
Status = gBS->GetMemoryMap (
&EfiMemoryMapSize,
EfiMemoryMap,
&EfiMapKey,
&EfiDescriptorSize,
&EfiDescriptorVersion
);
if (EFI_ERROR(Status)) {
FreePool (EfiMemoryMap); //释放掉前面分配的
}
} while (Status == EFI_BUFFER_TOO_SMALL); //一直小一直分配,直到不再小
DEBUG((DEBUG_ERROR | DEBUG_PAGE,"[CSDN] EfiMemoryMapSize=0x%x EfiDescriptorSize=0x%x EfiMemoryMap=0x%x \n", EfiMemoryMapSize, EfiDescriptorSize, (UINTN)EfiMemoryMap));
EfiMemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)EfiMemoryMap + EfiMemoryMapSize); //映射区结尾
EfiEntry = EfiMemoryMap; //映射区的开始地址
DEBUG((DEBUG_ERROR | DEBUG_PAGE,"===========================%S============================== Start\n", L"CSDN MemMap"));
while (EfiEntry < EfiMemoryMapEnd) { //循环获取各内存类型的信息
if (EfiEntry->Type == EfiReservedMemoryType){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiReservedMemoryType %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
ReservedMemoryTypePage = ReservedMemoryTypePage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiLoaderCode){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiLoaderCode %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
LoaderCodePage = LoaderCodePage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiLoaderData){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiLoaderData %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
LoaderDataPage = LoaderDataPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiBootServicesCode){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiBootServicesCode %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
BootServicesCodePage = BootServicesCodePage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiBootServicesData){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiBootServicesData %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
BootServicesDataPage = BootServicesDataPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiRuntimeServicesCode){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiRuntimeServicesCode %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
RuntimeServicesCodePage = RuntimeServicesCodePage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiRuntimeServicesData){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiRuntimeServicesData %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
RuntimeServicesDataPage = RuntimeServicesDataPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiConventionalMemory){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiConventionalMemory %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
ConventionalMemoryPage = ConventionalMemoryPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiUnusableMemory){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiUnusableMemory %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
UnusableMemoryPage = UnusableMemoryPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiACPIReclaimMemory){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiACPIReclaimMemory %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
ACPIReclaimMemoryPage = ACPIReclaimMemoryPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiACPIMemoryNVS){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiACPIMemoryNVS %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
ACPIMemoryNVSPage = ACPIMemoryNVSPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiMemoryMappedIO){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiMemoryMappedIO %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
MemoryMappedIOPage = MemoryMappedIOPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiMemoryMappedIOPortSpace){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiMemoryMappedIOPortSpace %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
MemoryMappedIOPortSpacePage = MemoryMappedIOPortSpacePage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiPalCode){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiPalCode %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
PalCodePage = PalCodePage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiPersistentMemory){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiPersistentMemory %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
PersistentMemoryPage = PersistentMemoryPage + EfiEntry->NumberOfPages;
}else if (EfiEntry->Type == EfiMaxMemoryType){
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiMaxMemoryType %3d %16lx pn %16lx \n", EfiEntry->Type, EfiEntry->PhysicalStart, EfiEntry->NumberOfPages));
MaxMemoryTypePage = MaxMemoryTypePage + EfiEntry->NumberOfPages;
}
EfiEntry = NEXT_MEMORY_DESCRIPTOR(EfiEntry, EfiDescriptorSize);
}
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiBootServicesData Page Number: %16lx %dMB\n", BootServicesDataPage, BootServicesDataPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiBootServicesCode Page Number: %16lx %dMB\n", BootServicesCodePage, BootServicesCodePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiACPIReclaimMemory Page Number: %16lx %dMB\n", ACPIReclaimMemoryPage, ACPIReclaimMemoryPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiACPIMemoryNVS Page Number: %16lx %dMB\n", ACPIMemoryNVSPage, ACPIMemoryNVSPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiReservedMemoryType Page Number: %16lx %dMB\n", ReservedMemoryTypePage, ReservedMemoryTypePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiRuntimeServicesCode Page Number: %16lx %dMB\n", RuntimeServicesCodePage, RuntimeServicesCodePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiRuntimeServicesData Page Number: %16lx %dMB\n", RuntimeServicesDataPage, RuntimeServicesDataPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiLoaderCode Page Number: %16lx %dMB\n", LoaderCodePage, LoaderCodePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiLoaderData Page Number: %16lx %dMB\n", LoaderDataPage, LoaderDataPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiMaxMemoryType Page Number: %16lx %dMB\n", MaxMemoryTypePage, MaxMemoryTypePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiConventionalMemory Page Number: %16lx %dMB\n", ConventionalMemoryPage, ConventionalMemoryPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiUnusableMemory Page Number: %16lx %dMB\n", UnusableMemoryPage, UnusableMemoryPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiMemoryMappedIO Page Number: %16lx %dMB\n", MemoryMappedIOPage, MemoryMappedIOPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiMemoryMappedIOPortSpace Page Number: %16lx %dMB\n", MemoryMappedIOPortSpacePage, MemoryMappedIOPortSpacePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiPalCode Page Number: %16lx %dMB\n", PalCodePage, PalCodePage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE, "[CSDN] EfiPersistentMemory Page Number: %16lx %dMB\n", PersistentMemoryPage, PersistentMemoryPage*4/1024));
DEBUG((DEBUG_ERROR | DEBUG_PAGE,"===========================%S============================== End\n", L"CSDN MemMap"));
return Status;
}
编写inf代码,编译,运行efi文件
部分运行结果如下:
显示内存类型 物理地址 页数
这是一个memmap dump demo,使用该demo可以得出当前BIOS的mem region分配情况,在shell下同样可以使用内置的memmap 命令来查看当前memory map。