文章目录
- 1.CPU寻址方式
- 2.段式内存访问的缺点
- 3.80386两级页表
- 4.PAE三级页表
- 5.x64四级页表
- 6.虚拟内存
思考一个问题:如果没有这样的分页机制时应用程序是怎么访问物理内存地址?
1.CPU寻址方式
Effective Address = Base + (Index * Scale) + Displacement
- Displacement:位移,是一个8位、16位、32位的值。一个单独的位移表示距离操作数的直接偏移量。因为位移被编码在指令中,所以一般用于
编译阶段静态分配的全局变量
之类。 - Base:基址,存放在某个通用的寄存器中。将内存地址存储在某个通用寄存器中,寄存器的值可以变化,所以一般用于运行时动态分配
变量、数据结构
等。 - Index:索引,存放在某个通用寄存器中,ESP不用做索引。
- Scale:比例索引,用来与索引想乘,可以取值1、2、4、8。
基址+位移:尤其适合寻址运行时分配的数据结构的字段,以及函数栈帧上的变量。
2.段式内存访问的缺点
想象一下下面这样的场景,因为没有页表机制,所以类似如下图这种段式访问是直接作用于物理内存上的,那如果现在一个新的进程需要11M的空间,按照目前的内存划分情况,即使总的空闲区域的内存空间是能满足需要的,但是由于物理内存的连续性,造成了这种低效性
,因此必须等待进程A和进程B完成后才划分空间给进程C。
实际上,在DOS时代,应用程序直接访问物理内存,代码中的地址实际都是物理内存地址。任何程序都有权读写所有的物理内存,稍有不慎就会覆盖其他程序的代码或数据
,连操作系统内核也无法自保。随着80386芯片的到来,PC进入了保护模式,并且开启了内存分页模式,通过特权级和进程地址空间隔离机制
解决上述问题。如今,主流的操作系统采用分页的方式管理内存。
在分页模式下,应用程序中使用的地址被称为线性地址
,需要由MMU(memory management unit)基于页表映射转换为物理内存地址,整个转换过程对于应用程序来说是完全透明的。
3.80386两级页表
- 80386架构的线性地址的宽度为
32
位,所以可以寻址4GB
大小的空间,与进程的地址空间大小相对应。地址总线为32位,硬件可以寻址4GB的物理内存。分页机制将每个物理内存页面
的大小设定为4096
字节,并按照4096对齐。 - 因为每个页面的大小为4096字节,并且地址总线的宽度为32位,所以每个页面正好可存储
1024
个物理内存地址。完整的页表结构的第一层是1个页目表页面,其物理地址存储在CR3寄存器中,通过页目录页面进一步找到第二层的1024个页表页面。 - 32位线性地址被MMU按照10位+10位+12位划分。
4.PAE三级页表
80386架构的线性地址的宽度为32位,每个进程拥有4GB的线性地址空间。主流操作系统一般按照2:2或者3:1的方式进一步将进程的4GB空间划分为用户空间和内核空间
。因为内核只有一份,所以内核占用的这组物理内存页面由所有进程共享,而每个进程独享的2GB或3GB的用户空间,即所谓的进程地址空间隔离就是通过进程独立的页表实现的
,然而硬件32位的地址总线只能寻址4GB的物理内存,在多进程的操作系统上,每个进程能够映射到的物理页面远远不足2GB。在这种情况下,Intel推出了物理地址扩展的技术(Physical Address Extension ,PAE)。
PAE将地址总线拓展到36
位,从而使得硬件能够寻址多达64GB的物理内存。线性的宽度仍然为32位,而MMU的页表映射机制需要进行相应调整
,以支持从32位线性地址到36位物理地址的映射。
为了支持36位的物理映射地址,页目录和页表中的地址被调整为64位,一个页面只能存储512个地址。MMU将32位的线性地址按照2位+9位+9位+12位划分。整体架构如下:
5.x64四级页表
通过PAE技术,虽然硬件支持的物理内存变大了,但进程的地址空间大小并没有变化。对于某些类型的程序,例如数据库程序,进程2~3GB的用户地址空间成为明显的瓶颈,而且32位的数据宽度也无法满足时下的计算需求,所以64位架构应运而生。
常见的x64、x86_64都是指amd64架构,如今的个人计算机基本是基于amd64架构的。
在amd64上,寄存器的宽度变成了64位,而线性地址实际只用到48位,也就是最大寻址256TB的内存。当然很少有单台计算机会安装如此大量的内存,所以没有必要实现48位的地址总线,常见的个人计算机的CPU的地址总线实际还不到40位。
amd64在PAE的基础之上进一步把页表拓展为四级,每个页面的大小仍然为4096字节,MMU将48位的线性地址按照9位+9位+9位+12位
划分,整个过程如下图所示:
6.虚拟内存
乍看起来,完整的页目录结构会占用大量的内存,例如在80386上就会占用1+1024=1025个物理页面。因为页目录本身也被用作页表,所以实际上就1024个页面,总共占用4096x1024=4M的空间。因为系统空间是所有进程共享的,所以对应的页表也是共享的,而大多数进程并不会申请大量的用户空间内存,用不到的页表也不会被分配,所以进程的页表是稀疏
的,并不会占用大多的内存。
进程是以页面为单位向操作系统申请内存的
,操作系统一般只是对进程已经申请的区间进行记账,并不会立即映射所有页面。等到进程真正去访问某个未映射的页面时,才会触发Page Fault异常,操作系统注册的Page Fault Handler会检查被中断程序,程序对这一切都是无感的,如果目标地址未申请,都是非法访问,系统一般会通过信号、异常等机制结束目标进程。
当物理内存不够用的时候,操作系统可以把一些不常用的物理页面写到磁盘交换区或交换文件,从而能够将空出的页面给有需要的进程使用。当被交换到磁盘的页面再次被访问时,也会触发Page Fault,操作系统的Page Fault Handler复杂从交换分区把数据加载回来内存。程序对这一切都是无感的,并不知道某个页面到底是在磁盘上还是在物理内存中,所以称为进程的虚拟内存
。