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. 我们平常看代码
DXE 阶段就更丰富一些,像
memory allocation acpi 指向黄色区域,一会传到dxe. dxe 就知道pei 用了之方面的内存。
蓝色的空间,比较重要的一个地方, dxe core 优先考虑放DXE CORE.
HOB 是完全连续的,会不断的在free space 里面追加,无法删除。
Allocatepool 函数的本质就是在上面添加类型为efi_hob_type_memory_pool 的hob.
GCD
global coherency domain, 中文译名为全局一致域。它管理pei 通过hob 传递过来的系统资源。从功能 上讲,分为管理存储空间的gcd memory space 和管理io 两部分服务。通过这两种服务,系统资源可以被添加,删除,使用。
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空间。