虚拟内存是当今计算机系统中最重要的抽象概念之一,它的提出是为了更加有效地管理内存并且降低内存出错的概率。
计算机存储器
存储器是计算机的核心部位之一,在完全理想状态下,存储器应该具备以下三个特性:1.速度足够快。2.容量足够大。3.价格足够便宜。
但是现实很残酷,我们当前的计算机技术无法同时满足上述的三个条件,于是现代计算机的存储器设计采用了一种分层次的结构:
从顶至底,现代计算机里的存储器类型分别为:寄存器,高速缓存,主存和磁盘,这些存储器的速度逐级递减而容量逐级递增。存取速度最快的是寄存器,因为寄存器的制作材料和CPU是相同的,所以速度和CPU一样快,CPU访问寄存器是没有延迟的,然而因为价格昂贵,因此容量很小。
第二层是高速缓存,也就是我们平时了解的CPU高速缓存L1,L2,L3,一般L1是每个CPU独享,L3是全部CPU共享,而L2则根据不同的架构设计会被设计成独享或者共享两种模式之一。
第三层是主存,通常称为随机访问存储器,是与CPU直接交换数据的内存存储器,它可以随时读写(刷新除外),而且速度很快,通常作为操作系统或其他正在运行中的程序的临时资料存储介质。
最后是磁盘,磁盘与主存相比,容量更大,而缺点就是访问速度则比主存慢了大概三个数量级。
主存
物理内存
我们平时一直提及的物理内存就是上文中的对应的第三种计算机存储器,RAM主存,它在计算机中以内存条的形式存在,嵌在主板的内存槽上,用来加载各种各样的程序与数据以提供CPU直接运行和使用。
虚拟内存
虚拟内存是现代计算机中的一个非常重要的存储器抽象,主要用于解决应用程序日益增长的内存使用需求,现代计算机系统对物理主存RAM进行了抽象,实现了虚拟内存技术。
虚拟内存的核心原理是:为每个程序设置一段连续的虚拟地址空间,把这个地址空间分割成多个具有连续地址范围的页,并把这些页和物理内存做映射,在程序运行期间动态映射到物理内存,当程序引用到一段在物理内存的地址空间时,由硬件立即执行必要的映射,而当程序引用到一段不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。
虚拟内存技术使得整个进程的地址空间可以通过较小的虚拟单元映射到物理内存,而不需要为程序的代码和数据地址进行重定位。
虚拟地址空间按照固定大小划分成被称为页的若干单元,物理内存中对应的则是页框。这两者一般来说是一样的大小,如上图中是4KB,不过实际上计算机系统中一般是512字节到1GB,这就是虚拟内存的分页技术,因为是虚拟内存,每个进程分配的大小为4GB,而实际上当然不可能给所有在运行中的进程都分配4GB的物理内存,所以虚拟内存技术还需要利用到一种交换的技术,也就是通常所说的页面置换算法,在进程运行期间只分配映射当前使用到的内存,暂时不使用的数据则写回磁盘作为副本保存,需要用的时候再读入内存,动态地在磁盘和内存之间交换数据。
页表
每次进行虚拟地址到物理地址的映射之时,都需要读取页表,从数学角度来说页表就是一个函数,入参是虚拟页号,输出是物理页框号(也就是物理地址的基址)。
页表由多个页表项组成,页表项的结构取决于机器架构,不过基本上都大同小异。
物理页框号:这是页表中最重要的域值,毕竟页表的存在的意义就是提供虚拟页号到物理页框号的映射。
有效值:表示该页面当前是否存在于主存中。
保护位:指示该页面所允许的访问类型。
修改位和访问位:为了记录页面使用情况而引入的,一般是页面置换算法会使用到。
高速缓存禁止位:用于禁止页面被放入CPU高速缓存。
地址翻译
进程在运行期间产生的内存地址都是虚拟地址,如果计算机没有引入虚拟内存这种存储器抽象技术的话,则CPU会把这些地址直接发送到内存地址总线上,然后访问和虚拟地址相同值的物理地址,如果使用虚拟内存技术的话,CPU则是把这些虚拟地址通过地址总线送到内存管理单元,内存管理单元将虚拟地址翻译成物理地址之后再通过内存总线去访问物理内存:虚拟地址(比如16位地址)分为两部分:虚拟页号和偏移量,虚拟地址转换成物理地址是通过页表来实现的。
这里我们基于一个例子来分析当页面命中时,计算机各个硬件是如何交互的:
第一步:处理器生成一个虚拟地址VA,通过总线发送到内存管理单元。
第二步:内存管理单元通过虚拟页号得到页表项的地址PTEA,通过内存总线从CPU高速缓存/主存读取这个页表项PTE。
第三步:CPU高速缓存或者主存通过内存总线向内存管理单元返回页表项页表。
第四步:内存管理单元先把页表项中的物理页框号PPN复制到寄存器的高三位中,接着把12位的偏移量VPO复制到寄存器的末12位构成15位的物理地址,即可以把该寄存器存储的物理内存地址PA发送到内存总线,访问高速缓存/主存。
第五步:CPU高速缓存/主存返回该物理地址对应的数据给处理器。
在内存管理单元进行地址转换时,如果页表项的有效位是0,则表示该页面并没有映射到真实的物理页框号PPN,则会引发一个缺页中断,CPU陷入操作系统内核,接着操作系统就会通过页面置换算法选择一个页面将其换出,以便为即将调入的新页面腾出位置,如果要换出的页面的页表项里的修改位已经被设置过,也就是被更新过,则这是一个脏页,需要写回磁盘更新该页面在磁盘上的副本,如果该页面是干净的,
则直接用调入的新页面覆盖掉被换出的旧页面即可。
缺页中断的具体流程如下:
第一步到第三步:和前面的页面命中的前3步是一致的。
第四步:检查返回的页表项PTE发现其有效位是0,则内存管理单元触发一次缺页中断异常,然后CPU转入到操作系统内核中的缺页中断处理器。
第五步:缺页中断处理程序检查所需的虚拟地址是否合法,确认合法后系统则检查是否有空闲物理页框号PPN可以映射给该缺失的虚拟页面,如果没有空闲页框,则执行页面置换算法寻找一个现有的虚拟页面淘汰,如果该页面已经被修改过,则写回磁盘,更新该页面在磁盘上的副本。
第六步:缺页中断处理程序从磁盘调入新的页面到内存,更新页表项PTE。
第七步:缺页中断程序返回到原先的进程,重新执行引起缺页中断的指令,CPU将引起缺页中断的虚拟地址重新发送给内存管理单元,此时该虚拟地址已经有了映射的物理页框号PPN,因此会按照前面的流程走一遍,最后主存把请求的数据返回给处理器。
虚拟内存和高速缓存
如果一台计算机同时配备了虚拟内存技术和CPU高速缓存,那么内存管理单元每次都会优先尝试到高速缓存中进行寻址,如果缓存命中则会直接返回,只有当缓存不命中之后才去主存寻址。
通常来说,大多数系统都会选择利用物理内存地址去访问高速缓存,因为高速缓存相比于主存要小很多,所以使用物理寻址也不会太复杂,另外也因为高速缓存容量很小,所以系统需要尽量在多个进程之间共享数据块,而使用物理地址能够使得多进程同时在高速缓存中存储数据块以及共享来自相同虚拟内存页的数据块变得更加直观