内存管理中的覆盖2025年4月29日 | 阅读 6 分钟 在计算机早期,对程序员施加的主要限制之一是计算机内存的大小。如果程序大于可用内存,则无法加载,这严重限制了程序的大小。固定分区的主要问题在于,进程的大小必须受到分区最大尺寸的限制,这意味着一个进程永远无法跨越另一个进程。 显而易见的解决方案是增加可用内存量,但这会显著增加计算机系统的成本。为了解决这个问题,早期人们使用了一种称为覆盖(Overlays)的解决方案。 覆盖的概念是,当一个进程运行时,它不会同时使用完整的程序。它只会使用其中的一部分。然后覆盖的概念是,你需要的任何部分,就加载它,一旦该部分完成,就卸载它,这意味着将其拉回并获取你所需的新部分并运行它。 正式来说,“将一部分程序代码或其他数据传输到内部内存的过程,替换已存储的内容”。 有时,与最大分区的尺寸相比,程序的大小甚至更大。在这种情况下,你应该使用覆盖。 ![]() 因此,覆盖是一种在程序大于物理内存大小时运行该程序的技术,其方法是在任何给定时间只保留所需的指令和数据。将程序划分为模块,以便并非所有模块都需要同时驻留在内存中。在内存管理中,覆盖按以下步骤工作,例如:
覆盖示例覆盖的最佳例子是汇编器。考虑汇编器有两个传递(passes),两个传递意味着在任何时候它只做一件事,要么是第一个传递,要么是第二个传递。这意味着它会先完成第一个传递,然后再完成第二个传递。假设可用主内存大小为 150KB,总代码大小为 200KB。 由于总代码大小为 200KB,主内存大小为 150KB,不可能同时使用两个传递。因此,在这种情况下,我们应该使用覆盖技术。
覆盖驱动程序(Overlays driver): 用户负责处理覆盖。操作系统不提供任何支持。这意味着用户甚至应该编写第一个传递中需要哪些部分的代码,一旦第一个传递完成,用户就应该编写代码来移除第一个传递并加载第二个传递。用户在此过程中承担的责任称为覆盖驱动程序。覆盖驱动程序将帮助我们移出和移入代码的各个部分。 覆盖的使用构建覆盖程序涉及手动将程序划分为称为覆盖(overlays)的自包含的目标代码块,并以树形结构布局。兄弟(Sibling)段,即同一深度级别的段,共享相同的内存,称为覆盖或目标区域(destination regions)。覆盖管理器,无论是操作系统的一部分还是覆盖程序的一部分,在需要时都将所需的覆盖从外部内存加载到其目标区域。链接器通常支持覆盖。 例如,程序的覆盖树如下所示: ![]() 使用覆盖的概念,我们不必将整个程序都放在主内存中。我们只需要在那个时刻需要的部分。我们需要的要么是 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 的程序在这套系统上运行。 一种解决方案是识别程序中相对独立且无需直接相互调用的模块(覆盖)。将覆盖与主程序分开,并将它们的机器代码存放在更大的内存中。将主程序放在指令内存中,但至少留出足够的空间来容纳最大的覆盖。 要调用位于覆盖中的函数,你必须首先将该覆盖的机器代码从大内存复制到指令内存中为其预留的空间,然后跳转到那里的入口点。 ![]() 上图显示了一个具有独立数据和指令地址空间的系统。程序将其代码从较大的地址空间复制到指令地址空间以映射覆盖。由于此处显示的覆盖都使用相同的映射地址,因此一次只能映射一个。 加载到指令内存中并准备使用的覆盖称为映射(mapped)覆盖。它的映射地址(mapped address)是它在指令内存中的地址。不存在(或仅部分存在)于指令内存中的覆盖称为未映射(unmapped)覆盖,它的加载地址(load address)是它在较大内存中的地址。映射地址也称为虚拟内存地址(virtual memory address,VMA),加载地址也称为加载内存地址(load memory address,LMA)。 不幸的是,覆盖并不是一种完全透明的程序适应有限指令内存的方法。它们引入了一套新的全局约束:
覆盖的优点内存管理中的覆盖具有以下优点,例如:
覆盖的缺点覆盖也有一些缺点,例如:
下一主题创业操作系统金字塔计划 |
我们请求您订阅我们的新闻通讯以获取最新更新。