前言:
在谈虚拟内存之前,我们先追本溯源,看看在虚拟内存出现之前,存储器抽象是什么样的。并且它是怎样一步一步进行演进的。
无存储器抽象
没错,是无存储器抽象,在早期的计算机中,没有存储器抽象这个概念。每一条指令都是直接在物理内存(从0到某个上限地址的集合)上进行操作。
带来的问题:
1: 你运行了两个程序,如果两个程序在同一个地址空间都会写入数据,那么这个数据是第一个程序的还是第二个程序?
结果:这就会导致先写数据的程序的数据丢失。
出错点:不同的程序相互访问、改写了不属于自己的数据。
改进:
针对这个问题,操作系统引入了保护建。
对于内存块:每一个内存单元都会被分配一个4位的保护键,保护键存储在CPU的特殊寄存器中。
对于进程:每一个进程有一个程序状态字(PSW),PSW中保存一个4位的码。
操作系统给一个进程和为这个进程所分配的内存单元分配一个标识,这个标识被称为保护键,不同的进程保护键自然也不同。在访问内存的时候,首先查看进程的PSW码和这个内存块的保护键是否相同。如果相同就可以访问,如果不同,就禁止该进程访问。这样就避免了不同程序之间的相互访问。
解决了这个问题,我们就可以让多个程序同时执行,因为已经避免了相互访问了,现在只要将其加载在不同的地址空间就行。
如果没有跳转指令,只是一行一行读取数据然后取执行,这样已经可以了。但是如果有跳转指令,问题就来了。
比如,我们现在运行了两个程序,A和B。
当他俩单独的运行的时候,一切ok。现在想要这两个程序同时执行。
加载地址如图:
当这两个程序同时运行时,A程序先跑,正常执行。B程序运行,执行到JMP 28 这条指令,本来的意思就是以当前程序入口为起点,偏移28个字节的位置。然而操作系统却是以物理内存为起点,偏移28个字节的地方,然后就导致程序崩溃。
出错点:这两个程序都引用了绝对物理地址。
改进:
使用静态重定位。
还是上面的程序,改进后的程序如图:
经过重定位后,多个进程可以同时进行了。
到此,看似问题已经很好的解决了。但是又有问题随之而来------效率
这个机制会减慢装载的速度。而且,它要求给所有的可执行程序提供额外的信息来区分那些内存单元中存有可重定位的地址,那些没有。例如上面程序的JMP需要重定位,而ADD则不需要重定位。
地址空间
因为上面的种种问题,所以地址空间这个概念被提了出来。
先来看看上面遗留的问题 ---- 效率。也就是重定位的时候,上面太耗时了。
而地址空间的概念就是让每个进程都有自己的一个地址空间,这个空间是自己私有的。
为了实现这个想法引入了基址寄存器和界限寄存器。即给每个CPU配置这两个寄存器
基址寄存器与界限寄存器
基址寄存器:程序的起始物理地址
界限寄存器:程序的长度
每一次一个进程访问内存,取出一条指令,读或写一个数据,CPU硬件会在把地址发送到内存总线前,自动把基址寄存器的值加到进程发出的地址上(加寄存器的值其实就是把地址空间中的逻辑地址转化为物理空间中的物理地址)。在加之前,会用进程发出的地址和界限寄存器中的值做比较,避免越界。
使用这两个寄存器就能提高效率吗?
可以的。使用这个方法确实可以提高效率。但是也有一定的缺点。
缺点
每次访问内存都需要进行加法和比较运算。比较运算很快。但是加法由于仅为传递时间的问题,在没有特殊电路的情况下会很慢。