操作系统 内存管理中的覆盖技术
早期计算机时代,程序员面临的主要限制之一是计算机内存的大小。如果程序的大小超过可用内存,那么它就无法加载,严重限制了程序的大小。固定分区的主要问题是进程的大小必须受限于分区的最大尺寸,这意味着一个进程永远不能跨越另一个进程。
显而易见的解决办法是增加可用的内存量,但这将大大增加计算机系统的成本。为了解决这个问题,早期的人们使用了一种称为“覆盖技术”的解决方案。
覆盖技术 的概念是,每当一个进程运行时,它不会同时使用完整的程序。它只会使用其中的一部分。然后,覆盖技术指出,无论你需要哪一部分,你就将其加载,一旦完成,你只需将其卸载,也就是将其拉回来,获取你所需的新部分并运行它。
正式地说,”将程序代码块或其他数据转移到内部内存中,替换已经存储的内容”。
有时候会出现这样的情况,与最大分区的大小相比,程序的大小会更大。那么,在这种情况下,你应该选择使用覆盖技术。
因此,覆盖是一种通过仅保留任何给定时间需要的指令和数据来运行大于物理内存大小的程序的技术。将程序分成模块,以便不需要同时在内存中放置所有模块。在内存管理中,覆盖的工作步骤如下:
- 程序员将程序分成许多逻辑部分。
- 整个程序的一小部分必须始终保留在内存中,但其余部分(或覆盖)只有在需要时才会加载。
- 使用覆盖使程序员能够编写比物理内存更大的程序,尽管内存使用取决于程序员而不是操作系统。
覆盖的示例
覆盖的最佳示例是汇编程序。考虑到汇编程序有两个通过,即任何时候它只能执行一件事,要么是第一遍通过,要么是第二遍。这意味着它将首先完成第一遍通过,然后是第二遍通过。假设可用的主内存大小为150KB,总的代码大小为200KB。
Pass 1.......................70KB
Pass 2.......................80KB
Symbol table............30KB
Common routine......20KB
由于代码总大小为200KB,主内存大小为150KB,无法同时使用两个通道。 因此,在这种情况下,我们应该使用覆盖技术。
- 根据覆盖概念,只使用一个通道,两个通道始终需要符号表和公共例程。
- 如果覆盖驱动程序为10KB,则需要的最小分区大小是多少?
- 对于通道1,总内存需求为=(70KB + 30KB + 20KB + 10KB)= 130KB。
- 对于通道2,总内存需求为=(80KB + 30KB + 20KB + 10KB)= 140KB。
- 因此,如果我们有一个最小的140KB大小的分区,我们可以很轻松地运行这个代码。
覆盖驱动程序: 用户的责任是处理覆盖。 操作系统不会提供任何东西。 这意味着用户甚至必须写出在第一遍遍历中需要哪一部分的代码,一旦第一遍遍历结束,用户应该写出提取第一遍遍历并加载第二遍遍历的代码。 用户在此过程中的责任称为 覆盖驱动程序 。 覆盖驱动程序将帮助我们移出和移入代码的各个部分。
覆盖的使用
构建覆盖程序涉及手动将程序划分为自包含的对象代码块,称为 覆盖 ,这些代码块以树形结构排列。 同级 段,位于相同深度级别的段,共享同一内存,称为 覆盖 或 目标区域 。 覆盖管理器,无论是操作系统的一部分还是覆盖程序的一部分,在需要时将所需的覆盖从外部存储器加载到其目标区域中。 通常,链接器提供对覆盖的支持。
例如,程序的覆盖树如下所示:
通过使用覆盖概念,我们不需要将整个程序放入主存中。我们只需要在该时刻所需的部分。不论是需要Root-A-D还是Root-A-E或者Root-B-F或者Root-C-G的部分。
Root+A+D = 2KB + 4KB + 6KB = 12KB
Root+A+E = 2KB + 4KB + 8KB = 14KB
Root+B+F = 2KB + 6KB + 2KB = 10KB
Root+C+G = 2KB + 8KB + 4KB = 14KB
因此,如果我们有一个14KB大小的分区,我们就可以运行它们中的任意一个。
覆盖工作原理
假设你有一台计算机,其指令地址空间只有64KB长,但内存比其他人都要大得多,比如特殊指令、段寄存器或内存管理硬件等。假设你想要在这个系统上运行一个大于64KB的程序。
其中一个解决方案是识别出你的程序中相对独立且不需要直接调用彼此的模块(覆盖)。将这些覆盖从主程序中分离出来,并将它们的机器代码放在较大的内存中。将主程序放在指令内存中,但要确保至少有足够的空间来容纳最大的覆盖。
要调用位于覆盖中的函数,你必须首先将该覆盖的机器代码从大内存复制到指令内存中为其预留的空间,然后跳转到其中的入口点。
上图显示了一个具有单独的数据和指令地址空间的系统。程序将其代码从较大的地址空间复制到指令地址空间以进行映射叠加。由于这里显示的覆盖都使用同一个映射地址,因此一次只能映射一个覆盖。
加载到指令存储器并准备使用的覆盖被称为 映射 覆盖。其 映射地址 是其在指令存储器中的地址。在指令存储器中不存在(或部分不存在)的覆盖被称为 未映射 ,其 加载地址 是其在较大存储器中的地址。映射地址也称为 虚拟内存地址 (VMA),加载地址也称为 加载内存地址 (LMA)。
不幸的是,覆盖并不是一种完全透明的适应有限指令存储器的方式。它们引入了一组全局约束:
- 在调用或返回覆盖中的函数之前,您的程序必须确保该覆盖实际上已映射。否则,调用或返回将在正确的地址转移控制,但在错误的覆盖中进行,您的程序很可能会崩溃。
- 如果在您的系统上映射覆盖的过程很昂贵,您将需要谨慎选择覆盖,以最小化其对程序性能的影响。
- 加载到系统上的可执行文件必须包含每个覆盖的指令,出现在覆盖的加载地址而不是映射地址。但是,每个覆盖的指令必须被重定位,并且其符号被定义为如果覆盖位于其映射地址上。您可以使用GNU链接器脚本为您的程序的各个部分指定不同的加载和重定位地址。
- 将可执行文件加载到系统的过程必须将其内容加载到较大的地址空间和指令和数据空间中。
覆盖的优势
内存管理中的覆盖具有以下优势,例如:
- 减少内存需求。
- 减少时间需求。
覆盖的缺点
覆盖也有一些缺点,例如:
- 程序员必须指定重叠映射。
- 程序员必须了解内存需求。
- 重叠模块必须完全不重叠。
- 叠加结构的编程设计复杂,并且并非在所有情况下都可行。