X86 架构的 CPU 在上电以后,为了与 8086 保持兼容,还是运行在 16 位实模式下,实模式的特点是所有访存指令访问的都是物理内存地址。
基于局部性原理,CPU 为程序员虚拟化了一层内存,我们只需要与虚拟内存打交道就可以了。
我们可以从两个方面来理解局部性原理。第一个方面是时间局部性,也就是说被访问过一次的内存位置很可能在不远的将来会被再次访问;另一方面是空间局部性,说的是如果一个内存位置被引用过,那么它邻近的位置在不远的将来也有很大概率会被访问。
基于这个原理,我们可以做出一个合理的推论:无论一个进程占用的内存资源有多大,在任一时刻,它需要的物理内存都是很少的。
为了让程序员编程方便,CPU 和操作系统还联手编织了一个假象:每个进程都独享 128T 的虚拟内存空间,并且每个进程的地址空间都是相互隔离的。
任何虚拟内存最终都要映射到物理内存,但虚拟内存的大小又远超真实的物理内存的大小。
操作系统管理着这种映射关系,所以你在写代码的时候,就不用再操心物理内存的使用情况了,你看到的内存就是虚拟内存。这种映射关系是以页为单位的,多个进程的虚拟内存中的页都被映射到物理内存页上。
第一,虽然虚拟内存提供了很大的空间,但实际上进程启动之后,这些空间并不是全部都能使用的。开发者必须要使用 malloc 等分配内存的接口才能将内存从待分配状态变成已分配状态。
在你得到一块虚拟内存以后,这块内存就是未映射状态,因为它并没有被映射到相应的物理内存,直到对该块内存进行读写时,操作系统才会真正地为它分配物理内存。然后这个页面才能成为正常页面。
第二,在虚拟内存中连续的页面,在物理内存中不必是连续的。只要维护好从虚拟内存页到物理内存页的映射关系,你就能正确地使用内存了。这种映射关系是操作系统通过页表来自动维护的,不必你操心。
页表的本质是页表项 (Page Table Entry, PTE) 的数组,虚拟空间中的每一个页在页表中都有一个 PTE 与之对应,PTE 中会记录这个虚拟内存页所对应的实际物理页的起始地址。
每个页表项是 4 字节,1024 个页表项组成一张页表。一个页表项对应着一个大小为 4K 的页,所以 1024 个页表项所能支持的空间就是 4M。
页目录表中的每一项叫做页目录项 (Page Directory Entry, PDE),每个 PDE 都对应一个页表,它记录了页表开始处的物理地址,这就是多级页表结构。现代的 64 位处理器上,为了编码更大的空间,还存在更多级的页表。
一个 CPU 要通过虚拟地址的步骤:
- 第一步是确定页目录基址。在 X86 上,这个寄存器是 CR3
- 第二步是定位页目录项(PDE)。一个 32 位的虚拟地址可以拆成 10 位,10 位和 12 位三段,上一步找到的页目录表基址加上高 10 位的值乘以 4,就是页目录项的位置。
- 第三步是定位页表项(PTE)
- 最后一步是确定真实的物理地址。
此文章为7月Day1学习笔记,内容来源于极客时间《编程高手必学的内存知识》