10分钟快速了解虚拟内存

346 阅读6分钟

计算机科学中的每个问题都可以用一间接层解决,虚拟内存亦是如此

好的抽象应当是简洁的,虚拟内存在OS的应用很复杂,但背后的模型其实简单

先抛个基础问题:一台机器上运行很多服务,其可用内存为何看起来远超物理内存?

物理内存的局限

计算机的主存是由多个连续的单元组成的,每个单元称为一个字节,每个字节都有一个唯一的物理地址 (Physical Address, PA)。

最早期的计算机是运行在“实模式”下的,实模式的特点是所有访存指令访问的都是物理内存地址。这会导致一个很大的问题:

因为这种模式下,必然要求程序员手动对数据进行布局,那么内存不够用怎么办呢?而且,每个进程分配多少内存、如何保证指令中访存地址的正确性,这些问题都全部要程序员来负责。

因此在现如今的多进程(多任务)计算模式下,“实模式”不具备可行性。

(在嵌入式开发中,往往没有进程的概念,也就是说整个应用独享全部内存,所以手动管理内存才有可能性。)

既然“实模式”不可行,那就得分析程序的数据访问特征,看看我们能否做一层抽象?核心是“局部性原理”。

局部性原理

程序的数据访问会表现出明显的倾向性。这种倾向性,我们就称之为局部性原理 (Principle of locality)。

  • 时间局部性,也就是说被访问过一次的内存位置很可能在不远的将来会被再次访问
  • 空间局部性,说的是如果一个内存位置被引用过,那么它邻近的位置在不远的将来也有很大概率会被访问

一个结论:无论一个进程占用的内存资源有多大,在任一时刻,它需要的物理内存都是很少的(如果很大,说明该优化代码啦)。在这个推论的基础上,CPU 为每个进程只需要保留很少的物理内存就可以保证进程的正常执行了。

OS的先驱们很早注意到这个原理,他们做了个抽象,提出了“虚拟内存”的概念。

虚拟内存

通过中间层,让每个进程都独占一样大小的内存空间,各进程之间的内存不存在冲突的问题,程序员不需要去管理具体的物理内存地址,这个问题将全收敛到OS来解决。

CPU 充分利用程序局部性原理,提出了虚拟内存和物理内存的映射 (Mapping) 机制:

img

每个进程都有一个描述虚拟空间的表,该表对进程的虚拟地址按区间划分,每个划分的区间都有表项来描述具体的物理地址。

  • 正常页面:当前运行中的程序代码块、数据块、正处理的资源,占用了物理内存,在表中已映射。
  • 未分配页面:未被进程需要的地址空间,如C开发者必须要使用 malloc 等分配内存的接口才能将内存从待分配状态变成已分配状态。
  • 为映射页面: 如局部性原理下,进程run起来后有些代码块还不需要被加载执行,那么可以先在表里面占个位,具体的物理内存等需要执行时再按需分配(有可能是被OS换出,也有可能未从磁盘加载到内存,依赖OS的缺页中断来分配。)

这样的模型下,对内存的管理就有了极大的操作空间:

  • 更低的内存空间要求:比如可以把一些当前不被使用、热度较低的内存数据置换到磁盘等其它存储介质,腾出来给其它任务使用。
  • 分配效率的提高:在虚拟内存中连续的页面,在物理内存中不必是连续的。提升了物理内存的空间利用率,减少碎片化问题。

虚拟内存的抽象对开发者来说屏蔽了内存分配的细节,极大地降低了开发者的成本,那么OS是如何应用的?

页表

每个进程的虚拟空间大小都是一样的,有单独的页表来描述物理内存映射关系。

映射的过程,是由 CPU 的内存管理单元 (Memory Management Unit, MMU) 自动完成的,但它依赖操作系统设置的页表。

img

页表的本质是页表项 (Page Table Entry, ``PTE) 的数组,虚拟空间中的每一个页在页表中都有一个 PTE 与之对应。

抽象出页表的中间层后,不仅能解决物理内存分配的问题,还能做一些安全的事情,PTE 中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。

(注:当前市场上主流的处理器也都选择将页大小定为 4K)

为了管理这些页表,我们还可以继续引入页表的数组:页目录表, 为应付更大的地址空间

img

好了,现在有了页表,CPU怎么去找到真实地址?

img

上文说了,每个进程都有一个单独的页表,这个页表的基地址是需要维护在进程的上下文的,每次进程被调度时就会把页表的基地址加载到寄存器 (在 X86 上,这个寄存器是 CR3。每一次计算物理地址时,MMU 都会从 CR3 寄存器中取出页目录所在的物理地址。)

大体过程如下,拿到虚拟地址后:

  1. 定位页目录项(PDE)
  1. 定位页表项(PTE)
  1. 确定真实的物理地址(虚拟地址的低 12 位,刚好可以对一页内的所有字节进行编码,所以我们用低 12 位来代表页内偏移)

虚拟内存技术,现实体系架构的应用情况是怎样的?看看x86体系架构

x86体系架构中的实模式和保护模式

8086 中的实模式

1978 年发行的 8086 芯片是 x86 架构的首款芯片,它在内存管理上使用的是直接访问物理内存的方式,这种工作方式,有一个专门的名称,那就是实模式(Real Mode)。

8086 的寄存器只有 16 位,我们也习惯于称 8086 的工作模式是 16 位模式。

在实模式下,程序员是不能通过内存管理单元(Memory Management Unit, MMU)访问地址的,程序必须直接访问物理内存。

i386 中的保护模式

x86 CPU 迎来了历史上使用最广泛、影响力最大的 32 位 CPU,这就是 i386 芯片。

i386 在完成各种初始化动作以后,就会开启页表(由此引入了虚拟内存技术),从此程序员就不必再直接操作物理内存的地址空间了,代替它的是线性地址空间。而且由于段和页都能提供对内存的保护,安全性也得到了提升,所以这种工作模式被称为保护模式(Protection Mode)

有兴趣的可以再去google了解下。

ref:

draveness.me/whys-the-de…

en.wikipedia.org/wiki/I386

time.geekbang.org/column/arti…