mit6.828 lab2:Memory Management

讲义概要

对于一个4GB的虚拟内存,虚拟地址占到32位。我们将内存中划分为一个一个的页组成,每页占4KB,那么整个虚拟内存中就存在2^20个页面。所以需要有一个映射(翻译)机制来讲虚拟地址转化为物理地址。也即:page table:

它是一个高度为2的树:

image-20201123135727848

树根是一个 page Directory,该结构有1024个条目,每个条目可以指向一个page table。每个page table中又存在1024个entry(PTE)里面保存了某个物理地址的PPN 和一些flag。

所以翻译的流程如下:

  1. 先取32位地址的最高10位,查找到page directory的PPN,根据PPN 查找page table。

  2. 然后根据接下来的10bit查找page table中的PPN,作为物理地址的高20位,剩余12位原样拼凑到物理地址低12位。

各进程都有各自的页表,其虚拟内存到物理内存的映射关系如下:

image-20201123141330805

当该进程申请内存时,系统会找一块空闲的页面,然后新增一个PTE至其页表中,指向该物理页面。然后设置该PTE的flag,说明其使用情况、读写情况等。

将虚拟地址KERNBASE:KERNBASE+PHTSTOP 映射至:物理地址0-PHYSTOP

Lab:

part 1 Memory Management

Lab1 最后进入了kern/init.c 中的i386_init函数,该函数调用了:

mem_init函数,这就是本节lab的入口。

首先参考一下下图,lab1结束后,物理内存分布如下:

image-20201124191256529

总体来看,物理地址空间的布局如下:

  1. 从0x00000~0xA0000,(低640KB)这部分叫做basemem,是可用的。
  2. 从0xA0000~0x100000,这部分叫做IO hole,是不可用的,主要被用来分配给外部设备了。
  3. 从0x100000~0x,这部分叫做extmem,是可用的,这是最重要的内存区域。

这个子函数中包括三个变量,其中

npages记录整个内存的页数,

npages_basemem记录basemem的页数

npages_extmem记录extmem的页数。

mem_init的作用就是将现有的物理地址空间,加上页表机制开起后的内容,映射至于memlayout.h中所示的虚拟地址空间结构上去。

接上面虚拟地址转换,在虚拟地址开启之前,首先需要有页表,并且必须先分配一个页目录,kern_pgdir 这个指针就会指向页目录所在地址,boot_alloc 这个函数就是为页目录分配一个页的大小,返回这个指针。

如何计算是否超出内存分配界限?为什么是nextfree - kernbase?

第一次调用boot_alloc 后,分配了一个屋里页面,然后接下来要求我们补充代码,为页表分配物理页面,页表数量为:npages,其物理页面就紧跟着之前那个页目录后面,需要注意的是,第一次计算nextfree需要进行PGSIZE 对齐,end 是前面加载kernel各个段的最后一个段的末尾地址,所以首先会计算该地址向后第一个4kb整数倍的地址来初始化静态变量nextfree的初始值。nextfreeend之间就会有一个空白的余留的空间。之后的物理内存结构如下:

image-20201124192937985

接下来看page_init

注释中已经写明了,他示例代码是把所有物理内存都当做是空闲可用的内存,但从我们上图来看,第0块页是不可用的,BIOS,640kb-1MB这一块也是不可用的,以及在extend mem中已经被使用的部分,所以在init需要把这几块排除。然后将page_free_list当做头指针,将pages[i]的地址&pages[i]当做新节点,依次头插到page_free_list,完成后,page_free_list 就可以当做一个空闲链表的头指针,遍历完所有的页表:pages

然后是:check_page_free_list(1),当输入 参数1时,会将前面生成的空闲链表反转,使得其链接的页表项,是从0-1023,然后检查每个页面是否空闲等。这里不需要我们操作。

继续查看mem_init的代码,进入到:check_page_alloc中,依次补充:page_allocpage_free函数。这两个函数都比较简单,只需要根据page_free_list 去取或者换一个PageInfo ,修改链表状态即可。

part 2 Virtual Memory

接下来手写页表管理程序:pgdir_walk,功能为:给一个页目录指针和线性地址va,返回该地址所对应的页表项指针。

  1. 根据前面虚地址转为物理地址的步骤,第一部分10bit用于找到该地址页表所在页目录项在页目录中的偏移。
  2. 通过该页目录项可以找到该地址对应的页表目录。
  3. 通过虚地址第二部分10bit,找到其页表项在页表中的偏移,返回该页表项的地址。

如何使用mmu.h中定义的宏来快捷的完成这些转换,需要去查看xv6代码中的注释。

接下来完成boot_map_region,该函数的作用在注释中已经写明白了。因为size至少是以PGSIZE对齐的,所以可以用一个循环,每次从SIZE中取一个页的大小,将虚地址和物理地址的对应关系写在页表项中。还记得上个pagedir_walk就是根据虚地址返回该地址对应的页表项,所以这里就可以派上用场:

pte_t entry = pgdir_walk(pgdir,va,1);
*entry = (pa | perm | PTE_P);  //将该页表项记录映射至物理地址pa,修改presence位

再看page_insert:

将物里页面所对应的pageInfo 映射到虚拟地址va

  1. 首先可以根据pagedir_walk找出va对应的页表项。
  2. 增加pp->ref
  3. 如果该页表项的presence不为0,说明已有其他 页面的映射,应该先移除tlb和该映射
  4. 将该页表项修改为该虚拟地址已经对应的flag
  5. 修改对应页表目录项的permission

page_lookup:返回 虚拟地址va所映射的page

  1. pagedir_walk 找到va对应的页表项。
  2. 检查页表项PTE_P为0,若是则直接返回
  3. 通过PTE_ADDR将页表项转为物理地址,然后pa2page将物理地址转为PageInfo

最后page_remove:取消虚拟地址va的映射

  1. pagedir_walk 找到va对应的页表项。
  2. 通过上面的page_lookup找到对应的page
  3. 调用page_decref来处理ref引用计数以及物理页面的释放。
  4. 取消va对应快表缓存

part3 kernel address space

这一节主要研究内存空间布局,主要的练习任务就是补充完mem_init中,check_page之后的代码,首先要映射pages,页表。根据memlayout.h中的虚拟内存示意图,pages在虚拟内存的起始地址为:UPAGES,其长度为 PTSIZE ,其物理地址可以使用相关宏得到:

boot_map_region(kern_pgdir,UPAGES,PTSIZE,PADDR(pages),PTE_U);

接着要映射内核栈,这部分代码注释中指出了,内核站本来很长,达到了PTSIZE,但目前来说分为两部分,我们只需要映射第一部分:[KSTACKTOP-KSTKSIZE, KSTACKTOP).

boot_map_region(kern_pgdir,KSTACKTOP-KSTKSIZE,KSTKSIZE,PADDR(bootstack),PTE_W);

最后将所有的物理地址,都映射到KERNBASE,这句话没看懂,可以参考注释中的例子:

将虚拟地址 [KERNBASE, 2^32) 映射到物理地址 [0, 2^32 - KERNBASE)

boot_map_region(kern_pgdir,KERNBASE,0xffffffff-KERNBASE,0,PTE_W);

到这里,lab2就全部完成啦。