超全的深度剖析内存系列——3. 虚拟内存 (二)

回忆下我们在上一节博客中提到的虚拟内存技术,操作系统使用它来为每一个进程提供一个一致的,私有的虚拟地址空间。

从 PC 硬件的角度来看,内存主要分为物理内存与磁盘上的文件。由于操作系统使用了虚拟内存技术来高效地管理内存,因此从操作系统的角度来看,虚拟内存空间也称为虚拟内存,对应的就是硬件上的物理内存和磁盘文件的并集。而虚拟地址空间就是虚拟内存地址的取值范围。都每一个进程而言,它们面对的都是大小相等却彼此独立的虚拟内存空间。

逻辑上来讲,虚拟内存可以被看作是存放在磁盘上的 N 个连续字节大小的单元组成的数组,每个字节都有一个唯一的虚拟地址作为数组的索引。

虚拟内存主要为操作系统提供了三个功能:

  • 使物理内存称为虚拟内存的缓存
  • 作为内存管理的工具
  • 作为保护内存的工具

1. 虚拟内存作为缓存的工具

虚拟内存技术将虚拟内存分割为了大小固定的块,这些块被称为虚拟页 (Virtual Page, VP)。类似地,物理内存也被分割为了大小相同的物理页 (Physical Page, PP),物理页有时候也被称为页帧 (page frame)。

虚拟内存也就是虚拟页的集合可以被分为以下三类:

  • 未映射的虚拟页:这种虚拟页是属于虚拟内存系统还没有创建的页,因此它不会占据任何磁盘上的空间。
  • 缓存的虚拟页:这种虚拟页已经被虚拟内存系统创建 (映射到磁盘上),同时也被缓存到了物理内存上。
  • 未缓存的虚拟页:这种虚拟页已经被虚拟内存系统创建 (映射到磁盘上),但没被缓存到了物理内存上。

下图展示了一个拥有 8 个虚拟页的的虚拟内存和 6 个物理页的物理内存:

39cc5d46a61e432c95188e4c2019b3aa.png

其中虚拟页 VP0,VP3 都还没有被虚拟内存系统分配,因此它们并不存在在磁盘上;虚拟页 VP1,VP4 和 VP6 已经被缓存到了物理页 PP1,PP3 和 PP5 上;虚拟页 VP2,VP5 和 VP7 已经被虚拟内存系统分配 (映射到了磁盘上), 但是当前没有缓存到物理页上。

1.1 页表

既然虚拟内存是被虚拟内存系统用作了物理内存的缓存,那么虚拟内存系统就必须通过某种方法确定是否某虚拟页已经缓存在了物理内存上。如果已经缓存在了物理内存上,虚拟内存系统还需要知道缓存在了具体哪个物理页上;如果没有缓存在物理内存上,虚拟内存系统需要知道该虚拟页在磁盘的什么位置,同时选择某一个物理页作为牺牲页,将虚拟页的内容复制到物理内存中替换掉选择的牺牲页。

如此复杂的功能需要软硬件联合提供,包括操作系统软件,内存管理单元 (Memory Management Unit, MMU) 中的地址翻译硬件和一个能够表示虚拟页与物理页映射关系的数据结构页表(Page Table)。

下图展示了一个常驻内存的页表,一个有 8 个虚拟页的虚拟内存和一个有 4 个物理页的物理内存:

70eacbc628864b3dacc855f44da80572.png

页表实际就是一个包含有多个页表条目 (Page Table Entry, PTE) 的数组。为了简化讨论,我们目前假设每个 PTE 都由一个有效位)和一个 n 位的地址字段组成。若有效位为 0,则表示该 PTE 对应的虚拟页没有被缓存到物理内存上,此时地址段上为虚拟页所在磁盘上的位置;若有效位为 1,则表示 PTE 所对应的虚拟页被缓存到了物理内存上,此时地址段上存储的是物理内存中物理页的位置。如果虚拟页还未被分配,则有效位为 0,地址段上为 null。

如上图所示,虚拟页 VP0 和 VP5 并没有被虚拟内存系统分配,因此在 PTE 的地址字段存储位 null,有效位为 0;虚拟页 VP1,VP2,VP4 和 VP7 被缓存在了物理页 PP0,PP1,PP3 和 PP2 上;虚拟页 VP3 和 VP6 已经分配了,但是当前没有缓存到物理内存上去。

1.2 页命中

如果 CPU 要访问的虚拟页已经被缓存到物理内存上的物理页,那么 CPU 就可以直接从物理内存中读取需要的数据,我们将这种情形称为页面中

下图展示了 CPU 想要读取虚拟页 VP2 中的数据时,发生了什么:

1e689960f91e4cac978bf4dc16dacce5.png

地址翻译硬件将虚拟地址作为索引来定位 PTE2,并从内存中读取它。由于 PTE2 中的有效位为 1,因此地址翻译硬件就知道 VP2 已经被缓存到了物理内存上了。因此它可以直接使用 PTE2 中地址字段中的地址,构建出一个需要访问的数据的物理地址。

1.3 缺页

如果 CPU 要访问的虚拟页在物理内存上未命中,我们称这种情形为缺页 (page fault)。

下图展示了 CPU 想要访问 VP3 中的数据,触发了缺页异常:

8a8754b4eda84ba79a0f4a0926a22764.png

CPU 需要访问 VP3 中的数据,因此地址翻译硬件在内存中读取 PTE3,从有效位 0 判断出 VP3 并没有被缓存到物理内存中,此时触发一个缺页异常。

下图展示了面对缺页异常,内核采取的措施:

ee01063aadb54cb1b2b5e436505bc20f.png

缺页异常的触发会导致内核中缺页异常处理程序的调用,首先该程序会在虚拟页中选择一个牺牲页,在这个例子中选择的是原本被缓存在物理页 PP3 上的虚拟页 VP4。如果之前 VP4 的数据在物理内存中被修改了,内核会将被修改后的 VP4 内容复制回磁盘上。之后内核会修改页表条目 PTE4 有效位为 0,地址字段指向 VP4 在磁盘中的位置。

紧接着内核会从磁盘上复制 VP3 到物理内存中的 PP3,并更新 PTE3 有效位为 1,地址字段指向 PP3 的位置,此时缺页异常处理程序完成了它的功能,开始返回。返回时它会重新启动之前导致缺页异常的那条指令,该指令会把导致缺页异常的虚拟地址再次发送给地址翻译硬件。此时,VP3 已经缓存在物理内存的 PP3 上了,因此该执行页命中后正常逻辑了。

1.4 分配虚拟页

之前例子中页表的 PTE0 和 PTE5 都处于未分配状态,下图展示虚拟内存系统分配一个新的虚拟页时对页表的操作:

231fb2dd1615440a87876fa2e537b703.png

本质上来说,分配新的虚拟页 VP5 的过程,就是在磁盘上创建空间,并更新页表 PTE5,使它指向磁盘上新建空间的位置。

2. 虚拟内存作为内存管理的工具

虚拟内存同样为操作系统提供了一种自然的管理内存的机制。

在上一节中的例子中,我们都在假设在内存中有一个单独的页表,用来将虚拟内存的虚拟地址空间映射到物理内存的物理内存上。事实上,如下图所示,操作系统为每一个进程都提供了一个独立的页表,因而每个进程都拥有一个一致的却独立的虚拟地址空间了。

79df8080dc2b405ab8af60ba654bf375.png

如上图所示,进程 i 的页表将 VP1 和 VP2 分别映射到物理内存的 PP2 和 PP7 上去;而进程 j 的页表将 VP1 和 VP2 分别映射到物理内存的 PP7 和 PP10 上。

因此,当我们在两个进程中使用相同的虚拟地址去定位 VP1 时,实际上访问到的是不同物理内存上的数据,从而保证了每个进程拥有一致的并且独立的虚拟内存空间。为了方便进程间共享某些内存上的数据,多个虚拟页面是可以被映射到相同物理页上的,如上图中的物理页面 PP7。

每个进程都使用的是类似的虚拟内存格式, 如下图

516ddff6106346acb2e08d42aab5038b.png

3. 虚拟内存作为内存保护的工具

所有的操作系统都需要提供一种机制用来控制对内存的访问。比如,不应该允许一个用户进程修改它的只读代码段;也不应该允许它读取或是修改任何内核中的代码和数据结构;不应该允许它读取或者写其它进程的私有内存等等。

正如我们上一节所提到的,独立的地址空间使得区分不同进程的私有内存变得相当容易。但是要想严密地控制对内存的访问,光靠地址空间还是不够的。由于地址翻译硬件在翻译地址时都会读取页表中的 PTE,因此一种简单的方法就是在 PTE 上添加一些额外的许可位来控制对虚拟页的访问。下图展示了大致的思想:

b4f4caa4440a4a4a95ba15d83a2ee452.png

在这个实例中,每个 PTE 中额外添加了三个控制许可位。SUP 位表示进程是否必须运行在超级用户模式下才能访问该页;READ 位和 WRITE 位控制对页面的读和写。比如进程 i 运行在用户模式下,那么它有对页面 VP0 的读权限,VP1 的读写权限,但是没有访问 VP2 的权限。

注:本篇博客中使用的图片来自于《深入理解计算机系统》,特此声明

参考:

  1. 《深入理解计算机系统》

总结不易,转载请注明:

转:http://www.wxueyuan.com/blog/articles/2017/11/10/1510323484783.html
文章来源:Jesmin 的个人博客