在DxeMain中调用如下函数开始初始化内存服务。
通过Hob找到一块可用的空间 根据PEI阶段传入的HobList
CoreAcquireMemoryLock RaiseTpl(16)
CoreAddRange
/** /Dxe/Mem/Page.c **/
190 if (Type == EfiConventionalMemory && Start == 0 && (End >= EFI_PAGE_SIZE - 1)) {
191 SetMem ((VOID *)(UINTN)Start, EFI_PAGE_SIZE, 0);
192 }
/** /Dxe/Mem/Page.c **/
31 //
32 // MemoryMap - The current memory map
33 //
34 UINTN mMemoryMapKey = 0;
197 mMemoryMapKey += 1;
触发有关Memory Map Change的事件
这里本质上是根据EventGroup的GUID来唤醒所有有关的事件,由于异步事件机制上基于时钟中断的,而现在已经拿到了内存锁(即禁中断),故这些事件只是从未触发状态转换到了触发状态,而并不会执行。
真正的执行会在锁释放后(开中断),时钟中断处理程序来执行这些被唤醒的事件。不过也有例外的可能,就是其他地方调用了TPL的提升和降低。具体机制参阅异步事件处理一节。
CoreAddRange->
CoreNotifySignalList(&gEfiEventMemoryMapChangeGuid);
修正全局变量gMemoryMap
这里出现了一个很关键的全局变量gMemoryMap,
不过先不着急看它的详细定义,先看来看这里做了什么事情
遍历gMemoryMap这个链表里的每一个Entry,类型为MEMORY_MAP。宏CR用于从链表结构获得到数据结构,并包含一个签名检查的操作。
/** /Dxe/Mem/Page.c **/
218 Link = gMemoryMap.ForwardLink;
219 while (Link != &gMemoryMap) {
220 Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE);
221 Link = Link->ForwardLink;
231 if (Entry->End + 1 == Start) {
232
233 Start = Entry->Start;
234 RemoveMemoryMapEntry (Entry);
235
236 } else if (Entry->Start == End + 1) {
237
238 End = Entry->End;
239 RemoveMemoryMapEntry (Entry);
240 }
若存在Entry与新加入的Start和End相连接,则修改Start或End将二者合二为一,并把Entry从链表中删除
可以看到gMemoryMap保存来所有内存区域的信息,如果新加入的区域与某个区域相连,则合并。
包含必要的Type、Start、End、和Attribute。
域FromPages表示该区域是否来自某个页内,在后面的函数AllocateMemoryMapEntry中可以看到相关的处理。
VirtualStart还不知道干嘛的,后续看看能不能分析到。
NextLink = Entry->Link.ForwardLink;
if (NextLink != &gMemoryMap) {
NextEntry = CR (NextLink, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE);
//
// ---------------------------------------------------
// | +----------+ +------+ +-----------------+ |
// ---|gMemoryMep|---|Entry1|---|EntryX Entry3|---
// +----------+ +------+ +-----------------+
//
if ((Entry->Type == NextEntry->Type) && (Entry->End + 1 == NextEntry->Start)) {
Entry->End = NextEntry->End;
RemoveOldEntry (NextEntry);
}
}
InsertNewEntry (
&Entry->Link,
Entry->Start,
Start - 1,
Entry->Type,
FALSE,
AddRegion
);
// ---------------------------------------------------
// | +----------+ +------+ +------+ +------+ |
// ---|gMemoryMep|---|Entry1|---|EntryX|---|Entry3|---
// +----------+ +------+ +------+ ^ +------+
// |
// +------+
// |EntryZ|
// +------+
//
6)通过临时内存区域mMapStack转换到gMemoryMap中
之所以要使用临时内存区域mMapStack,是因为,我们的内存服务还没有初始化好呢。mMapStack本质上是全局变量,在编译时期分配到数据段中。
/** /Dxe/Mem/Page.c **/
36 #define MAX_MAP_DEPTH 6
37
38 ///
39 /// mMapDepth - depth of new descriptor stack
40 ///
41 UINTN mMapDepth = 0;
42 ///
43 /// mMapStack - space to use as temp storage to build new map descriptors
44 ///
45 MEMORY_MAP mMapStack[MAX_MAP_DEPTH];
46 UINTN mFreeMapStack = 0;
全局变量mMapDepth相当于栈顶指针,全局变量mFreeMapStack标记是否经过了CoreFreeMemoryMapStack的操作。
CoreAddRange 之后就是
CoreFreeMemoryMapStack ();
while (mMapDepth != 0) {
//
// Deque an memory map entry from mFreeMemoryMapEntryList
//
Entry = AllocateMemoryMapEntry ();
ASSERT (Entry);
这里调用了AllocateMemoryMapEntry来获取一个Entry,这个函数需要使用gMemoryMap里的内存区域来申请到一个Entry。注意中入栈的时候,也把入栈的MemoryMap链接到了gMemoryMap中,所以此时AllocateMemoryMapEntry是可用的。
对于栈中mMapStack[mMapDepth]的Link不为空的情况(对于当前情况,该mMapStack[mMapDepth]是被链接到gMemoryMap中),首先断开Link,然后把数据拷贝到申请到的Entry中,随后把Entry加入到gMemoryMap里。
(这一步是否多此一举呢,其实并没有,由于mMapStack[mMapDepth]被链接到gMemoryMap中,所以AllocateMemoryMapEntry实际上会修改mMapStack)
对于Link为空的情况,则将Entry直接插入全局链表mFreeMemoryMapEntryList,显然该全局变量是用来维护可用于MEMORY_MAP的内存区域的,也就是说这个链表中的元素都是一个一个的空的MEMORY_MAP结构体,可以直接拿来用。
即在mMapStack[mMapDepth] 的Link 为空的情况下,通过AllocateMemoryMapEntry申请的Entry又插回了mFreeMemoryMapEntryList。
哎,有点晕了吗。到底mMapStack、gMemoryMap和CoreAllocatePoolPages()之间是如何作用的呢,当mMapStack中的一个元素同时存在于gMemoryMap中,申请内存的时候又是如何操作的呢。
当执行 CoreAllocatePoolPages() 函数时,它会从系统中分配一定数量的物理内存页,并返回一个指向这些页的指针。这些分配的物理内存页不一定是连续的,因此需要进行内存映射。
在内存映射过程中,会将分配的物理内存页与虚拟地址空间中的某个区域进行映射,以便程序可以通过虚拟地址来访问这些物理内存页。这个映射关系会被记录在 gMemoryMap 中的一个内存映射项中。
同时,为了方便管理和跟踪内存映射信息,将这个内存映射项也添加到 mMapStack 中。这样,当需要释放这些内存页时,可以通过 mMapStack 中的信息找到对应的内存映射项,并从 gMemoryMap 中删除该映射项。
因此,当 mMapStack 中的一个元素同时存在于 gMemoryMap 中时,表示这个内存映射项对应的内存页已经分配并映射到了虚拟地址空间中的某个区域。
总结起来,mMapStack 用于记录内存映射信息,gMemoryMap 用于管理所有已分配的内存块的映射关系,而 CoreAllocatePoolPages() 则是用于分配物理内存页并进行内存映射的函数。当 mMapStack 中的元素同时存在于 gMemoryMap 中时,表示相应的内存页已经成功分配并映射到了虚拟地址空间中。
在这里给一个简略的描述的话,大概是这样的一个过程,但首先给一个定义以帮助描述,这里称动态内存空间为先前通过HOB数据搜索所得到的内存空间,称静态内存空间为mMapStack全局变量在编译时期所确定的数据段的一段内存空间。
初始条件是mMapStack中存在一项MEMORY_MAP,该项同时也链接到了gMemoryMap。此项描述了一块可用的内存空间,在整个初始化过程中来说,也就是动态内存空间。
函数CoreFreeMemoryMapStack的意图是把mMapStack中的项转移到gMemoryMap中,而mMapStack本质上是临时内存,所以CoreFreeMemoryMapStack本质上也就是把保存在静态内存空间中的Entry转移到动态内存空间。
函数AllocateMemoryMapEntry需要检查全局变量mFreeMemoryMapEntryList来获取可用的Entry,第一次运行时该全局变量必然为空,所以需要通过CoreAllocatePoolPages来获取一段动态内存空间。
函数CoreAllocatePoolPages本质上是通过gMemoryMap来获取可用的内存的。由于在第1步中gMemoryMap已经链接到来一项Entry,而这项Entry是位于静态内存空间,该Entry的FromPages标记为FALSE,于是在根据gMemoryMap中的这一项Entry来分配动态内存的时候,最终会使得这项Entry分裂成两项,一项Entry0描述剩余的动态内存空间,一项Entry1描述分配出的一页大小的动态内存空间。最终的情况就是,mMapStack中存在两项即Entry0和Entry1,这两项也都链接到gMemoryMap中。CoreAllocatePoolPages最终返回一个地址,该地址是Entry1描述的动态内存空间的基地址。
回到函数AllocateMemoryMapEntry,在通过CoreAllocatePoolPages获得可用的一页动态内存后,会按照sizeof(MEMORY_MAP)的大小将这一页空间划分成若干个Entry保存到mFreeMemoryMapEntryList。此时这些Entry本身的数据实际是位于动态内存空间,其属性FromPages被标记为TRUE,不过这些Entry并没有描述某一段内存空间,其本身的数据都是空值。此时`mMapStack中的Entry0和Entry1自身的数据仍然位于静态内存空间中,但这两项所描述的内存空间是动态内存空间。
回到函数CoreFreeMemoryMapStack,在通过AllocateMemoryMapEntry获取到一个位于动态内存空间的Entry后,便把静态内存空间(即mMapStack)中的一项数据拷贝进去。此时的Entry本身位于动态内存空间,而且描述了一段动态内存。
把mMapStack中的所有项都这样复制出来后,mMapStack便为空,gMemoryMap便包含若干个Entry,这些Entry描述了一段动态内存。可见此时这段动态内存里有一页的空间是用来存放这些Entry本身的。
整体视角
申请页
更新全局变量mMemoryTypeStatistics和gMemoryTypeInformation
经过mMapStack来更新gMemoryMap。
关键的函数抽象有CoreConvertPages和CoreFreeMemoryMapStack,前者实现内存类型的转换,后者把mMapStack的Entry转移到动态内存空间中。
申请内存池
全局变量mPoolHead存放内存类型对于的Pool结构体,内含FreeList
全局静态变量mPoolSizeTable用于划分内存空间,规范申请粒度
申请内存需要用一个头和尾把所需申请的空间包围起来。
ApplyMemoryProtectionPolicy (
EfiMaxMemoryType,
Type,
Start,
LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT)
);
Status = CoreConvertPages (Memory, NumberOfPages, EfiConventionalMemory);
更新全局变量mMemoryTypeStatistics和gMemoryTypeInformation
- 通过mMapStack来更新gMemoryMap链表。
- mMapStack:它是一个用于存储内存映射信息的栈。每当分配或释放内存时,相关的内存映射信息会被推入或弹出这个栈中。
- gMemoryMap:它是一个全局的内存映射表,用于记录系统中所有已分配的内存块的信息。每个内存块都有一个对应的内存映射项。
- CoreAllocatePoolPages():它是一个函数,用于分配一定数量的物理内存页。