文章目录
- 1. 目标
- 2. 覆盖的基本原理
- 3. 覆盖技术的不足
1. 目标
覆盖技术产生于上世纪80年代和90年代初的时候,在那时候操作系统能力是很弱的,所以说当初目标是要在能够比较小的可用内存中运行比较大的程序,这个比较小,比较大的相对而言,在当时的代表就是 doc 系统,在这个操作系统下,硬件一般只有 640K 的内存。软件一般也就几十K, 大一点的上兆,这种容量下怎么能够使软件可以充分利用内存空间,可以虚拟出一个更大的空间,让软件能够跑,这实际上是靠覆盖技术,那它的基本原理什么呢?
2. 覆盖的基本原理
首先要把程序根据它的执行逻辑进行分类,划分出若干个区间,相对于独立的程序模块,在不同区间这些模块可以不用同时运行,那么就可以把这些不能同时执行的模块设置成共享同一块内存空间,按照时间顺序,前一段时间可能是某一个模块或者某一个分区里面的那些函数去执行,在后一段时间是另一些函数执行,但这些函数是共享一个内存空间,简单理解为分时的方式来共享同一块内存空间,在这个实验过程中需要注意几点:
- 首先一定会有常驻内存的一块代码和数据,它是要负责管理,它来负责决定在某个时间段要把这个相应的函数数据导入内存或者导出内存。
- 有一部分不常用的功能的函数数据在其他程序正在实现的时候,是要把它放到外存中去,只是在需要的时候装入内存。
- 如果是不存在相互间调用关系的函数或者模块,它不必同时装入到内存中去,这样就可以实现相互的覆盖,那么这些模块可以共享共用一个分区,这个分区实际上是一块内存空间。
OK,这是它基本的原理。看个例子,用例子来理解这个处理的过程,看下图:
这里面有一个程序,它有 A、B、C、D、E、F 五个模块,简单理解为五个函数,每个函数占了一定的空间,比如说 A 占了20 K, B 50 K, C 30K,还有D、E、F。上图可以看到每个调用关系, A 调了 B 和 C,B 调了 D,C 调了 E、F,这是它的逻辑调用关系.
基于逻辑调用关系就给它进行分类,这个分类实际上是可以看出来,需要把不具有调用关系的函数或者模块分成一个分区,在这里面可以看到 A 是单独分区,常驻内存,因为它要调用 B C D E F ,那么把 A 作为常驻在内存中的一块区域,只占20K。 B 和 C 这两个函数是对等的,它都被 A 所调用,但是 B 和 C 之间相互不会调用,所以说可以把它分在一个区域,同理 D E F 也可以分在一个区,那这是有分区了。
上图右边代表是当前物理内存的大小,它只有110K,那前面的 A B C D E F加起来一共有190K,如果说想把这个程序全装到内存中去,那其实物理内存是不够的,那能不能用覆盖技术来解决这个问题? 是不是能够把一些相互之间没有调用关系的一些模块函数放在一个分区
因为它们访问时间是不一样的,比如说 A 先调 B,再调 C:
- 那这时候它既然先调 B,就可以把 B 这 50K 空间导到内存里面来,从硬盘放到内存中去,那这时候它占了50K。
- 当 A 调完 B 之后,接着会执行调 C 的函数,但它调 C 之前,需要把 C 模块从硬盘调到内存中去,当然在调之前,首先要把 B 所用到的数据和代码导回到硬盘中去,需要注意一点,代码是只读的,它不会改变,所以代码导入过程是不需要处理的,只需要把这个空间直接释放就 OK 了。
- C 在这里面只占了30K,所以说把它们共享 50K 空间中只用 30K 来放 C,当 A 把 C 代码和数据调到内存来之后,A 就可以调用 C 了。这时候 A 不需要调用 B,所以说 B 没必要保存在内存中,可以体现出来 B 和 C 它们是没有调用关系。它们可以放在一个分区里,分区的大小是50K,也就是 B 和 C 中最大的容量,只要够就 OK。
- 当 C 执行完之后,C 会调用 E,C调 E 的时候,需要注意,D 函数和 F 函数其实是不会被执行的,因为在串行的过程中,C 在调 E 的时候,这两个函数不会执行,也意味着 C 调 E 的时候, D 和 F 其实是没必要放在内存中去,只需把 E 这20K 放到第二个 40 K 覆盖区里面去,这里面虽然 D E F 都有可能占用这40K 的内存空间,但是它们占用空间的时间是不一样,就是在 C 调 E 的时候,D F 是不可能会被执行的。
- 所以说当 E 执行完 20K 内存空间后,C 紧接着会调 F,在这时候它首先会把 E 占用空间给释放掉,把 F 从硬盘中导到这个覆盖区去,使得 F 可以正常执行。
当然在这里面可以看到它这个力度是以程序的逻辑调用关系来实现的,当然这种逻辑调用关系也意味着可以找出多种不同的这种覆盖分区的方式,上图其实只给出了一种,还有另外一种覆盖方式,这种方式需要内存空间可能更小,首先还是 A 是常驻内存的分区,它只占20K, D E F 相互间不会调用,那么它们可以共享50K 的覆盖区,那同时 D 和 C 也没有调用关系,共享一个覆盖区,但是 D 和 C 共享覆盖区,只需要30K 的空间,比刚才那种方式还要省出10K 的空间,也意味着在这种覆盖方法下,整个内存空间只需要100K,比刚才110 K 还要小。所以说在这里面可以通过程序员精心的安排,根据它代码的执行情况来选出一个最佳的覆盖方式,可以实现有效的覆盖管理,从而让大的程序可以利用很小空间就可以完成执行。
3. 覆盖技术的不足
虽然看到它好处就是说一个大程序可以放在一个很小内存里面,但是它还有一些潜在的问题,就由这个覆盖技术本身引入的一些问题。最大问题就是开销问题,开销问题分两类:
- 程序员需要考虑设计的开销,需要考虑怎么把一个大的程序根据它的逻辑执行关系来划分出一个一个小的相之间可以覆盖的功能模块,确定覆盖关系,其实还是挺费时费力的。
- 要不停地在新的不同阶段,要做出换入换出这种操作,操作本身会涉及到对硬盘的读写,加载到内存中去,或者写回硬盘,这个过程其实也有很大开销。
所以说它增加了程序员的负担,引入了很多时间开销,效率上来说会受一点影响,相对来说是它不太理想的地方。当然在早期操作系统没有很强大的管理之下,有这个技术已经很不错了,它可以使一些大型软件得以运行。