xv6的虚拟存储
xv6是MIT为其操作系统课程开发的一个教学目的操作系统,本文将介绍xv6的虚拟存储。
1 基本配置
xv6中页(page)的大小是4096(\(2^{12}\))字节,因此虚拟地址中需要12位来索引页中字节,此外还需要27位来索引页表中的物理页号,所以实际上xv6只使用了64位虚拟地址中的39位,保留了高25位。
一个页表表项(page table entry,后面简称pte)中包含了一个44位物理页号,与虚拟地址中的12位页内偏移合并即为56位的物理地址;除此之外还有10位权限位。
由于页表中常常含有大量的空映射,为了节约存储空间,xv6采用了三级页表,将虚拟地址中27位索引位分成3份各9位分别索引各级页表。如果采用单级页表,创建页表时需要在内存中留出\(2^{27}\)个pte的空间;而三级页表则是先创建一个含有\(2^{9}\)个pte的页表,当添加映射时才需要创建后两级页表,实现了表项的稀疏存储。
2 虚拟地址的转换
2.1 在用户态下的虚拟地址转换
假设用户程序要解引用一个指针,这个指针当中存储的64位地址会被视为虚拟地址,内存管理单元(MMU)会到用户程序的页表中找到虚拟地址相应的表项,然后将虚拟地址转换成物理地址。satp
寄存器中会存储当前程序页表的物理地址,MMU则会使用这个页表进行转换。
satp
寄存器指向第二级页表,MMU使用虚拟页号高9位取得pte,该pte中的物理页号是第一级页表的物理地址,使用虚拟页号中9位索引第一级页表得到第二个pte,这个pte指向第零级页表,最后用低9位索引第零级页表得到最终的pte,将这个pte中的物理页号和虚拟地址中12位偏移量结合得最终的物理地址。
2.2 在内核态下的虚拟地址转换
内核地址空间与用户程序地址空间的不同点在于内核地址空间使用直接映射,即虚拟地址的值和物理地址的值相同。地址空间中除了trampoline和kstack以外都采用了直接映射,稍后会看到直接映射的好处。
在内核态下解引用一个指针,MMU仍然会查询内核页表来转换虚拟地址,只不过转换后得到的物理地址和原来的虚拟地址相同。
图中的free memory区域是内核用来分配给用户程序的内存,因此用户程序使用的物理内存地址不仅在用户页表中存在映射,在内核页表中也存在映射,但是两个映射的虚拟地址不同。
2.3 内核态下需要使用用户提供的虚拟地址
用户程序常常需要内核执行一些任务并返回结果,因此会通过系统调用传递一个虚拟地址,希望内核将执行结果存储到内存中的相应位置。
但是用户程序和内核使用的是完全不相同的两个页表,内核并不能直接解引用这个虚拟地址,但是:
- 内核保存了用户页表的物理地址
- 内核可以通过程序在这个页表中查询到虚拟地址对应的物理地址PA,walk()函数实现了该任务
- 取得这个物理地址PA后,可以直接对其解引用,MMU会将PA视为虚拟地址,然后在内核页表中查询对应的物理地址
- 由于内核页表直接映射的特点,最后得到的物理地址和PA完全相同
注意,2和3是两个不同的过程,2是在软件中进行的,模拟了MMU查询页表的过程,而3是在硬件中进行的过程。这个过程的伪代码如下:
1 | copyout(userVA): |
比如用户程序传递虚拟地址0x0000020000
给内核,内核通过walk函数得到相应的物理地址0x00800e0000
,对其进行解引用并赋值为结果值,MMU在内核页表中查询“虚拟地址”0x00800e0000
对应的物理地址,查询结果为0x00800e0000
,则CPU将结果值存入0x00800e0000
。