虚拟内存¶
参考:【读薄 CSAPP】柒 虚拟内存与动态内存分配 | 小土刀 2.0
操作系统将物理内存划分为固定大小的页面(通常是4KB或8KB),并将其映射到磁盘上的一个或多个页面文件中。这些页面文件可以是预先设置的交换文件或页面交换文件。
当程序需要更多的内存时,操作系统会将当前未使用的物理内存页面移动到磁盘上的页面文件中,并将程序所需的新页面加载到物理内存中。这样,程序就能够继续执行,尽管实际上它只能看到自己的虚拟内存,而不需要关心物理内存的限制。
虚拟内存就是存储在磁盘上的 N 个连续字节的数组 部分内容会缓存在DRAM中。 在DRAM中的每个缓存块(cache block)称为 页(page)
每个页表实际上是一个数组,数组中的每个元素称为页表项(PTE, page table entry),每个页表项负责把虚拟页映射到物理页上。在 DRAM 中,每个进程都有自己的页表,具体如下
虚拟内存页表存储在操作系统的内核空间中,由 页目录 和 页表 组成,并通过特定的索引方式实现虚拟地址到物理地址的映射。
作为内存管理工具¶
前面提到,每个进程都有自己的虚拟地址空间,这样一来,对于进程来说,它们看到的就是简单的线性空间(但实际上在物理内存中可能是间隔、支离破碎的),具体的映射过程可以用下图表示:
在内存分配中没有太多限制,每个虚拟页都可以被映射到任何的物理页上。这样也带来一个好处,如果两个进程间有共享的数据,那么直接指向同一个物理页即可(也就是上图 PP 6 的状况,只读数据)
虚拟内存带来的另一个好处就是可以简化链接和载入的结构(因为有了统一的抽象,不需要纠结细节)
作为内存保护工具¶
页表中的每个条目的高位部分是表示权限的位,MMU 可以通过检查这些位来进行权限控制(读、写、执行),如下图所示:
地址翻译¶
其中 N
表示虚拟地址空间中的地址数量,M
表示物理地址空间中的地址数量,P
是每一页包含的字节数(page size)。
虚拟地址(VA, Virtual Address)中的元素:
TLBI
: TLB 的索引值TLBT
: TLB 的标签(tag)VPO
: 虚拟页偏移量VPN
: 虚拟页编号
物理地址(PA, physical address)中的元素:
PPO
: 物理页偏移量(与VPO
的值相同)PPN
: 物理页编号
然后我们通过一个具体的例子来说明如何进行地址翻译
具体的访问过程为:
- 通过虚拟地址找到页表(page table)中对应的条目
- 检查有效位(valid bit),是否需要触发页错误(page fault)
- 然后根据页表中的物理页编号(physical page number)找到内存中的对应地址
- 最后把虚拟页偏移(virtual page offset)和前面的实际地址拼起来,就是最终的物理地址了
这里又分两种情况:Page Hit 和 Page Fault,我们先来看看 Page Hit 的情况
主要有 5 步,CPU 首先把虚拟地址发送给 MMU,MMU 检查缓存,并把从页表中得到对应的物理地址,接着 MMU 把物理地址发送给缓存/内存,最后从缓存/内存中得到数据。
而 Page Fault 的时候就复杂一些,第一次触发页错误会把页面载入内存/缓存,然后再以 Page Hit 的机制得到数据:
这里有 7 步,前面和 Page Hit 是一致的,先把虚拟地址发给 MMU 进行检查,然后发现没有对应的页,于是触发异常,异常处理器会负责从磁盘中找到对应页面并与缓存/内存中的页进行置换,置换完成后再访问同一地址,就可以按照 Page Hit 的方式来访问了。
虽然缓存已经很快了,但是能不能更快呢,为什么不能直接在 MMU 进行一部分的工作呢?于是就有了另外一个设计:Translation Lookaside Buffer(TLB)。TLB 实际上可以认为是页表在处理芯片上的缓存,整体的机制和前面提到的缓存很像,我们通过下面的图进行讲解:
这里 VPN + VPO 就是虚拟地址,同样分成三部分,分别用于匹配标签、确定集合,如果 TLB 中有对应的记录,那么直接返回对应页表项(PTE)即可,如果没有的话,就要从缓存/内存中获取,并更新 TLB 的对应集合。