我对虚拟内存的理解


什么是虚拟内存?

先直接摘抄一段 wikipedia 上的介绍。

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

对于 C 语言里面的变量,我们可以使用 & 运算符来获得其地址, 既然是虚拟地址,就是指这个地址是虚拟的。

虚拟地址机制不是必须的,在简单的单片机中,编写的代码编译时都需要指定物理 RAM 空间分布,不会有虚拟地址的概念,地址就是指在 RAM 中的物理地址。

为什么需要虚拟内存?

下面都是我的理解,可能有不恰当的地方。

假设在裸机上运行一个程序,这时就没办法再同时执行其他程序了,需要引入操作系统来进行管理。有了操作系统之后,我们或许能运行多个不同程序,但很可能无法同时执行同一个程序的多个实例,因为同一个程序使用的物理地址是一样的(假设是旧的编译器),一起运行会有冲突。想运行同一个程序的多个实例,看起来有 2 种方案。

  1. 重新编译一个使用其他地址的程序,和前一个不冲突。
  2. 运行该程序时使用添加地址偏移等方式保证使用地址不同。

第一个是让编译器来完成,理论上可以,但是会过于麻烦,如果我想同时运行3个,4个呢, 就必须多编译几次。
第二个呢,是让操作系统来完成,应该算是虚拟内存的雏形了。使用该模式后,编译器给出的程序内相关地址就不是实际物理地址了,算是虚拟地址。

X86 的虚拟内存技术

GDT/LDT

GDT 和 LDT 都是在 80286 的时候引入 x86 体系的,LDT 和 GDT 有着类似的结构。 LDT 的出现就是为了多进程使用独立地址空间来服务的,通常每个进程一个 LDT, 而共享内存和内核内存则使用 GDT。 每个程序根据段描述符来确定基址,而且每个 Entry 里面还有 limit 字段,正好可以对程序访问空间作限制。但在 80386 引入了更优秀的分页技术后,LDT 基本上就不再使用了。

分页

分页作为当前虚拟内存技术的实现,肯定有比 LDT 更好的地方,但它们的实现思路都是类似的。操作系统为每个进程维护一个 handle,这个handle关联的是该进程从虚拟地址到物理地址转换的相关数据块。在 LDT 中 handle就是 LDT 指针与长度, 数据块就是 LDT 自身。分页模式下数据块叫做 paging structure, handler 是指向其的指针。

paging structure 有 4096 bytes, 包含有独立的 entries,不同模式下每个 entry 的大小不同。 每个 entry 包含一个物理地址,可以指向一个 page frame,也可以指向另一个 paging structur,也就是级联的方式。 指向第一个 page structure 的指针在 CR3 寄存器里, 之后从线性地址到物理地址的过程就是一个迭代的过程。线性地址的一部分用来指示对应的 entry, 该 entry 如果指向的是另一个 page structure 则继续,直到指向了一个 page frame则表示地址转换完成,使用最后这个 entry 作为基址,线性地址剩余部分表示偏移。

现在专门讨论 32bit 模式下的分页。32bit下每个 entry 4bytes,每个 paging structure包含 1024 个 entries, 需要 10bit 来区分每个 entry。实际上 32bit 模式下使用了 2 级 pageing structure。 第一级称为 Page Directory, 使用 32bit 线性地址的 bits 31-22 来区分, 第二级称为 Page Table, 使用 32bit 线性地址的 bits 21-12 来区分,剩下的 bits 11-0正好是用来计算在 4K page 里的偏移。

在所有的线性地址到物理地址翻译中, CR3,PDPTE,PDT, PTE等存储的都是下一步的基址, 线性地址中存的则是偏移。

本文永久更新链接地址:

相关内容