虚拟地址到物理地址到转换需要经过两步,页表在物理内存中的存储方式是以两层树的方式进行的。树的根就是一个占据4096bytes的页目录,包含了1024条指向页表的32位的PTEs。首先,paging硬件利用虚拟地址的最高10位来选择一个页目录的入口地址,若该地址是存在的,那么进行第二步,也就是利用虚拟地址接下来的10位来选择页目录所指向的页表。当然,如果这两步中存在一步发生没有选择的地址,那么会产生转换失败。每个PTE包含PNN的同时,其FLAG存储着该地址的信息。
当在main中调用函数kvmalloc时,操作系统就切换到了另一个页表,这是由于内核对进程空间的描述有更加周密的方案。
每一个进程又一个独立的页表,并且xv6要求页表硬件:当进程进程切换时,页表也要进行切换。
Xv6用页表来给每一个进程一个地址空间。如图,地址空间包括从地址0开始的用户内存,紧接着时指令、全局变量,然后时栈,最后是堆。
再看下图,这是一个进程的虚拟地址空间以及物理地址空间的布局。注意一个机器有大于2G的物理内存,xv6只能利用KERNBASE到0xFE00000之间的内存。一个进程的用户内存从地址0开始,可以增长到KERNBASE,允许一个进程地址达到2GB内存(包括内核)。文件memlayout.h (0200)声明了xv6内存布局的常量和转换虚拟地址到物理地址到宏。
当一个进程需要向xv6申请更多的内存时,xv6首先找空闲的页表来提供储存,然后将PTEs加入到指向到新的物理地址的进程页表中。设置PTE的FLAG,大多数进程不会利用到整个用户内存地址空间,所以xv6就清理那些空闲的PTEs的PTE_P。不同的进程的页表将用户地址转换为不同的物理内存的页,以至于每个进程都有私有的用户内存。
xv6还包括对所有内核进程页表的映射。它将虚拟地址KERNBASE:KERNBASE+PHYSTOP映射到 0:PHYSTOP,原因一是内核能使用它自己的指令和数据。另一个理由是,内核有时需要能过对一个物理内存给定的页进行写操作。当然,这样做对缺陷就是xv6不能利用大于2GB的物理内存。一些设备也是可以从虚拟地址映射到物理地址。
这样使得所有的页表都包括了用户内存和整个内核,这在系统调用和中断时,切换用户代码和内核代码的时候就非常方便了:这样的切换不需要页表的切换。内核并没有它自己的页表,它总是借助进程的页表。
总的来说就是,xv6保证每个进程可以使用它自己的内存,并且视自己的内存为从地址0开始的毗邻的虚拟地址。
code:
main 调用 kvmalloc (1857)
setupkvm (1837)
mappages (1779) kmap(1828)
walkpgdir (1754)