mit6.828 lab2
Contents
mit6.828 lab2:Memory Management
讲义概要
对于一个4GB的虚拟内存,虚拟地址占到32位。我们将内存中划分为一个一个的页组成,每页占4KB,那么整个虚拟内存中就存在2^20个页面。所以需要有一个映射(翻译)机制来讲虚拟地址转化为物理地址。也即:page table:
它是一个高度为2的树:
树根是一个 page Directory,该结构有1024个条目,每个条目可以指向一个page table。每个page table中又存在1024个entry(PTE)里面保存了某个物理地址的PPN 和一些flag。
所以翻译的流程如下:
先取32位地址的最高10位,查找到page directory的PPN,根据PPN 查找page table。
然后根据接下来的10bit查找page table中的PPN,作为物理地址的高20位,剩余12位原样拼凑到物理地址低12位。
各进程都有各自的页表,其虚拟内存到物理内存的映射关系如下:
当该进程申请内存时,系统会找一块空闲的页面,然后新增一个PTE至其页表中,指向该物理页面。然后设置该PTE的flag,说明其使用情况、读写情况等。
将虚拟地址KERNBASE:KERNBASE+PHTSTOP
映射至:物理地址0-PHYSTOP
,
Lab:
part 1 Memory Management
Lab1 最后进入了kern/init.c 中的i386_init
函数,该函数调用了:
mem_init
函数,这就是本节lab的入口。
首先参考一下下图,lab1结束后,物理内存分布如下:
总体来看,物理地址空间的布局如下:
- 从0x00000~0xA0000,(低640KB)这部分叫做basemem,是可用的。
- 从0xA0000~0x100000,这部分叫做IO hole,是不可用的,主要被用来分配给外部设备了。
- 从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
的初始值。nextfree
与end
之间就会有一个空白的余留的空间。之后的物理内存结构如下:
接下来看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_alloc
和page_free
函数。这两个函数都比较简单,只需要根据page_free_list 去取或者换一个PageInfo ,修改链表状态即可。
part 2 Virtual Memory
接下来手写页表管理程序:pgdir_walk
,功能为:给一个页目录指针和线性地址va,返回该地址所对应的页表项指针。
- 根据前面虚地址转为物理地址的步骤,第一部分10bit用于找到该地址页表所在页目录项在页目录中的偏移。
- 通过该页目录项可以找到该地址对应的页表目录。
- 通过虚地址第二部分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
- 首先可以根据
pagedir_walk
找出va对应的页表项。 - 增加pp->ref
- 如果该页表项的presence不为0,说明已有其他 页面的映射,应该先移除tlb和该映射
- 将该页表项修改为该虚拟地址已经对应的flag
- 修改对应页表目录项的permission
page_lookup
:返回 虚拟地址va所映射的page
pagedir_walk
找到va对应的页表项。- 检查页表项PTE_P为0,若是则直接返回
- 通过
PTE_ADDR
将页表项转为物理地址,然后pa2page
将物理地址转为PageInfo
最后page_remove
:取消虚拟地址va的映射
pagedir_walk
找到va对应的页表项。- 通过上面的
page_lookup
找到对应的page - 调用
page_decref
来处理ref引用计数以及物理页面的释放。 - 取消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就全部完成啦。
Author 秋酱
LastMod 2020-11-27