2-虚拟内存的实现
虚拟内存实现
页表
硬件通过处理器和 MMU(Memory Management Unit)实现
任何一条地址应该认为是虚拟内存地址;该地址会转到 MMU,MMU 翻译为物理地址,从物理地址加载
MMU 只是去查看 page table,page table 存在于内存中
虚拟内存地址只使用了低 39bit;低 12bit 作为页内地址偏移(offset),高 27bit 是索引页的 index,实际上是由 3 个 9bit 的数字组成(L2,L1,L0)。前 9 个 bit 被用来索引最高级的 page directory(注:通常 page directory 是用来索引 page table 或者其他 page directory 物理地址的表单
用 44bit 的 PPN,再加上 12bit 的 0,这样就得到了下一级 page directory 的 56bit 物理地址。这里要求每个 page directory 都与物理 page 对齐
- 3 级页表由硬件实现的,所以 3 级 page table 的查找都发生在硬件中。MMU 是硬件的一部分而不是操作系统的一部分。
- 在 XV6 中,有一个函数也实现了 page table 的查找,因为时不时的 XV6 也需要完成硬件的工作,所以 XV6 有这个叫做 walk 的函数,它在软件中实现了 MMU 硬件相同的功能。
PTE 结构
- 第一个标志位是 Valid。如果 Valid bit 位为 1,那么表明这是一条合法的 PTE,你可以用它来做地址翻译。对于刚刚举得那个小例子(应用程序只用了 1 个 page 的例子),我们只使用了 3 个 page directory,每个 page directory 中只有第 0 个 PTE 被使用了,所以只有第 0 个 PTE 的 Valid bit 位会被设置成 1,其他的 511 个 PTE 的 Valid bit 为 0。这个标志位告诉 MMU,你不能使用这条 PTE,因为这条 PTE 并不包含有用的信息。
- 下两个标志位分别是 Readable 和 Writable。表明你是否可以读/写这个 page。
- Executable 表明你可以从这个 page 执行指令。
- User 表明这个 page 可以被运行在用户空间的进程访问
TLB
每个核有自己的 MMU 和 TLB
如果切换 page table,操作系统需要告诉处理器当前正在切换 page table,处理器会清空 TLB。因为本质上来说,如果你切换了 page table,TLB 中的缓存将不再有用,它们需要被清空,否则地址翻译可能会出错。
在 RISC-V 中,清空 TLB 的指令是 sfence_vma
虚拟地址映射
映射过程
CPU 将虚拟地址传入 MMU,MMU 根据 SATP 中的值判断是否进行虚拟内存和物理内存的映射
- SATP=0,虚拟地址就是物理地址
- SATP 为页表的根地址,进行物理页的索引
内核
free memory 来存放用户进程的 page table,text 和 data。如果我们运行了非常多的用户进程,某个时间点我们会耗尽这段内存,这个时候 fork 或者 exec 会返回错误。
左侧低于 PHYSTOP 的虚拟地址,与右侧使用的物理地址是一样的。
kernel stack 之下有一个未被映射的 Guard page,这个 Guard page 对应的 PTE 的 Valid 标志位没有设置,这样,如果 kernel stack 耗尽了,它会溢出到 Guard page,但是因为 Guard page 的 PTE 中 Valid 标志位未设置,会导致立即触发 page fault;Guard page 不会映射到任何物理内存,它只是占据了虚拟地址空间的一段靠后的地址。
kernel stack 被映射了两次,在靠后的虚拟地址映射了一次,在 PHYSTOP 下的 Kernel data 中又映射了一次,但是实际使用的时候用的是上面的部分,因为有 Guard page 会更加安全。