Fork me on GitHub
侧边栏

uefi 内存管理

PEI 阶段

PEI 阶段有它自己的使命,其中非常重要的一个就是初始化内存,建立内存信息,然后传给下一个阶段。为了完成PEI 的这些任务,它也需要有内存管理。

PEI 阶段最为重要的结构是HOB, 初始化内存服务前,PEI 申请的内存其实是插入到FV 文件

也就是FLASH 里面去运行。

在此状态下,FLASH 可读不可写,所以是不能使用全局变量的。如果有需要模块间共享信息,需要申请HOB.

HOB 拥有以下类型

//
// HobType of EFI_HOB_GENERIC_HEADER.
//
#define EFI_HOB_TYPE_HANDOFF              0x0001
#define EFI_HOB_TYPE_MEMORY_ALLOCATION    0x0002
#define EFI_HOB_TYPE_RESOURCE_DESCRIPTOR  0x0003
#define EFI_HOB_TYPE_GUID_EXTENSION       0x0004
#define EFI_HOB_TYPE_FV                   0x0005
#define EFI_HOB_TYPE_CPU                  0x0006
#define EFI_HOB_TYPE_MEMORY_POOL          0x0007
#define EFI_HOB_TYPE_FV2                  0x0009
#define EFI_HOB_TYPE_LOAD_PEIM_UNUSED     0x000A
#define EFI_HOB_TYPE_UEFI_CAPSULE         0x000B
#define EFI_HOB_TYPE_FV3                  0x000C
#define EFI_HOB_TYPE_UNUSED               0xFFFE
#define EFI_HOB_TYPE_END_OF_HOB_LIST      0xFFFF

HANDOFF 是第一个hob. 我们平常看代码

HOB (Hand-Off Block)

Hob 主要功能就是记录信息并且可以传递出去。(关于HOB 初始化,还有例如PEI 阶段临时内存到恒定内存,堆栈转换等等,会在之后PEI Core 里讲解)。

1

具体各个区块的含义,下面用到哪个就具体介绍哪一个,先看看长什么样就好。

1 PEI 阶段内存的安装

主要的一个function 就是 InstallEfiMemory() 这个function 里呢做了很重要的两件事:

  • build Resource HOB
  • install PEI memory

下图是X86内存MAP 的一个大概的样子

  //
  // Memory Mapping
  //
  // *------------+ HostIOBoundaryHi 
  // *            +
  // *------------+ 4G = HighSystemMemoryBase
  // *            +
  // *            +   [MMIO Memory]
  // *            +
  // *------------+ HostIOBoundary
  // *            +
  // *------------+ GMS Base
  // *            +
  // *------------+ GTT Base
  // *            +
  // *------------+ TSEG Base
  // *            +
  // *------------+ dPeiMemHi  
  // *            + 
  // *            +
  // *------------+ dPeiMemBase Base
  // *            +
  // *            +
  // *            +   [System Memory]
  // *            +
  // *------------+ 0x1000     
  // *            +   
  // *------------+ 0x400000 (256M)
  // *            +
  // *------------+ 0x100000 (1M) = LowSystemMemoryBase
  // *            +   
  // *------------+ legacy
  // *            +   
  // *------------+ 0
  图2

那么在InstallEfiMemory ()里会为上图每个区块调用BuildResourceDescriptorHob()去记录这些信息(包括属性,起始地址和长度)。之后在DXE 阶段就可以通过查找这些ResourceHob 去了解整个系统内存的分布情况。接下来还会调用 InstallPeiMemory()去建立PEI 内存的使用信息。我们PEI 阶段使用的内存会在TSEG BASE 下面,如上图的dPeiMemBase 位置。PeiMemSize 可以根据自己的板子,RCcode或者kernel 开发者会去调整大小。

2 . HOB 的建立

HOB 是有规定的,第一个HOB 必须是PHIT HOB , 最后一个必须是End of HOB List HOB.
当InstallPeiMemory 之后PHIT 被初始化如下内容:

PHIT->EfiMemoryTop = dPeiMemHi
PHIT->EfiMemoryBottom = dPeiMemBase
PHIT->EfiFreeMemoryTop = dPeiMemHi
PHIT->EfiFreeMemoryBottom = PHIT->EfiEndOfHobList + sizeof (EFI_HOB_GENERIC_HEADER);

到目前,我们Hob 里就有如下信息:

  • Resource hob 若干 (每个Hob 都描述了一个地址区块) 总共0-4G 或者 0-64G
  • PHIT信息指向了 Pei memory 区块。

3. PEI 内存分配

PEI 阶段内存分配的函数有两个:

  • AllocatePage()
  • AllocatePool()

具体请参考Vol1_PEI_1_5 4.6

还是看图,有的时候,我看spec,我就喜欢看总纲的图,每次都看,每次看会有不同的理解。

1

关于这两个函数AllocatePage(),AllocatePool()。如果仅仅通过字面上,一个是分配比较大内存,一个是比较小的内存。

但是两个function 还是有点差别的。主要差别如下:

  • AllocatePage() 需要memory install 之后才能使用,而AllocatePool() 不需要。
  • AllocatePage() 分配是的内存在蓝色框里,并且在绿色框里增加一个EFI_HOB_TYPE_MEMORY_ALLOCATION 的Hob; AllocatePool() 分配内存返回地址是在绿色框内,并且在绿色框里增加EFI_HOB_TYPE_MEMORY_POOL 类型的Hob;
  • 地址对齐不同
  • AllocatePage 会被记录到GCD 里,AllocatePool() 不会。

关于这两个function 具体实现,我不想贴代码,还是直接看上图吧 仔细观察黄色框部分和绿色框中Allocate 1 2 3 字的部分。

黄色框的部分是我们用AllocatePage() 分配内存时候的状况,绿色框里的Allocate 1 2 3 是我们用AllocatePool() 分配内存的时候。也回想一下,上篇文章,当Pei memory install 的时候PHIT 指针的情况

PHIT->EfiMemoryTop = dPeiMemHi 
PHIT->EfiMemoryBottom = dPeiMemBase 
PHIT->EfiFreeMemoryTop = dPeiMemHi 
PHIT->EfiFreeMemoryBottom=PHIT->EfiEndOfHobList+sizeof (EFI_HOB_GENERIC_HEADER);

刚开始EfiMemoryTop 和 EfiFreeMemoryTop 是相等的,当我们用AllocatePage 分配两端内存两个黄色框的Memory Allocation 2,3 的时候EfiFreeMemoryTop 就向下移动到黄色框底端(当然,还会添加一个hub 在绿色区域,EfiFreeMemoryBottom 也会往移动)。相反,当我们用AllocatePool 的时候EfiFeeMemoryBottom 就往上移动。

DXE 阶段

首先UEFI Firmware 它要向OS 汇报内存资源(这里的内存不单单指system memory,也包括地址线上映射的其他设备的memory),它首先要先收集这些资源信息,收集信息一部分是在PEI阶段并且用Resource Hob 传递出去。接下来在DXE,它除了还要继续收集信息,并且要整理与记录这些资源信息,这个管理机制就是之后会介绍的GCD 机制。DXE 阶段还需要内存资源的分配与释放,那么还有一个Memory Services 的一系列function。汇报资源,legacy 不介绍,UEFI 会用GetMemoryMap() function 把这些资源信息传递给OS,同时ACPI 还规定了每个device 也要_CRS method 汇报资源使用情况包括 memory MMIO interrupt 等信息。以上,就是整个uefi firmware 关于内存资源这方面的大概情况。

DXE 阶段就更丰富一些,像

1

蓝色的空间,比较重要的一个地方, dxe core 优先考虑放DXE CORE.

HOB 是完全连续的,会不断的在free space 里面追加,无法删除。

1. GCD

global coherency domain, 中文译名为全局一致域。它管理pei 通过hob 传递过来的系统资源。从功能 上讲,分为管理存储空间的gcd memory space 和管理io 两部分服务。通过这两种服务,系统资源可以被添加,删除,使用。

1.1 GCD 初始化

在DXE 阶段刚刚开始时,dxe 主函数调用coreinitiallizegcdservices 函数初始始化gcd 视图

根据cpu hob 信息,初始gcd mem 视图与Io 视图的初始化空间

根据resource descriptor hob 信息, 细化视图结构

将已经使用的内存和dxe 即将使用的内存设置为已经分配状态

调用coregetmemoryspacemap 获得所有gcd 存储空间描述符,遍历它,将剩下的,没分配的系统内存申请出来,作为内存管理中的可分配内存。

/**
  Internal function.  Inserts a new descriptor into a sorted list
  @param  Link                   The linked list to insert the range BaseAddress
                                 and Length into
  @param  Entry                  A pointer to the entry that is inserted
  @param  BaseAddress            The base address of the new range
  @param  Length                 The length of the new range in bytes
  @param  TopEntry               Top pad entry to insert if needed.
  @param  BottomEntry            Bottom pad entry to insert if needed.
  @retval EFI_SUCCESS            The new range was inserted into the linked list
**/
EFI_STATUS
CoreInsertGcdMapEntry (
  IN LIST_ENTRY           *Link,
  IN EFI_GCD_MAP_ENTRY     *Entry,
  IN EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN UINT64                Length,
  IN EFI_GCD_MAP_ENTRY     *TopEntry,
  IN EFI_GCD_MAP_ENTRY     *BottomEntry
  )
{
  ASSERT (Length != 0);
 
  if (BaseAddress > Entry->BaseAddress) {
    ASSERT (BottomEntry->Signature == 0);
 
    CopyMem (BottomEntry, Entry, sizeof (EFI_GCD_MAP_ENTRY));
    Entry->BaseAddress      = BaseAddress;
    BottomEntry->EndAddress = BaseAddress - 1;
    InsertTailList (Link, &BottomEntry->Link);
  }
 
  if ((BaseAddress + Length - 1) < Entry->EndAddress) {
    ASSERT (TopEntry->Signature == 0);
 
    CopyMem (TopEntry, Entry, sizeof (EFI_GCD_MAP_ENTRY));
    TopEntry->BaseAddress = BaseAddress + Length;
    Entry->EndAddress     = BaseAddress + Length - 1;
    InsertHeadList (Link, &TopEntry->Link);
  }
 
  return EFI_SUCCESS;
}

从这个过程可以看出两点,一是GCD使用链表管理,二是这个链表是严格按从高地址到低地址的方式排列的。

根据这个特性,根据地址和长度寻找对应资源节点时(CoreSearchGcdMapEntry函数),可以按记录的base 顺序查找,当然,由于长度没有限制,可以横跨多个节点,返回它们的头切点和尾节点。

基于这个函数构成了以下的GCD 管理函数。

coreconvertspace : 对GCD 视图中指定区域进行添加,删除和修改属性。

coreallocatespace: 分配新的gcd 结点。

内存服务:

内存类型

/// Enumeration of memory types introduced in UEFI.
///
typedef enum {
  ///
  /// Not used.
  ///
  EfiReservedMemoryType,
  ///
  /// The code portions of a loaded application.
  /// (Note that UEFI OS loaders are UEFI applications.)
  ///
  EfiLoaderCode,
  ///
  /// The data portions of a loaded application and the default data allocation
  /// type used by an application to allocate pool memory.
  ///
  EfiLoaderData,
  ///
  /// The code portions of a loaded Boot Services Driver.
  ///
  EfiBootServicesCode,
  ///
  /// The data portions of a loaded Boot Serves Driver, and the default data
  /// allocation type used by a Boot Services Driver to allocate pool memory.
  ///
  EfiBootServicesData,
  ///
  /// The code portions of a loaded Runtime Services Driver.
  ///
  EfiRuntimeServicesCode,
  ///
  /// The data portions of a loaded Runtime Services Driver and the default
  /// data allocation type used by a Runtime Services Driver to allocate pool memory.
  ///
  EfiRuntimeServicesData,
  ///
  /// Free (unallocated) memory.
  ///
  EfiConventionalMemory,
  ///
  /// Memory in which errors have been detected.
  ///
  EfiUnusableMemory,
  ///
  /// Memory that holds the ACPI tables.
  ///
  EfiACPIReclaimMemory,
  ///
  /// Address space reserved for use by the firmware.
  ///
  EfiACPIMemoryNVS,
  ///
  /// Used by system firmware to request that a memory-mapped IO region
  /// be mapped by the OS to a virtual address so it can be accessed by EFI runtime services.
  ///
  EfiMemoryMappedIO,
  ///
  /// System memory-mapped IO region that is used to translate memory
  /// cycles to IO cycles by the processor.
  ///
  EfiMemoryMappedIOPortSpace,
  ///
  /// Address space reserved by the firmware for code that is part of the processor.
  ///
  EfiPalCode,
  ///
  /// A memory region that operates as EfiConventionalMemory,
  /// however it happens to also support byte-addressable non-volatility.
  ///
  EfiPersistentMemory,
  EfiMaxMemoryType
} EFI_MEMORY_TYPE;

POOL

与PEI 类似, DXE 中管理内存用的也是pool 和page.

pool

申请空间的碎片化资源,每个类型有一长串的pool 头节点,代表粒度从低到高,后面再跟着可使用的节点,类似于一个二次链表

每次要申请空间都会去找对应长度和对应类型的pool 是否有free 的节点。有就用,没有就申请一页内存把它拆成Pool, 被用的直接用掉,没被用的送进free.

//
// Each element is the sum of the 2 previous ones: this allows us to migrate
// blocks between bins by splitting them up, while not wasting too much memory
// as we would in a strict power-of-2 sequence
//
STATIC CONST UINT16 mPoolSizeTable[] = {
  128, 256, 384, 640, 1024, 1664, 2688, 4352, 7040, 11392, 18432, 29824
};

这个是pool 的粒度。每一数都是前两个数的和。

page

与pool 对应,page 是被申请出来的整页内存,按uefi 规范,一页的大小为4k.

内存预分配

在内核初始化时,内核通过配置表拿到bios的内存布局,根据内存布局完成内核的初始始内存配置。

对于rt 类内存,内核认为bios 可能会使用,将其标记为保留

对于bs 类内存,内核认为bios 不会使用,将其收入可使用空间

比较特殊的是efiacpireclaimmemory类型内存,此段内存用于存储bios 传给内核的acpi表,在内核初始化acpi 后,将此段空间释放。

内存预分配如何实现

1 在 PEI 阶段,构造GUI HOB(HOB 内容可来自PCD 或VARIABLE 变量区)

2 DXE 初始化内存状态,调用COREADDMEMROYDESCRIPTOR 函数,如果此函数是第一次被调用,进入内存预分配流程。

3 BDS 结束后,会调用函数统一具体内存使用情况,将估算后的值填回VARIABLE(为保证S4, 应设置更新内容后重启)。

gMemoryMap

gMemoryMap 链表是内存管理的另一重要结构

这个结构有些类似于GCD, 甚至插入删除的写法都与GCD类似,可以直观的看出当前系统内存的情况和属性

GCD 和内存管理有什么关系呢?

PromoteMemoryResource

转换GCD已初始化但未测试的部分转换进动态存储空间

找到属性为EfiGcdMemoryTypeReserver, 已经初始化但未测试的部分,修改它的节点属性,调用CoreAddRange将资源放入mMapStack空间。

1

参考博客

UEFI-MemoryManagement_Pedroa的博客-CSDN博客

posted @ 2025-08-07 17:37  yooooooo  阅读(106)  评论(0)    收藏  举报