现代需要运行的程序往往大到内存无法容纳,而且必须能够支持多个程序同时运行,及时内存可以满足其中一个单独的程序的要求,但总体来看仍然超出了内存大小。而交换技术由于磁盘速度的限制,也不是一个非常好的方法。
在早起计算时代(20世纪60年代)采用的方法:把程序分割成许多片段,称为覆盖。程序开始时,将覆盖管理模块装入内存,该模块负责在运行时动态地调入覆盖0、1、2……并决定是获取空闲区还是占用已使用的区域放置要调入的覆盖n。一般由操作系统动态地换入换出覆盖,但是程序员必须把程序分割成段。这是一项非常费时且易出错的操作。因此有人找到把全部工作交给计算机去做的办法----虚拟内存。
虚拟内存的基本思想是:每个进程拥有自己的地址空间,这个空间被分割成多个块,每一块被称作一页或页面。每一页有连续的地址范围,这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行进程。当进程需要用到不再物理内存的地址空间时,操作系统负责将缺失的部分装入物理内存。
分页
大部分虚拟内存系统中都使用一种称为分页的技术。形如进程执行指令:MOV REG,1000。这些由进程产生的地址称为虚拟地址,他们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线,读写操作使用具有同样地址的物理内存;在使用虚拟内存的情况下,虚拟地址先被送到MMU(内存管理单元),MMU把虚拟地址映射为物理内存地址。
虚拟地址空间按照固定大小划分成称为页面的若干单元,在物理内存中对应的单元称为页框。现有系统中页大小一般为512字节到64KB。
例如:在一个16位地址的计算机中,物理内存为32KB,程序可以有64KB的虚拟地址空间。若页大小为4KB,则有16个虚拟页面,8个页框。虚拟地址空间有0至15编号的虚拟页面,0虚拟页面对于的虚拟地址为0~4095(4KB)并以此类推。执行指令过程:MOV REG,20500。该指令对虚拟地址20500进行操作,首先20500=20480+20,即虚拟页面为5,从页面5起始的第20个字节处。要知道到该指令操作的实际物理内存,需要首先将虚拟页面映射到页框(位于物理内存),此时有两种情况。(1)若虚拟页面5能够映射到页框,如对应页框为1,则对应的物理内存地址为4096+20=4116字节处;(2)还有一种情况,该虚拟页面没有被映射到内存中。此时CPU将陷入到操作系统,这个陷阱称为缺页中断,操作系统将一个很少用的页框写入磁盘,将需要访问的页面读到该页框中,修改映射关系,然后重新启动引起陷阱的指令。在实际的硬件中,用一个“在/不在”位来表示该虚拟页面在内存中的实际存在情况。在上述的例子中,虚拟地址的前4位表示对应的虚拟页面号,后12位表示从页面起始位置的偏移量,这种方式十分高效,故页面大小通常都为2的整数次幂。
页表:
页表是虚拟地址到物理地址最简单的一种映射方式:虚拟地址被分成虚拟页号(高位部分)和偏移量(低位地址)两部分。每个虚拟页号有一个对应的页表项,然后由页表项找到页框号(若有的话)。然后把页框号拼接到偏移量的高位端替换掉虚拟页号,就形成了对应的物理地址。
页表项的结构:
页框号:用来找到对应的页框,映射到对应的物理地址
‘在/不在’位:1表示该项有对应的页框,0表示该虚拟页面不再内存中
保护位:指出一个页允许的访问类型,一般有3位,分别表示是否能够读、写、执行
修改位:标志该页面是否有过修改,如果一个页面是被修改过(称为“脏”的),则该页面被重新分配页框时必须写回磁盘。若没有被修改过(“干净”的)则简单的丢弃即可。故也被称为“脏”位
访问位:帮助操作系统在发生缺页终端时选择要被淘汰的页面,正在被使用的页面更不容易被淘汰。
高速缓存禁止位:对于一些映射到设备寄存器而不是内存的页面,假如操作系统正在等待某个I/O设备的想要,该位保障硬件是从设备中读取数据而不是访问一个旧的高速缓存的副本。具有独立的I/O空间而不使用内存映像I/O的机器不需要这一位。
操作系统把虚拟页面对于的磁盘地址等信息保存在操作系统内部的软件表格中,故页表不保存不在内存中的页面的磁盘地址信息。
分页系统中主要需要考虑两个问题:
1. 虚拟地址到物理地址的映射必须快速
2. 如果虚拟地址空间很大,页表也会很大
每次访问内存,都需要进行虚拟地址到物理地址的映射。这样,每条指令都将至少进行一两次或更多的页表访问,需要避免映射成为计算机速度的主要瓶颈。
对大而快速的页映射的需求是构建计算机的重要约束。一种简单的设计:使用由一组“快速硬件寄存器”组成的单一页表。当启动一个进程时,把保存在内存中的进程页表的部分载入到寄存器中,这样进程运行过程中就不必再为页表而访问内存。优点是简单且快速,缺点是页表很大时代价很高,而且进程切换时性能降低。
另一种极端方法是整个页表都在内存中,硬件只要有一个指向页表起始位置的寄存器即可。这样进程切换十分迅速,但每次执行指令都要一次或多次访问内存,速度很慢。
分页加速
大多数程序总是对少量的页面进行多次访问。即只有很少的页表项会被反复读取,而其他的页表项很少被访问。基于这种情况,可以为计算机设置一个小型的硬件设备,将虚拟页面直接映射到页框。这种设备称为转换检测缓冲区(TLB),有时也称为相联存储器。通常在MMU中,包含少量表项(一般不超过64个)。
将一个虚拟地址放到MMU中进行转换时,引进首先通过虚拟页号与TLB中所有表项同时进行匹配(并行发生),判断虚拟页面是否在其中,若在则可根据保护位选择是否直接取出页框号;而若虚拟页面号不在其中时,就会进行正常的页表查询(内存中),并从TLB中淘汰一个表项,用找到的新表项替代它。这样若很快再次访问TLB时就可以命中。有两种不同的TLB失效:(1)是页面访问在内存中而不再TLB中,称为软失效。此时只需更新TLB即可,只需花费几纳秒时间;(2)页面本身不在内存中,称为硬失效,此时需要从磁盘中装入该页面,这个过程需要几毫秒,是软失效的百万倍。
TLB也可以用软件来管理,此时TLB表项被操作系统显式地装载。发生TLB访问失效时,不再是MMU到页表中查找并取出需要的表项,而是生成一个TLB失效并将问题交给操作系统来解决。当TLB大到(如64个表项)可以较少失效率时,软件管理会变得足够有效。这样可以使得MMU十分简单,可以用节省的资源来改善CPU上的高速缓存或其他性能。
大内存的页表
有两种常用的解决办法:
(1) 多级页表
引入多级页表可以避免把全部页表一直保持在内存中。如一个32位的虚拟地址,页面长度为4KB,则有个页面,即页表有个表项。但是可能该程序实际只需要12MB的内存。这样,可以用虚拟地址的前10位组成由1024个表项的*页表,每个表项都表示一个4MB的虚拟地址空间,并引导找到二级页表。由于只需要用到12MB的空间,即3个4MB的地址空间,*页表只有三个表项是实际被使用的。用该虚拟地址的11位至20位这10位来引导二级页表,则二级页表的每个表项对于一个4KB大小的地址空间,并映射到实际的物理内存地址。这样用4个1024个表项的页表就可以寻址32位的虚拟地址。
二级页表可以扩充为三级、四级页表,但级别越多复杂性就越大,一般不会超过三级。
(2) 倒排页表
多级页表可以解决32位虚拟地址空间的寻址问题,但是对于64位虚拟地址就不再适用。倒排页表使一种解决方案。在该方案中,实际内存中的每一个页框有一个表项,而不是每个虚拟页面有一个表项,表项会记录哪一个进程的虚拟页面定位于该页框。
这种方案使得虚拟地址到物理地址的转换变得困难,当进程n访问虚拟页面p时,需要搜索整个倒排页表来查找表项(n,p),这回严重影响运行效率。使用TLB可以解决该问题,用TLB来记录所有频繁使用的页面,地址转换就能够变快。但是当TLB失效时,依然需要搜索整个倒排也表。另一个可行的方案是建立一张散列表,用虚拟地址来散列,所有内存中具有相同散列值得虚拟页面被链接在一次。
倒排页表在64位机器中很常见。