虚拟内存是非常重要的操作系统概念。但在学习虚拟内存之前,我们需要学习一些内存的基本概念。重定向就是其中的一个。
我们先来看一个例子:
0 main: mov ax,0 ... 40 int 0x80
这段汇编代码在编译成可执行文件后,被加载到内存中。从物理内存0位置开始执行,到物理位置40的时候结束。逻辑非常的简单清晰。但是,存在一个非常严重的问题:当另外一个程序如果也使用到这块内存的时候会发生覆盖。事实上,我们也不可能使用0这个位置的内存地址。因为从0开始的内存是被操作系统占用的。假设我们从1000这个位置开始可以给用户程序使用。那么上面这段程序的起始位置就应该是1000,结束位置是1040,如下图。但程序员写程序的时候不会直接写绝对地址,也就是说main的位置还是为0,这时候就需要给这个程序在原来的地址上偏移1000。我们把这种方式叫重定位。
1000 main: mov ax,0 ... 1040 int 0x80
第一个要考虑的问题是我们什么时候重定位?
第一种就是在编译的时候就把内存位置定好,把需要偏移的位置加上。这是可行的,但存在非常严重的问题,就是你在编译的时候那块内存是可用的,但可能过了一会,那块内存已经不可用了。这时候还是会出现覆盖的问题。这就有一个非常严格的要求,就是这个系统非常的“硬”,所有的应用程序都是事先固定好位置的。比如一些嵌入式系统,程序被烧如后就不会改变了。这样的好处是程序运行速度非常快,因为位置的偏移是需要做计算的。而这些计算在编译的时候已经做好了。
第二种方式就是在载入内存的时候就行重定位。第一种方式局限性太大了,那么在程序载入内存的时候再进行位置的计算是不是就可以了呢?在载入的时候再算位置就不会有后面的程序来和我抢位置了。第一种方式会抢位置因为在计算的时候大家都觉得那段位置是空闲的。然而这种方式也是有问题的,原因在于程序在内存中的位置并不是一直不变的,而是会被换进换出,叫做交换技术。
假设内存中有进程1,2,3,这时候有一个进程4要进内存,但内存已经没有足够的空间给进程4了。这时候只能够把一些优先级低的进程(假设是进程1和进程2)写回磁盘虚拟内存中,成功执行后变成下图的样子。
当进程4执行完或者内存有足够的空间,并且进程1和进程2被CPU唤醒的时候,就有机会将进程1和进程2又重新写入内存。如下图:
注意此时进程1和进程2的位置已经发生了变化,和原来的位置不一样了。进程2变成在进程1的前面。当然也有可能在进程1和进程2直接还有别的进程插入。为了简单起见,假设就是上述的理想情况。进程1在进行换入换出前的起始地址是1000,换入换出后起始地址可能变成了2000。这就会出问题。在载入内存的地址已经加上1000的偏移量了。当起始地址变成2000后就出现了问题。地址就不对了,自然程序也就会出错。如果不使用交换技术的话,这种方式也是可行的。
由于会使用到交换技术,那么在加载入内容的时候重定位也就会出现问题。那么在运行的时候计算地址就能很好的解决问题。哪怕是进程换入换出了,在进程再次被写入内存也会重新计算内存地址。那么操作系统是怎么让后面的程序不会覆盖前面的进程数据呢?这就要用到进程的一个数据结构PCB(进程控制表)。每个进程都有自己的PCB,PCB包含进程的一些主要信息,包括进程的阻塞状态,大小,位置等。通过PCB就能够算出进程和进程之间的相对关系。