UEFI开发实战:如何用GetMemoryMap函数精准获取内存布局(附完整代码解析)

5G Massive MIMO实战指南:从基站配置到终端优化全解析 本文是一份面向网络工程师的5G Massive MIMO实战指南,深入解析从基站配置到终端优化的全流程。文章摒弃理论空谈,聚焦真实部署场景,详细拆解了在密集城区、郊区农村及大型场馆等不同环境下,如何通过精准配置波束、优化传输模式和CSI反馈,将Massive MIMO的纸面性能转化为用户可感知的高速稳定体验,并提供了关键性能监控与故障排查思路。 阅读详情

UEFI开发实战:如何用GetMemoryMap函数精准获取内存布局(附完整代码解析)

最近在调试一个UEFI驱动时,遇到了一个棘手的内存访问问题。系统在特定阶段会随机崩溃,排查了半天,最终发现是驱动尝试访问了一块已被标记为保留的内存区域。这让我重新审视了UEFI环境下内存管理的重要性。对于固件和驱动开发者而言,系统内存的实时布局图,就像是城市的地下水管和电路图,不搞清楚就动工,随时可能“挖断电缆”。而GetMemoryMap函数,正是UEFI为我们提供的这张至关重要的“内存地图”的读取工具。它不是简单的信息罗列,其背后涉及引导服务、运行时服务的状态转换,以及操作系统加载前的关键资源规划。今天,我们就抛开理论,直接从代码入手,拆解如何安全、高效地调用GetMemoryMap,并深度解析获取到的每一个字节所代表的意义,让你在UEFI开发中,对内存了如指掌。

1. 理解UEFI内存地图:不只是地址列表

在传统BIOS时代,获取内存信息通常依赖于中断调用,信息相对简单,主要是可用的物理内存范围。而UEFI引入的GetMemoryMap服务,则提供了一个动态的、类型化的内存描述符数组。这不仅仅是地址的起止,更是对每一块内存区域“身份”和“用途”的精确标注。

为什么内存地图是动态的? 因为在UEFI启动过程中,内存的使用情况是不断变化的。例如:

  • UEFI自身会占用一部分内存作为代码和数据区。
  • 加载的每个UEFI应用程序或驱动都会申请内存。
  • 某些硬件(如显卡)会通过“预留”方式声明其帧缓冲区所需的内存。
  • 在调用ExitBootServices()之前,内存地图可能会因为驱动的加载和卸载而改变。

因此,GetMemoryMap返回的是一个快照,反映了调用那一瞬间整个系统的内存视图。这个视图的核心数据结构是EFI_MEMORY_DESCRIPTOR。理解这个结构体是解读内存地图的关键。

#define EFI_MEMORY_DESCRIPTOR_VERSION 1
typedef struct {
  UINT32                Type;           // 内存类型
  EFI_PHYSICAL_ADDRESS  PhysicalStart;  // 物理起始地址
  EFI_VIRTUAL_ADDRESS   VirtualStart;   // 虚拟起始地址
  UINT64                NumberOfPages;  // 页数
  UINT64                Attribute;      // 内存属性(如缓存策略)
} EFI_MEMORY_DESCRIPTOR;

我们来重点看看Type字段,它定义了内存区域的用途,是开发者最需要关心的部分。UEFI规范定义了几十种内存类型,但最核心的几种如下表所示:

内存类型 (Type)值 (示例)含义与典型用途
EfiReservedMemoryType0保留内存。硬件专用或系统保留,绝对不可被分配或使用。如ACPI NVS区域、芯片组保留区域。
EfiLoaderCode1加载器代码。操作系统加载器(如GRUB)的代码段所在内存。
EfiLoaderData2加载器数据。操作系统加载器的数据段、堆栈所在内存。
EfiBootServicesCode3引导服务代码。UEFI固件自身及驱动提供的引导服务例程代码。
EfiBootServicesData4引导服务数据。UEFI固件及驱动的全局变量、堆内存等数据区。
EfiRuntimeServicesCode5运行时服务代码。操作系统运行后仍需调用的UEFI服务(如GetTime、ResetSystem)代码。
EfiRuntimeServicesData6运行时服务数据。运行时服务所需的持久化数据区。
EfiConventionalMemory7可用内存。操作系统可以自由支配的物理内存。这是OS内核和应用程序的主要舞台。
EfiUnusableMemory8不可用内存。已损坏或有硬件缺陷的内存,必须避免使用。
EfiACPIReclaimMemory9ACPI可回收内存。ACPI表暂存区,OS读取ACPI表后,可回收此区域作为可用内存。
EfiACPIMemoryNVS10ACPI NVS内存。供ACPI固件组件使用,操作系统应保留。
EfiMemoryMappedIO11内存映射I/O。映射到内存空间的设备寄存器,访问即操作硬件。

注意:EfiBootServicesCode和EfiBootServicesData这两类内存在操作系统调用ExitBootServices()之后,会被回收并转变为EfiConventionalMemory。这意味着你的驱动如果在这之后还试图访问之前申请的引导服务数据,会导致未定义行为(通常是系统崩溃)。这是UEFI内存管理的一个关键分水岭。

2. GetMemoryMap调用实战:两步法与错误处理

直接调用GetMemoryMap并期望一次成功是不现实的。由于我们无法预知当前系统中有多少个内存描述符,UEFI设计了一个经典的“两步调用”模式。这个模式在UEFI编程中非常常见,用于处理输出缓冲区大小未知的情况。

2.1 核心参数解析

首先,我们看看函数原型:

EFI_STATUS EFIAPI gBS->GetMemoryMap (
  IN OUT UINTN                  *MemoryMapSize, // 输入为缓冲区大小,输出为实际所需大小/实际大小
  IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,     // 存放描述符数组的缓冲区
  OUT UINTN                    *MapKey,        // 内存地图的“版本号”或“钥匙”
  OUT UINTN                    *DescriptorSize, // 单个描述符的实际大小(可能带填充)
  OUT UINT32                   *DescriptorVersion // 描述符结构版本
);
  • MemoryMapSize: 这是一个输入输出参数。首次调用时,我们将其指向的变量设为0(或一个较小的估计值),函数会返回EFI_BUFFER_TOO_SMALL,并在这个变量中填充存储完整内存地图所需的字节数。第二次调用时,我们分配足够大的缓冲区,并将缓冲区大小通过此参数传入,函数成功后会在此变量中返回实际写入的字节数。
  • MemoryMap: 指向我们分配的缓冲区。首次试探性调用时传入NULL。
  • MapKey: 一个唯一标识符。每次内存地图发生变化(如分配/释放内存),这个键值都会改变。在调用ExitBootServices()时,必须提供调用GetMemoryMap得到的最新MapKey,以证明调用者拥有当前内存布局的最新视图,这是防止竞争条件的安全机制。
  • DescriptorSize: 这是一个极易被忽略但至关重要的参数。UEFI规范允许不同厂商或版本的固件在EFI_MEMORY_DESCRIPTOR结构体尾部添加私有字段。因此,我们不能直接用sizeof(EFI_MEMORY_DESCRIPTOR)来遍历数组。必须使用GetMemoryMap返回的DescriptorSize作为步进值来遍历内存描述符数组。
  • DescriptorVersion: 结构体版本,目前通常是1。

2.2 稳健的调用流程与代码实现

基于以上理解,一个健壮的GetMemoryMap调用流程如下,我将其封装成了一个函数:

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h> // 提供gBS
#include <Library/DebugLib.h>                // 提供DEBUG宏
#include <Library/MemoryAllocationLib.h>     // 提供AllocatePool/FreePool

/**
 * 获取当前系统的UEFI内存地图。
 *
 * @param[out] MemoryMapSize     实际获取到的内存地图数据大小(字节)。
 * @param[out] MemoryMap         指向分配的内存地图缓冲区的指针,调用者使用后需负责释放。
 * @param[out] MapKey            获取到的内存地图键值。
 * @param[out] DescriptorSize    单个内存描述符的大小(字节)。
 * @param[out] DescriptorVersion 描述符结构版本。
 *
 * @retval EFI_SUCCESS           操作成功。
 * @retval Others                操作失败。
 */
EFI_STATUS
GetCurrentMemoryMap (
  OUT UINTN                  *MemoryMapSize,
  OUT EFI_MEMORY_DESCRIPTOR **MemoryMap,
  OUT UINTN                  *MapKey,
  OUT UINTN                  *DescriptorSize,
  OUT UINT32                 *DescriptorVersion
  )
{
  EFI_STATUS Status;
  UINTN      LocalMemoryMapSize;
  UINTN      LocalMapKey;
  UINTN      LocalDescriptorSize;
  UINT32     LocalDescriptorVersion;
  EFI_MEMORY_DESCRIPTOR *LocalMemoryMap;

  // 1. 初始化输出参数为安全值
  if (MemoryMapSize != NULL) {
    *MemoryMapSize = 0;
  }
  if (MemoryMap != NULL) {
    *MemoryMap = NULL;
  }

  // 2. 第一次调用:获取所需缓冲区大小
  LocalMemoryMapSize = 0;
  Status = gBS->GetMemoryMap (
                  &LocalMemoryMapSize,
                  NULL, // 第一次传入NULL
                  &LocalMapKey,
                  &LocalDescriptorSize,
                  &LocalDescriptorVersion
                  );
  // 我们期望的返回状态是 EFI_BUFFER_TOO_SMALL
  if (Status != EFI_BUFFER_TOO_SMALL) {
    DEBUG ((DEBUG_ERROR, "[ERROR] First GetMemoryMap call failed: %r\n", Status));
    return Status; // 可能是其他错误,如参数无效
  }

  // 3. 循环分配和尝试,处理可能的竞争条件
  //    在分配和调用间隙,可能有其他事件(如定时器)分配内存,导致地图变化。
  do {
    // 分配缓冲区,额外增加一个描述符的大小作为安全余量是常见做法
    LocalMemoryMap = AllocatePool (LocalMemoryMapSize + (2 * LocalDescriptorSize));
    if (LocalMemoryMap == NULL) {
      DEBUG ((DEBUG_ERROR, "[ERROR] Failed to allocate pool for memory map.\n"));
      return EFI_OUT_OF_RESOURCES;
    }

    // 第二次(或第N次)调用:尝试获取内存地图
    Status = gBS->GetMemoryMap (
                    &LocalMemoryMapSize,
                    LocalMemoryMap,
                    &LocalMapKey,
                    &LocalDescriptorSize,
                    &LocalDescriptorVersion
                    );

    if (EFI_ERROR (Status)) {
      // 如果失败(例如因为地图在分配后发生了变化),释放缓冲区并重试
      FreePool (LocalMemoryMap);
      // 如果是缓冲区仍然太小,循环会继续
      if (Status != EFI_BUFFER_TOO_SMALL) {
        DEBUG ((DEBUG_WARN, "[WARN] GetMemoryMap failed (not buffer too small): %r\n", Status));
        // 对于其他错误,可以选择返回或重试有限次数,这里直接返回
        return Status;
      }
    }
  } while (Status == EFI_BUFFER_TOO_SMALL); // 只要报告缓冲区不足,就重试

  // 4. 到这里说明Status == EFI_SUCCESS
  //    将局部变量赋值给输出参数
  if (MemoryMapSize != NULL) {
    *MemoryMapSize = LocalMemoryMapSize;
  }
  if (MemoryMap != NULL) {
    *MemoryMap = LocalMemoryMap;
  } else {
    // 如果调用者不想要缓冲区,我们需要释放它
    FreePool (LocalMemoryMap);
  }
  if (MapKey != NULL) {
    *MapKey = LocalMapKey;
  }
  if (DescriptorSize != NULL) {
    *DescriptorSize = LocalDescriptorSize;
  }
  if (DescriptorVersion != NULL) {
    *DescriptorVersion = LocalDescriptorVersion;
  }

  DEBUG ((DEBUG_INFO, "[INFO] Got Memory Map. Size=0x%08lx, DescSize=0x%08lx, MapKey=0x%08lx\n",
          LocalMemoryMapSize, LocalDescriptorSize, LocalMapKey));

  return EFI_SUCCESS;
}

提示:上面的代码中,我在分配缓冲区时增加了 2 * LocalDescriptorSize 的安全余量。这是一个实践经验。因为在多处理器(MP)系统或某些特定固件实现中,在GetMemoryMap调用执行期间,可能会发生内存分配事件(例如处理器间中断),导致实际所需大小比第一次查询时略大。增加一点余量可以避免因极端情况导致的无限循环或失败。

3. 解析与遍历内存描述符数组

成功获取到内存地图缓冲区后,下一步就是遍历它,提取我们需要的信息。关键在于使用正确的步长——DescriptorSize。

/**
 * 遍历并打印内存地图信息。
 *
 * @param[in] MemoryMap         内存地图缓冲区。
 * @param[in] MemoryMapSize     内存地图数据大小(字节)。
 * @param[in] DescriptorSize    单个内存描述符的大小(字节)。
 */
VOID
DumpMemoryMap (
  IN EFI_MEMORY_DESCRIPTOR *MemoryMap,
  IN UINTN                  MemoryMapSize,
  IN UINTN                  DescriptorSize
  )
{
  EFI_MEMORY_DESCRIPTOR *Entry;
  UINTN                  NumEntries;
  UINTN                  Index;
  UINT64                 TotalPages = 0;
  UINT64                 ConventionalPages = 0;

  if (MemoryMap == NULL || MemoryMapSize == 0 || DescriptorSize == 0) {
    DEBUG ((DEBUG_ERROR, "[ERROR] Invalid parameters for DumpMemoryMap.\n"));
    return;
  }

  // 计算描述符的个数
  NumEntries = MemoryMapSize / DescriptorSize;
  DEBUG ((DEBUG_INFO, "\n=== UEFI Memory Map Dump ===\n"));
  DEBUG ((DEBUG_INFO, "Number of memory descriptors: %lu\n", NumEntries));

  // 遍历所有描述符
  Entry = MemoryMap;
  for (Index = 0; Index < NumEntries; Index++) {
    // 计算该描述符的物理结束地址(注意:结束地址是 exclusive 的)
    UINT64 PhysicalEnd = Entry->PhysicalStart + (Entry->NumberOfPages * EFI_PAGE_SIZE);

    DEBUG ((DEBUG_INFO,
            "[%03lu] Type: %02u (%a)  Phys: [0x%016lx - 0x%016lx)  Pages: %6lu  Attr: 0x%016lx\n",
            Index,
            Entry->Type,
            GetMemoryTypeString (Entry->Type), // 需要一个将类型转换为字符串的函数
            Entry->PhysicalStart,
            PhysicalEnd,
            Entry->NumberOfPages,
            Entry->Attribute
            ));

    // 统计总内存和可用内存
    TotalPages += Entry->NumberOfPages;
    if (Entry->Type == EfiConventionalMemory) {
      ConventionalPages += Entry->NumberOfPages;
    }

    // 关键步骤:使用DescriptorSize移动到下一个描述符
    // 不能使用 Entry++ 或 sizeof(EFI_MEMORY_DESCRIPTOR)!
    Entry = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)Entry + DescriptorSize);
  }

  DEBUG ((DEBUG_INFO, "\n=== Summary ===\n"));
  DEBUG ((DEBUG_INFO, "Total Physical Memory:    %lu MB\n",
          (TotalPages * EFI_PAGE_SIZE) / (1024 * 1024)));
  DEBUG ((DEBUG_INFO, "Available Memory (Conv):  %lu MB\n",
          (ConventionalPages * EFI_PAGE_SIZE) / (1024 * 1024)));
}

上面代码中的GetMemoryTypeString函数是一个简单的辅助函数,用于将数字类型转换为可读的字符串,例如:

CONST CHAR8*
GetMemoryTypeString (
  IN EFI_MEMORY_TYPE  Type
  )
{
  switch (Type) {
    case EfiReservedMemoryType: return "Reserved";
    case EfiLoaderCode: return "LoaderCode";
    case EfiLoaderData: return "LoaderData";
    case EfiBootServicesCode: return "BootCode";
    case EfiBootServicesData: return "BootData";
    case EfiRuntimeServicesCode: return "RtCode";
    case EfiRuntimeServicesData: return "RtData";
    case EfiConventionalMemory: return "Conventional";
    case EfiUnusableMemory: return "Unusable";
    case EfiACPIReclaimMemory: return "ACPIReclaim";
    case EfiACPIMemoryNVS: return "ACPINvs";
    case EfiMemoryMappedIO: return "MMIO";
    case EfiMemoryMappedIOPortSpace: return "MMIOPort";
    case EfiPalCode: return "PAL";
    default: return "Unknown";
  }
}

4. 实战整合:在UEFI应用中的完整用例

现在,我们将上述所有步骤整合到一个简单的UEFI应用程序中。这个应用会获取内存地图,分析并打印摘要,然后根据地图信息,安全地分配一块可用内存。

/**
  UEFI应用程序入口点。
**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS            Status;
  UINTN                 MemoryMapSize;
  EFI_MEMORY_DESCRIPTOR *MemoryMap;
  UINTN                 MapKey;
  UINTN                 DescriptorSize;
  UINT32                DescriptorVersion;
  VOID                  *AllocatedBuffer = NULL;
  UINTN                 BufferPages = 16; // 尝试分配16页(64KB)

  // 1. 获取当前内存地图
  Status = GetCurrentMemoryMap (
             &MemoryMapSize,
             &MemoryMap,
             &MapKey,
             &DescriptorSize,
             &DescriptorVersion
             );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "[APP] Failed to get memory map: %r\n", Status));
    return Status;
  }

  // 2. 解析并打印内存地图
  DumpMemoryMap (MemoryMap, MemoryMapSize, DescriptorSize);

  // 3. 利用内存地图信息进行安全的内存分配
  // 思路:遍历地图,找到第一块足够大的EfiConventionalMemory区域。
  Status = FindAndAllocateFromConventional (
             MemoryMap,
             MemoryMapSize,
             DescriptorSize,
             BufferPages,
             &AllocatedBuffer
             );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_WARN, "[APP] Could not allocate %lu pages from conventional memory: %r\n",
            BufferPages, Status));
  } else {
    DEBUG ((DEBUG_INFO, "[APP] Successfully allocated buffer at 0x%p\n", AllocatedBuffer));
    // ... 使用分配的缓冲区 ...
    // 使用完毕后需要释放: gBS->FreePool(AllocatedBuffer);
  }

  // 4. 释放内存地图缓冲区(由GetCurrentMemoryMap分配)
  if (MemoryMap != NULL) {
    FreePool (MemoryMap);
  }

  // 5. 演示MapKey的变化:尝试分配一点内存,再次获取地图,比较Key是否不同。
  DEBUG ((DEBUG_INFO, "\n[APP] Demonstrating MapKey change...\n"));
  UINTN NewMapKey;
  VOID *TempBuffer;
  Status = gBS->AllocatePool (EfiBootServicesData, 1024, &TempBuffer);
  if (!EFI_ERROR (Status)) {
    // 再次快速获取地图,只关心MapKey
    MemoryMapSize = 0;
    Status = gBS->GetMemoryMap (&MemoryMapSize, NULL, &NewMapKey,
                                &DescriptorSize, &DescriptorVersion);
    if (Status == EFI_BUFFER_TOO_SMALL) {
      DEBUG ((DEBUG_INFO, "[APP] Original MapKey: 0x%08lx, New MapKey after allocation: 0x%08lx\n",
              MapKey, NewMapKey));
      if (NewMapKey != MapKey) {
        DEBUG ((DEBUG_INFO, "[APP] MapKey changed! Memory layout was altered.\n"));
      }
    }
    gBS->FreePool (TempBuffer);
  }

  return EFI_SUCCESS;
}

其中,FindAndAllocateFromConventional是一个辅助函数,展示了如何基于内存地图做出智能分配决策:

EFI_STATUS
FindAndAllocateFromConventional (
  IN  EFI_MEMORY_DESCRIPTOR *MemoryMap,
  IN  UINTN                  MemoryMapSize,
  IN  UINTN                  DescriptorSize,
  IN  UINTN                  RequiredPages,
  OUT VOID                  **Buffer
  )
{
  EFI_MEMORY_DESCRIPTOR *Entry;
  UINTN                  NumEntries;
  UINTN                  Index;
  EFI_PHYSICAL_ADDRESS   StartAddress;

  NumEntries = MemoryMapSize / DescriptorSize;
  Entry = MemoryMap;

  for (Index = 0; Index < NumEntries; Index++) {
    // 只关注可用内存
    if (Entry->Type == EfiConventionalMemory) {
      // 检查这块区域是否足够大
      if (Entry->NumberOfPages >= RequiredPages) {
        // 尝试从这块区域的起始地址开始分配
        StartAddress = Entry->PhysicalStart;
        // 使用AllocatePages按页分配,可以指定地址(如果允许)
        // 这里使用AllocateAnyPages让固件选择地址,但我们已经知道有足够空间。
        // 更精确的做法是使用AllocatePages with EfiAllocateAddress,但需要复杂对齐处理。
        // 此处仅作演示,使用简单的AllocatePool。
        // 实际项目中,对于大块、对齐要求高的内存,AllocatePages是更好的选择。
        DEBUG ((DEBUG_INFO, "[ALLOC] Found conventional region at 0x%lx, size %lu pages.\n",
                Entry->PhysicalStart, Entry->NumberOfPages));
        // 实际分配略过,这里假设调用gBS->AllocatePages成功
        // Status = gBS->AllocatePages(AllocateAnyPages, EfiBootServicesData, RequiredPages, &StartAddress);
        // 简单起见,我们直接使用AllocatePool
        return gBS->AllocatePool (EfiBootServicesData, RequiredPages * EFI_PAGE_SIZE, Buffer);
      }
    }
    Entry = NEXT_MEMORY_DESCRIPTOR (Entry, DescriptorSize); // 使用宏或手动计算下一个
  }
  return EFI_NOT_FOUND;
}
// 一个常用的遍历宏定义
#define NEXT_MEMORY_DESCRIPTOR(Ptr, Size) ((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(Ptr) + (Size)))

调试这个应用,你会在串口或UEFI Shell中看到类似下面的输出,这为你呈现了系统启动阶段内存的全景视图:

=== UEFI Memory Map Dump ===
Number of memory descriptors: 42
[000] Type: 03 (BootCode)  Phys: [0x0000000000000000 - 0x0000000000001000)  Pages:     1  Attr: 0x0000000000000000
[001] Type: 07 (Conventional)  Phys: [0x0000000000001000 - 0x000000000009f000)  Pages:   158  Attr: 0x0000000000000000
[002] Type: 04 (BootData)  Phys: [0x000000000009f000 - 0x00000000000a0000)  Pages:     1  Attr: 0x0000000000000000
[003] Type: 07 (Conventional)  Phys: [0x0000000000100000 - 0x0000000007e00000)  Pages: 12672  Attr: 0x0000000000000000
... // 更多条目
[041] Type: 0A (ACPINvs)  Phys: [0x00000000fed20000 - 0x00000000fed21000)  Pages:     1  Attr: 0x0000000000000000

=== Summary ===
Total Physical Memory:    8174 MB
Available Memory (Conv):  8062 MB

5. 高级话题:内存地图在OS加载器中的关键作用

对于UEFI应用程序开发者,尤其是编写操作系统加载器(如GRUB2、systemd-boot或自定义引导程序)的开发者,GetMemoryMap的角色从“信息查看”升级为“资源交接”。

核心任务:调用ExitBootServices()。这是UEFI启动过程中的一个仪式性事件,标志着固件将系统的完全控制权移交给操作系统。在此调用之后,UEFI的引导服务(包括AllocatePool、CreateEvent、gBS表中的大部分函数)将不再可用。而调用ExitBootServices有一个硬性要求:

EFI_STATUS EFIAPI gBS->ExitBootServices (
  IN EFI_HANDLE  ImageHandle,
  IN UINTN       MapKey // 必须传入最新GetMemoryMap得到的MapKey
);

为什么需要MapKey? 这是一个同步机制。设想以下场景:

  1. 加载器调用GetMemoryMap,获得地图A和Key_A。
  2. 在准备调用ExitBootServices的瞬间,一个UEFI定时器中断触发,某个驱动分配了内存,地图变为B。
  3. 如果加载器拿着旧的Key_A去调用ExitBootServices,固件会拒绝(返回EFI_INVALID_PARAMETER),因为Key不匹配,表明加载器拥有的地图不是最新的。
  4. 加载器必须重新调用GetMemoryMap,获取新的地图B和Key_B,然后用Key_B再次尝试ExitBootServices。

因此,一个健壮的OS加载器代码中,ExitBootServices的调用通常包裹在一个循环里:

EFI_STATUS Status;
UINTN MapKey;
// ... 获取内存地图 ...

do {
  // 尝试退出引导服务
  Status = gBS->ExitBootServices (ImageHandle, MapKey);
  if (EFI_ERROR (Status)) {
    // 如果失败原因是Key无效(通常是EFI_INVALID_PARAMETER)
    // 则重新获取内存地图和新的Key
    if (Status == EFI_INVALID_PARAMETER) {
      // 释放旧的地图缓冲区
      if (MemoryMap != NULL) {
        FreePool (MemoryMap);
        MemoryMap = NULL;
      }
      // 重新获取
      Status = GetCurrentMemoryMap (&MemoryMapSize, &MemoryMap, &MapKey,
                                    &DescriptorSize, &DescriptorVersion);
      if (EFI_ERROR (Status)) {
        // 处理错误...
        break;
      }
      // 现在有了新的MapKey,循环会再次尝试ExitBootServices
    } else {
      // 其他错误,退出循环
      break;
    }
  }
} while (EFI_ERROR (Status));

if (!EFI_ERROR (Status)) {
  DEBUG ((DEBUG_INFO, "[Loader] Successfully exited boot services.\n"));
  // 此时,gBS, gRT(运行时服务)部分函数失效,系统进入OS接管阶段
  // 必须使用内存地图中的信息来设置OS的内核页表等结构。
}

内存地图的最终用途:在成功退出引导服务后,加载器需要将内存地图(通常是转换后的格式,如E820 map或UEFI内存描述符数组指针)以及其大小、描述符大小等信息,作为启动参数的一部分传递给操作系统内核。内核将依据这份权威的“财产清单”来初始化自己的物理内存管理器,知道哪些内存可用,哪些是MMIO,哪些必须保留。

有一次我在移植一个轻量级内核时,忽略了将ACPI NVS区域标记为保留,结果系统在进入ACPI模式时挂掉了。排查了很久才发现,内核把那片内存当普通内存用了,覆盖了固件的重要数据。所以,精确传递和解析这份地图,是系统稳定性的基石。GetMemoryMap的调用,远不止是获取一些地址信息那么简单,它贯穿了从固件到操作系统的权力交接全过程,是UEFI开发中最需要严谨对待的操作之一。

UEFI 基础教程 (十五) - 获取UEFI MemoryMap 一 编写源代码 EfiMemoryMapSize = 0; EfiMemoryMap = NULL; Status = gBS->GetMemoryMap ( &EfiMemoryMapSize, EfiMemoryMap, &EfiMapKey, &EfiDescriptorSize, 阅读详情

相关推荐

高校技术转移办公室人员在开展技术推介时,如何高效生成科技成果展示材料?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

ACPI之 系统地址映射接口

目前有三种方法将内存映射告诉OSPM, 1 . 通过 BIOS INT 15. 2. UEFI 通过GetMemoryMap()  boot service 告诉OS loader, 然后OS loader 告诉OSPM. 3. 如果内存资源被动态的增加或删除,我们可以通过定义在ACPI 名字空间里的内存设备去表达出来。 ACPI 定义了五种范围: AdddressRangeMem

Helping BIOSer become Awesome! 6158

产业园区运营负责人如何实现创新资源的高效整合与精准配置?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

UEFI——获取UEFI MemoryMap

内存映射(Memory Mapping)是一种将文件内容映射到进程的虚拟地址空间的技术。在这种机制下,文件可以视为内存的一部分,从而允许程序直接对这部分内存进行读写操作,而无需传统的I/O调用。内存映射缓冲区利用虚拟内存机制,让操作系统将一部分磁盘文件映射到进程地址空间的一块连续区域当中。操作系统负责管理内存页的加载和卸载,应用程序只需要访问这块内存区域即可,从而避免了频繁的磁盘I/O操作和多余的系统调用。

蟹蟹泥的博客 2524

基于Spring Boot的宾馆客房管理系统(代码+数据库+LW)

摘 要 随着旅游业与商务出行的蓬勃发展,宾馆行业竞争日趋激烈,传统管理模式中信息更新滞后、流程依赖人工、服务响应缓慢等问题凸显,严重影响运营效率与客户满意度。为此,本文设计并实现基于 Spring Boot 的宾馆客房管理系统,助力宾馆行业数字化转型。 系统采用 B/S 架构与前后端分离模式,前端以 Vue.js 框架构建交互界面,后端依托 Spring Boot 框架开发接口逻辑,搭配 MySQL 数据库实现高效数据存储与查询。系统涵盖房间管理、订单管理、用户管理、财务管理、服务与支持及系统支撑六大核心模块,实现在线预订、房态可视化、快捷入住退房、消费统计、智能客服等功能,满足住客、员工与管理者的多元化需求。 通过信息化手段,系统优化了宾馆服务流程,缩短住客等候时间,减轻员工工作负担,为管理者提供数据决策支持。测试表明,系统运行稳定、操作便捷,有效提升了宾馆运营效率与客户体验,为中小型宾馆信息化建设提供了实用的技术解决方案。 关键词:Spring Boot;宾馆客房管理;B/S 架构;前后端分离;数字化转型

政府科技管理者在推动科技成果转化过程中,审批工作如何实现智能化支撑?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

UEFI Memory V E820 Memory

Here is the source code for a simple UEFI command line utility to print out the the EFI memory regions: #include &lt;efi.h&gt;#include &lt;efilib.h&gt; #define PAGE_SIZE 4096 const CHAR16 *memory_ty...

sharrring的专栏 1480

科技中介服务机构如何提升技术评估的智能化与专业化水平?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

ACPI SpecV3.0学习总结

本文非原创文章,是对网上资料的整理,但忘了出处,在此未标明源地址表示抱歉。1.涉及的TABLESDTH   : System Description Table Header (这个不是Table,它是每个Table都包含的头) RSDP   : Root System Description Pointer  ('RSD PTR')RSDT    : Root System Descriptio...

一墨的博客 8018

科技中介服务机构如何利用平台提升服务质量与运营效率?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

OxygenXMLEditor-v26.x + keygen

OxygenXMLEditor-v26.x + keygen

Memory Map 内存映射

In computer science, a memory map is a structure of data (which usually resides in memory itself) that indicates how memory is laid out. Memory maps can have a different meaning in different par

Flower195的专栏 2130

完美复现基于自适应虚拟谐波阬的光储VSG并网电流谐波抑制模型(Simulink仿真实现)

内容概要:本文详细阐述了基于自适应虚拟谐波阬的光储VSG并网电流谐波抑制模型,旨在通过Simulink仿真平台有效解决光伏发电与储能系统在并网过程中产生的谐波电流问题。该模型融合虚拟同步发电机(VSG)技术,创新性地引入自适应虚拟谐波阬控制策略,以动态调节谐波阻抗特性,从而显著提升系统对特定次谐波的抑制能力,改善并网电能质量。文档强调该模型已实现“完美复现”,具备高度的可验证性和工程参考价值,适用于高水平学术论文复现与实际工程项目的仿真验证。内容涵盖系统建模、控制算法设计、仿真搭建、动态性能分析及关键参数优化等全过程,突出理论与实践的紧密结合。; 适合人群:具备电力电子、新能源并网、自动控制等相关领域基础知识,熟悉Matlab/Simulink仿真环境,从事电力系统仿真、VSG控制策略研究、电能质量治理方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①开展光储系统并网谐波治理关键技术的研究与方案可行性验证;②支撑高水平科研论文的复现工作,包括模型重建、数据对比与结果分析;③为VSG控制与电能质量协同优化提供可靠的仿真测试平台,服务于新型电力系统中谐波抑制算法的开发与优化。; 阅读建议:建议读者结合提供的Simulink模型文件与文档中的技术要点进行同步操作,重点理解自适应虚拟谐波阬的控制逻辑、参数整定方法及其在VSG系统中的集成方式。应通过调整工况与参数进行多次仿真,观察系统动态响应,深化对自适应控制机制与谐波抑制物理机理的理解,并推荐参考相关文献以拓宽技术视野。

产业园区运营负责人如何高效管理企业技术需求并精准对接科技成果?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

产业园区运营负责人如何实现创新资源的高效联动与精准配置?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

高校技术转移办公室人员如何提升科技成果的市场转化率?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

基于时频特征匹配的雷达多体制信号数字下变频自适应方法探究(Matlab代码实现)

内容概要:本文聚焦于“基于时频特征匹配的雷达多体制信号数字下变频自适应方法”的研究,提出一种面向复杂电磁环境下多体制雷达信号的智能识别与自适应处理技术。通过引入时频分析手段对不同调制样式的雷达信号进行特征提取与精确匹配,实现数字下变频关键参数的自动配置,显著提升信号接收系统的适应性、处理精度与抗干扰能力。结合Matlab代码实现,验证了该方法在多信号共存场景下的有效性与鲁棒性,并可能融合改进的灵敏度分析机制以优化系统响应性能。; 适合人群:具备雷达信号处理、数字信号处理等相关专业知识背景,从事电子信息系统、通信工程、雷达系统设计等方向的科研人员、高校研究生及工程技术开发者;熟悉Matlab仿真平台并希望深入理解自适应信号处理算法实现的专业人士。; 使用场景及目标:①应用于复杂电磁环境中多体制雷达信号的并行接收与分离处理;②实现数字下变频过程的智能化与自适应调控,提升接收机对未知信号的快速响应能力;③服务于电子战、频谱监测、智能感知与国防安全等关键领域中的信号识别与处理需求; 阅读建议:建议读者结合所提供的Matlab代码逐模块剖析其实现流程,重点理解短时傅里叶变换(STFT)或Wigner-Ville分布等时频分析工具的应用、特征匹配算法的设计逻辑以及下变频参数自适应调整机制,同时可深入研读文中涉及的改进灵敏度分析方法以增强对系统优化策略的理解。

科技中介服务机构如何降低技术支持服务的操作门槛与服务成本?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

上一篇: 从HFSS到AD:差分走线设计全流程避坑指南(含等长调节技巧)
下一篇: Android位置追踪不掉线的秘密:手把手教你配置小米/华为后台无限制策略
butter
博客等级 码龄25年 52粉丝 1690原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值