- 源码EDK2:Tianocore
- UEFI源码分析系列第二篇,DXE阶段的内存服务
- 第二部分,函数接口
- DXE阶段源码目录
MdeModulePkg/Core/Dxe
- 首先我们来分析上一节的遗留问题,即函数
CoreAddRange
- 之后我们分析一下其余的接口,如申请内存、释放内存等
- 最后我们从一个整体的视角来看整个内存服务
初始化流程
首先我们回顾一下整个内存初始化的过程。
在DxeMain
中调用如下函数开始初始化内存服务。
/** /Dxe/DxeMain/DxeMain.c **/
272 CoreInitializeMemoryServices (&HobStart, &MemoryBaseAddress, &MemoryLength);
这个函数好一顿找,最后找到一块可用的内存空间,然后调用如下函数。
/** /Dxe/Gcd/Gcd.c **/
2288 CoreAddMemoryDescriptor (
2289 EfiConventionalMemory,
2290 BaseAddress,
2291 RShiftU64 (Length, EFI_PAGE_SHIFT),
2292 Capabilities
2293 );
最后该函数有是检查、转换和测试,最后把工作扔给来下面这个函数
/** /Dxe/Mem/Page.c **/
550 CoreAcquireMemoryLock ();
551 End = Start + LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT) - 1;
552 CoreAddRange (Type, Start, End, Attribute);
553 CoreFreeMemoryMapStack ();
554 CoreReleaseMemoryLock ();
函数CoreFreeMemoryMapStack
及其涉及到的变量与整个初始化过程息息相关,后续分析中会看到。
内存初始化CoreAddRange
此函数接收四个参数:
Type
,类型为EFI_MEMORY_TYPE
,根据传参来看值是EfiConventionalMemory
Start
,内存空间起始地址End
,内存空间的终止地址,注意到其值为Start + Length - 1
Attribute
,内存属性掩码,这部分不作重点,只在遇到的时候指明,感兴趣的还请自行挖掘吧~
/** /Dxe/Mem/Page.c **/
163 VOID
164 CoreAddRange (
165 IN EFI_MEMORY_TYPE Type,
166 IN EFI_PHYSICAL_ADDRESS Start,
167 IN EFI_PHYSICAL_ADDRESS End,
168 IN UINT64 Attribute
169 )
170 {
/** 添加一块可用的内存区域 **/
259 return ;
260 }
额外看一下对Type
域的描述,即空闲的内存区域
/** /MdePkg/Include/Uefi/UefiMultiPhase.h **/
22 typedef enum {
......
55 ///
56 /// Free (unallocated) memory.
57 ///
58 EfiConventionalMemory,
......
91 } EFI_MEMORY_TYPE;
1)一些基础的检查工作
是否页对齐,高地址应当大于低地址,锁有没有加上
/** /Dxe/Mem/Page.c **/
174 ASSERT ((Start & EFI_PAGE_MASK) == 0);
175 ASSERT (End > Start) ;
176
177 ASSERT_LOCKED (&gMemoryLock);
178
179 DEBUG ((DEBUG_PAGE, "AddRange: %lx-%lx to %d\n", Start, End, Type));
2) 若包含了从地址零开始的一页,则置为零
/** /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 }
3) 全局变量mMemoryMapKey
增加1
/** /Dxe/Mem/Page.c **/
31 //
32 // MemoryMap - The current memory map
33 //
34 UINTN mMemoryMapKey = 0;
197 mMemoryMapKey += 1;
4)触发有关Memory Map Change的事件
这里本质上是根据EventGroup
的GUID
来唤醒所有有关的事件,由于异步事件机制上基于时钟中断的,而现在已经拿到了内存锁(即禁中断),故这些事件只是从未触发状态转换到了触发状态,而并不会执行。
真正的执行会在锁释放后(开中断),时钟中断处理程序来执行这些被唤醒的事件。不过也有例外的可能,就是其他地方调用了TPL
的提升和降低。具体机制参阅异步事件处理一节。
/** /Dxe/Mem/Page.c **/
208 CoreNotifySignalList (&gEfiEventMemoryMapChangeGuid);
顺便提一下这一步是UEFI 2.0规范才有的,UEFI2.0规范是Intel把EFI 1.0规范交给UEFI Form后出的规范,原本的1.0规范里并没有这一步。
5) 修正全局变量gMemoryMap
这里出现了一个很关键的全局变量gMemoryMap
/** /Dxe/Mem/MemData.c **/
23 //
24 // MemoryMap - the current memory map
25 //
26 LIST_ENTRY gMemoryMap = INITIALIZE_LIST_HEAD_VARIABLE (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;
跳过不一致的内存,包括类型不一致、属性不一致
223 if (Entry->Type != Type) {
224 continue;
225 }
226
227 if (Entry->Attribute != Attribute) {
228 continue;
229 }
若存在Entry
与新加入的Start
和End
相连接,则修改Start
或End
将二者合二为一,并把Entry
从链表中删除
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 }
可以看到gMemoryMap
保存来所有内存区域的信息,如果新加入的区域与某个区域相连,则合并。
类型MEMORY_MAP
定义于/Dxe/Mem/Imem.h
/** /Dxe/Mem/Imem.h **/
38 #define MEMORY_MAP_SIGNATURE SIGNATURE_32('m','m','a','p')
39 typedef struct {
40 UINTN Signature;
41 LIST_ENTRY Link;
42 BOOLEAN FromPages;
43
44 EFI_MEMORY_TYPE Type;
45 UINT64 Start;
46 UINT64 End;
47
48 UINT64 VirtualStart;
49 UINT64 Attribute;
50 } MEMORY_MAP;
包含必要的Type
、Start
、End、
和Attribute
。
域FromPages
表示该区域是否来自某个页内,在后面的函数AllocateMemoryMapEntry
中可以看到相关的处理。
VirtualStart
还不知道干嘛的,后续看看能不能分析到。
6)通过临时内存区域mMapStack
转换到gMemoryMap
中
247 mMapStack[mMapDepth].Signature = MEMORY_MAP_SIGNATURE;
248 mMapStack[mMapDepth].FromPages = FALSE;
249 mMapStack[mMapDepth].Type = Type;
250 mMapStack[mMapDepth].Start = Start;
251 mMapStack[mMapDepth].End = End;
252 mMapStack[mMapDepth].VirtualStart = 0;
253 mMapStack[mMapDepth].Attribute = Attribute;
254 InsertTailList (&gMemoryMap, &mMapStack[mMapDepth].Link);
255
256 mMapDepth += 1;
257 ASSERT (mMapDepth < MAX_MAP_DEPTH);
259 return ;
260 }
之所以要使用临时内存区域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
的操作。
7)CoreFreeMemoryMapStack
此时轮到我们这个息息相关的函数出场了,同样需要在锁的持有下操作
/** /Dxe/Mem/Page.c **/
318 VOID
319 CoreFreeMemoryMapStack (
320 VOID
321 )
322 {
323 MEMORY_MAP *Entry;
324 MEMORY_MAP *Entry2;
325 LIST_ENTRY *Link2;
326
327 ASSERT_LOCKED (&gMemoryLock);
检查并标记全局变量mFreeMapStack
332 if (mFreeMapStack != 0) {
333 return ;
334 }
339 mFreeMapStack += 1;
依次处理保存在栈中的MEMORY_MAP
。
341 while (mMapDepth != 0) {
342 //
343 // Deque an memory map entry from
344 //
345 Entry = AllocateMemoryMapEntry ();
346
347 ASSERT (Entry);
348
349 //
350 // Update to proper entry
351 //
352 mMapDepth -= 1;
这里调用了AllocateMemoryMapEntry
来获取一个Entry
,这个函数需要使用gMemoryMap
里的内存区域来申请到一个Entry
。注意到6)中入栈的时候,也把入栈的MemoryMap链接到了gMemoryMap
中,所以此时AllocateMemoryMapEntry
是可用的。
后面会详细解释一下此时AllocateMemoryMapEntry
的执行情况。
354 if (mMapStack[mMapDepth].Link.ForwardLink != NULL) {
355
356 //
357 // Move this entry to general memory
358 //
359 RemoveEntryList (&mMapStack[mMapDepth].Link);
360