FreeNOS中物理内存与虚拟内存的初始化过程(七)

在跳转到kmain函数之前已经开启了分页机制,一个操作系统需要建立堆栈以及对内存进行管理,以下部分分析相关函数。

主要函数入口:
INITCLASS(Memory, initialize, PMEMORY)
INITOBJ(X86Memory, memory, VMEMORY)
FreeNOS中物理内存与虚拟内存的初始化过程(七)
(1)INITCLASS(Memory, initialize, PMEMORY)
FreeNOS中物理内存与虚拟内存的初始化过程(七)
Memory类有四个静态成员变量:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
第53~54行从multibootInfo结构体中获取可使用内存大小(包括低端内存和高端内存),multibootInfo结构boot.S文件中已经将grub传递过来的遵守Multiboot规范的Multiboot信息复制给了multibootInfo,此处内存可使用量和内存总量是一致的。

第57~64行使用 memoryMap ~ memoryMapEnd对用地址中的每bit指示4KB的内存是否使用,将内存总量全部设置为未使用。

接下来使用了placement new,它也是对operator new的一个重载:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
所以,第66~68行 在物理地址0x00300000处分配BubbleAllocator类——主要管理堆(heap),
第71行可以知道该堆的起始地址和大小,起始地址在物理地址0x00300000处偏移类PoolAllocator和类BubbleAllocator大小(sizeof(BubbleAllocator) + sizeof(PoolAllocator)),大小为1MB减去这俩个类的大小。

第67~68行在物理地址0x00300000+sizeof(BubbleAllocator)处分配类PoolAllocator——分配一系列大小不同的内存块,所有分配的内存都位于堆中,即{0x00300000+sizeof(BubbleAllocator) + sizeof(PoolAllocator)} ~ 0x00400000,即处于kernel.ld文件的以下部分:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
以下两行:
pool->setParent(bubble);
Allocator::setDefault(pool);
表明以下类申请内存时需要在pool (class PoolAllocator)中进行相关处理,而与此同时pool中申请内存需要在bubble (BubbleAllocator)中进行相关处理。
FreeNOS中物理内存与虚拟内存的初始化过程(七)
关于以上类的使用有具体实例再结合源码分析。

(2)INITOBJ(X86Memory, memory, VMEMORY)
实列化一个class X86Memory实列memory,这里使用了设计模式中的单件类,只实列化一次,可以去看看模板template class Singleton的实现细节:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
FreeNOS中物理内存与虚拟内存的初始化过程(七)
class X86Memory构造函数如下:

FreeNOS中物理内存与虚拟内存的初始化过程(七)
1).Memory()主要是class Memory构造函数的调用
FreeNOS中物理内存与虚拟内存的初始化过程(七)
可以看到Memory()构造函数只是将部分物理地址标记为已被使用,包括最开始包含系统镜像的0~4MB物理地址以及引导模块所在的物理地址。
FreeNOS中物理内存与虚拟内存的初始化过程(七)
在qemu启动时也可看到以上部分信息:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
2)其他参数初始化
remPageDir = 0x00801000
myPageDir = 0x00401000
(以上部分为线性地址)
这里回顾一下之前的分页机制:
页目录表kernelPageDir,位于物理地址0x00400000(4MB),其中第一项对应的4MB线性地址对应为物理地址0~4MB,第二项把页目录表kernelPageDir(大小4KB)转换成以4MB开始的线性地址.

所以上图中的myPageDir = 0x00401000线性地址转换为页目录表项为第二项(myPageDir>>DIRSHIFT——0x00401000>>22=1,表项索引从0开始),然后计算页表项((
myPageDir>>PAGESHIFT)&PAGESIZE = (0x00401000>>12)&0x000003ff=1,取16bit~24bit的值)),计算得出的页表项索引为1,线性地址myPageDir = 0x00401000对应的物理地址是表项kernelPageDir+1的内容。
而kernelPageDir+4对应页目录表项第二项,所以表示实际物理地址即为kernelPageDir,也就是说myPageDir 线性地址对应的物理地址为kernelPageDir。
同理,remPageDir= 0x00801000 线性地址转换为页目录表项为第三项,即kernelPageDir+2,那么表项的内容是什么呢?好像是是初始值0?
看看PAGEDIRADDR_REMOTE在哪个地方被引用:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
第一行是第二行的软链接文件,其实就是Memory.h文件:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
FreeNOS中物理内存与虚拟内存的初始化过程(七)
可以看到myPageDir[DIRENTRY(pageDirAddr)]其实转化为物理地址就是
kernelPageDir[DIRENTRY(pageDirAddr)],其中DIRENTRY(pageDirAddr)为
DIRENTRY(PAGEDIRADDR_REMOTE)=2,即
kernelPageDir[2] = p->getPageDirectory() | (PAGE_PRESENT|PAGE_RW|PAGE_PINNED|prot);
进程的页目录地址:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
FreeNOS中物理内存与虚拟内存的初始化过程(七)
FreeNOS中物理内存与虚拟内存的初始化过程(七)
很明显,remPageDir = 0x00801000线性地址最终指向的是pageDir[DIRENTRY(PAGETABFROM) ]的内容,即pageDir[1]:
pageDirAddr | PAGE_PRESENT | PAGE_RW,特定进程的页目录物理地址,作用与kernelPageDir一样,只不过一个是针对系统内核,一个是针对特定的进程。最后pageDirAddr 在进程调度切换时被引用:
FreeNOS中物理内存与虚拟内存的初始化过程(七)
后续详细分析进程调度函数contextSwitch。