内存应容纳操作系统和各种用户进程,因此应该尽可能有效地分配内存。
通常,我们需要将多个进程同时放在内存中。因此我们需要考虑,如何为输入队列中需要调入内存的进程分配内存空间。在采用连续内存分配时,每个进程位于一个连续的内存区域,与包含下一个进程的内存相连。
1. 最为简单的内存分配方法之一,就是将内存分为多个固定大小的分区。每个分区可以只包含一个进程。因此,多道程序的程度受限于分区数。如果使用这种多分区方法,那么当一个分区空闲时,可以从输入队列中选择一个进程,以调入空闲分区。当该进程终止时,它的分区可以用于其他进程。
这种方法最初为 IBMOS/360 操作系统所使用,现在已不再使用。下面所描述的方法是固定分区方案的推广(称为 MVT),它主要用于批处理环境。这里所描述的许多思想也可用于采用纯分段内存管理的分时操作系统。
2. 对于可变分区方案,操作系统有一个表,用于记录哪些内存可用和哪些内存已用。开始,所有内存都可用于用户进程,因此可以作为一大块的可用内存,称为块。最后,正如将会看到的,内存有一个集合,以包含各种大小的块。
随着进程进入系统,它们将被加入输入队列。操作系统根据所有进程的内存需求和现有可用内存的情况,决定哪些进程可分配内存。当进程分配到空间时,它就加载到内存,并开始竞争 CPU。当进程终止时,它将释放内存,该内存可以被操作系统分配给输入队列内的其他进程。
任何时候,都有一个可用块大小的列表和一个输入队列。操作系统根据调度算法来对输入队列进行排序。内存不断地分配给进程,直到下一个进程的内存需求不能满足为止,这时没有足够大的可用块来加载进程。操作系统可以等到有足够大的空间,或者可以往下扫描输入队列,以确定是否有其他内存需求较小的进程可以被满足。
通常,如上所述,可用的内存块为分散在内存里的不同大小的块的集合。当新进程需 要内存时,系统为该进程查找足够大的块。如果块太大,那么就分为两块:一块分配给新进程,另一块还回到块集合。当进程终止时,它将释放内存,该内存将还给块的集合。如果新块与其他块相邻,那么将这些块合并成大块。这时,系统可以检查,是否有进程在等待内存空间,以及新合并的内存空间是否满足等待进程等。
这种方法是通用动态存储分配问题(根据一组空闲块来分配大小为 n 的请求)的一个特例。这个问题有许多解决方法。
从一组可用块中选择一个空闲块的最为常用方法包括:首次适应、最优适应及最差适应:
模拟结果显示,首次适应和最优适应在执行时间和利用空间方面都好于最差适应。首次适应和最优适应在利用空间方面难分伯仲,但是首次适应要更快些。
用于内存分配的首次适应和最优适应算法都有外部碎片的问题。
随着进程加载到内存和从内存退出,空闲内存空间被分为小的片段。当总的可用内存之和可以满足请求但并不连续时,这就出现了外部碎片问题:存储被分成了大量的小块,这个问题可能很严重。
在最坏情况下,每两个进程之间就有空闲(或浪费的)块。如果这些内存是一整块,那么可能可以再运行多个进程。
选择首次适应或者最优适应,可能会影响碎片的数量。(对一些系统来说,首次适应更好;对另一些系统,最优适应更好)。另一因素是从空闲块的哪端开始分配。(哪个是剩余的块,是上面的还是下面的?)不管使用哪种算法,外部碎片始终是个问题。
根据内存空间总的大小和平均进程大小的不同,外部碎片问题或许次要或许重要。例如,采用首次适应方法的统计说明,不管使用什么优化,假定有 N 个可分配块,那么可能有 0.5N 个块为外部碎片。即 1/3 的内存可能不能使用。这一特性称为 50%规则。
内存碎片可以是内部的,也可以是外部的。假设有一个 18 464 字节大小的孔,并采用 多分区分配方案。假设有一个进程需要 18 462 字节。如果只能分配所要求的块,那么还剩下一个 2 字节的块。维护这一小块的开销要比块本身大很多。
因此,通常按固定大小的块为单位(而不是字节)来分配内存。采用这种方案,进程所分配的内存可能比所需的要大。这两个数字之差称为内部碎片,这部分内存在分区内部,但又不能用。
外部碎片问题的一种解决方法是紧缩。它的目的是移动内存内容,以便将所有空闲空间合并成一整块。然而,紧缩并非总是可能的。如果重定位是静态的,并且在汇编时或加载时进行的,那么就不能紧缩。只有重定位是动态的,并且在运行时进行的,才可采用紧缩。
如果地址被动态重定位,可以首先移动程序和数据,然后再根据新基地址的值来改变基地址寄存器。如果能采用紧缩,那么还要评估开销。最简单的合并算法是简单地将所有进程移到内存的一端,而将所有的块移到内存的另一端,从而生成一个大的空闲块。这种方案比较昂贵。
外部碎片化问题的另一个可能的解决方案是,允许进程的逻辑地址空间是不连续的,这样,只要有物理内存可用,就允许为进程分配内存。有两种互补的技术可以实现这个解决方案:分段和分页。这两个技术也可以组合起来。
碎片是一个常见问题,当需要管理数据块时它就可能出现。
程序员通常愿意将内存看作一组不同长度的段,这些段之间并没有一定的顺序(图 1)。
当编写程序时,程序员认为它是由主程序加上一组方法、过程或函数所构成的。它还可以包括各种数据结构,例如对象、数组、堆栈、变量等。每个模块或数据元素通过名称来引用。程序员会说“堆栈”、“数学库”和“主程序”等,而并不关心这些元素所在内存的位置,及他不关心堆栈是放在函数 Sqrt() 之前还是之后。
分段就是支持这种用户视图的内存管理方案。逻辑地址空间是由一组段构成。每个段都有名称和长度。地址指定了段名称和段内偏移。因此用户通过两个量来指定地址:段名称和段偏移。
分段允许进程的物理地址空间是非连续的。分页是提供这种优势的另一种内存管理方案。然而,分页避免了外部碎片和紧缩,而分段不可以。
不仅如此,分页还避免了将不同大小的内存块匹配到交换空间的问题,在分页引入之前采用的内存管理方案都有这个问题。由于比早期方法更加优越,各种形式的分页为大多数操作系统采用,包括大型机的和智能手机的操作系统。实现分页需要操作系统和计算机硬件的协作。
实现分页的基本方法涉及将物理内存分为固定大小的块,称为帧或页帧,而将逻辑内存也分为同样大小的块,称为页或页面。当需要执行一个进程时,它的页从文件系统或备份存储等处,加载到内存的可用帧。备份存储划分为固定大小的块,它与单个内存帧或与多个内存帧(簇)的大小一样。
分页优点之一是可以共享公共代码。对于分时环境,这种考虑特别重要。
分页,分段等,所有这些策略都有相同的目标,就是同时将多个进程保存在内存中,以便允许多道程序。然而,这些策略都倾向于要求每个进程在执行之前应完全处于内存中。
虚拟内存技术允许执行进程不必完全处于内存。这种方案的一个主要优点就是,程序可以大于物理内存。此外,虚拟内存将内存抽象成一个巨大的、统一的存储数组,进而实现了用户看到的逻辑内存与物理内存的分离。这种技术使得程序员不再担忧内存容量的限制。
虚拟内存还允许进程轻松共享文件和实现共享内存。此外,它为创建进程提供了有效的机制。然而,虚拟内存的实现并不容易,并且使用不当还可能会大大降低性能。
内存管理算法的实现有一个基本要求,就是执行的指令应处于物理内存中。满足这一要求的第一种方法是,将整个逻辑地址空间置于物理内存中。动态加载可以帮助缓解这种限制,但它通常需要特殊的预防措施和程序员的额外工作。
指令应处于物理内存以便执行的要求,似乎是必要的和合理的,但它也是有缺点的,因为它将程序的大小限制为物理内存的大小。事实上,通过实际程序的研究会发现,在许多情况下并不需要将整个程序置于内存中(见下例)。
例如,分析以下内容:
100X100
个元素来声明的数组,可能实际很少用到大于 10X10
个的元素。虽然汇编程序的符号表可能有 3000 个符号的空间,但是程序平均可能用到的只有不到 200 个符号。即使在需要整个程序的情况下,也可能并不同时需要整个程序。分段能够执行只有部分处于内存的程序,可以带来许多好处:
虚拟内存将用户逻辑内存与物理内存分开。这在现有物理内存有限的情况下,为程序员提供了巨大的虚拟内存(如下图所示)。
因此,虚拟内存使得编程更加容易,因为程序员不再需要担心有限的物理内存空间,只需要关注所要解决的问题。
除了将逻辑内存与物理内存分开外,虚拟内存允许文件和内存通过共享页而为多个进程所共享。这带来了以下好处:
原文:https://www.cnblogs.com/sunny0824/p/13673999.html