虚拟内存
尽管基址寄存器和界限寄存器可以用于创建地址空间的抽象,还有另一个问题需要解决:管理软件的膨胀(bloatware)。虽然存储器容量增长快速,但是软件大小的增长更快。需要运行的程序往往大到内存无法容纳,而且必然需要系统能够支持多个程序同时运行,即使内存可以满足其中单独一个程序的需要,但总体来看,它们仍然超出了内存大小。交换技术(swapping)并不是一个有吸引力的解决方案(换入换出耗时),因为一个典型的SATA磁盘的峰值传输率最高达到100MB/s,这意味着至少需要10秒才能换出一个1GB的程序,并需要另一个10秒才能再将一个1GB的程序换入。
程序大于内存的问题早在计算时代开始就产生了。在20世纪60年代所采取的解决方法是:把程序分割成许多片段,称为覆盖(overlay)。程序开始执行时,将覆盖管理模块装入内存,该管理模块立即装入并运行覆盖0。执行完成后,覆盖0通知管理模块装入覆盖1,或者占用覆盖0的上方位置(如果有空间),或者占用覆盖0(如果没有空间)。一些覆盖系统非常复杂,允许多个覆盖块同时在内存中。覆盖块存放在磁盘上,在需要时由操作系统动态地换入换出。
虽然由系统完成实际的覆盖块换入换出操作,但是程序员必须把程序分割成多个片段。把一个大程序分割成小的、模块化的片段是非常费时和枯燥的,并且易于出错。很少程序员擅长使用覆盖技术。因此,没过多久就有人找到一个办法,把全部工作都交给计算机去做。采用的这个方法称为虚拟内存(virtual memory)。虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分割成多个块,每一块称作一页或页面(page)。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻执行必要的映射。当程序引用到一部分不在物理内存中的地址空间(缺页中断)时,由操作系统负责将缺失的部分装入物理内存(页面置换)并重新执行失败的指令。
从某个角度来讲,虚拟内存是对基址寄存器和界限寄存器的一种综合。8088为正文和数据分离出专门的基址寄存器(但不包括界限寄存器)。而虚拟内存使得整个地址空间可以用相对较小的单元映射到物理内存,而不是为正文段和数据段分别进行重定位。
虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用。
分页
大部分虚拟内存系统中都使用分页技术。在任何一台计算机上,程序引用了一组内存地址。当程序执行指令
- MOV REG,1000
时,它把地址为1000的内存单元的内容复制到REG中(或者相反,这取决于计算机的型号)。地址可以通过索引、基址寄存器、段寄存器或其他方式产生。
由程序产生的这些地址称为虚拟地址,它们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字;而在使用虚拟内存的情况下,虚拟地址不是被直接送到内存总线上,而是被送到内存管理单元(Memory Management Unit,MMU),MMU把虚拟地址映射为物理内存地址,如图3-8所示。
图3-9中一个简单的例子说明了这种映射是如何工作的。在这个例子中,有一台可以产生16位地址的计算机,地址范围从0到64K,且这些地址是虚拟地址。然而,这台计算机只有32KB的物理内存,因此,虽然可以编写64KB的程序,但它们却不能被完全调入内存运行。在磁盘上必须有一个可以大到64KB的程序核心映像的完整副本,以保证程序片段在需要时能被调入内存。
虚拟地址空间按照固定大小划分成称为页面(page)的若干单元。在物理内存中对应的