玩命加载中 . . .

空闲内存管理


在动态分配内存时,操作系统必须对其进行管理。有两种方式跟踪内存使用情况:位图和空闲链表。

  1. 使用位图的存储管理

使用位图方法时,内存可能被划分成小到几个字或大到几千字节的分配单元。每个分配单元对应于位图中的一位,0表示空闲,1表示占用(或者相反)。一块内存区和其对应的位图如下图所示。

image-20220517221848784

分配单元的大小是一个重要的设计因素。分配单元越小,位图越大。然而即使只有4个字节大小的分配单元,32位(4字节)的内存也只需要位图中的1位;32n位的内存需要n位的位图,所以位图只占用了1/32的内存。若选择比较大的分配单元,则位图更小。但若进程的大小不是分配单元的整数倍,那么在最后一个分配单元中就会有一定数量的内存被浪费了。

因为内存的大小和分配单元的大小决定了位图的大小,所以它提供了一种简单的利用一块固定大小的内存区就能对内存使用情况进行记录的方法。这种方法的主要问题是,在决定把一个占k个分配单元的进程调入内存时,存储管理器必须搜索位图,在位图中找出有k个连续0的串。查找位图中指定长度的连续0串是耗时的操作。(因为在位图中该串可能跨越字的边界),这是位图的缺点

  1. 使用链表的存储管理

维护一个记录已分配内存段和空闲内存段的链表。其中链表中的一个结点或者包含一个进程,或者是两个进程间的一个空的空闲区。可用上图c所示的段链表来表示上图a所示的内存布局。链表中的每一个结点都包含以下域:空闲区(H)或进程(P)的指示标志、起始地址、长度和指向下一结点的指针。

在本例中,段链表是按照地址排序的,其好处是当进程终止或被换出时链表的更新非常直接。一个要终止的进程一般有两个邻居(除非它是在内存的最底端或最顶端),它们可能是进程也可能是空闲区,这就导致了图3-7所示的四种组合。在上图a中更新链表需要把P替换为H;在上图b和上图c中两个结点被合并成为一个,链表少了一个结点;在上图d中三个结点被合并为一个,从链表中删除了两个结点。

A
A
x
x
B
B
A
A
B
B
删除后
删除后
A
A
x
x
A
A
删除后
删除后
x
x
B
B
B
B
删除后
删除后
x
x
删除后
删除后
Text is not SVG - cannot display

因为进程表中表示终止进程的结点中通常含有指向对应于其段链表结点的指针,因此段链表使用双链表可能要比上图c所示的单链表更方便。这样的结构更易于找到上一个结点,并检查是否可以合并

当按照地址顺序在链表中存放进程和空闲区时,有几种算法可以用来为创建的进程(或从磁盘换入的已存在的进程)分配内存。这里,假设存储管理器知道要为进程分配的多大的内存。

最简单的算法是首次适配(first fit)算法。存储管理器沿着段链表进行搜索,直到找到一个足够大的空闲区,除非空闲区大小和要分配的空间大小正好一样,否则将该空闲区分为两部分,一部分供进程使用,另一部分形成新的空闲区。首次适配算法是一种速度很快的算法,因为它尽可能少地搜索链表结点。

对首次适配算法进行很小的修改就可以得到下次适配(next fit)算法。它的工作方式和首次适配算法相同,不同点是每次找到合适的空闲区时都记录当时的位置。以便在下次寻找空闲区时从上次结束的地方开始搜索,而不是像首次适配算法那样每次都从头开始。Bays(1977)的仿真程序证明下次适配算法的性能略低于首次适配算法。

最佳适配(best fit)算法。搜索整个链表(从开始到结束),找出能够容纳进程的最小的空闲区。试图找出最接近实际需要的空闲区,以最好地区配请求和可用空闲区,而不是先拆分一个以后可能会用到的大的空闲区。

以上图为例来考察首次适配算法和最佳适配算法。假如需要一个大小为2的块,首次适配算法将分配在位置5的空闲区,而最佳适配算法将分配在位置18的空闲区。

因为每次调用最佳适配算法时都要搜索整个链表,所以它要比首次适配算法慢。它比首次适配算法或下次适配算法浪费更多的内存,因为它会产生大量无用的小空闲区。一般情况下,首次适配算法生成的空闲区更大一些。

最佳适配的空闲区会分裂出很多非常小的空闲区,为了避免这一问题,可以考虑最差适配(worst fit)算法,即总是分配最大的可用空闲区,使新的空闲区比较大从而可以继续使用。仿真程序表明最差适配算法也不是一个好主意。

如果为进程和空闲区维护各自独立的链表,那么这四个算法的速度都能得到提高。这样就能集中精力只检查空闲区而不是进程。但这种分配速度的提高的一个不可避免的代价就是增加复杂度和内存释放速度变慢,因为必须将一个回收的段从进程链表中删除并插入空闲区链表。

如果进程和空闲区使用不同的链表,则可以按照大小对空闲区链表排序,以便提高最佳适配算法的速度。在使用最佳适配算法搜索由小到大排列的空闲区链表时,只要找到一个合适的空闲区,则这个空闲区就是能容纳这个作业的最小的空闲区,因此是最佳适配。因为空闲区链表以单链表形式组织,所以不需要进一步搜索。空闲区链表按大小排序时,首次适配算法与最佳适配算法一样快,而下次适配算法在这里则毫无意义。

在与进程段分离的单独链表中保存空闲区时,可以做一个小小的优化。不必像上图c那样用单独的数据结构存放空闲区链表,而可以利用空闲区存储这些信息。每个空闲区的第一个字可以是空闲区大小,第二个字指向下一个空闲区。于是就不再需要上图c中所示的那些三个字加一位(P/H)的链表结点了。

另一种分配算法称为快速适配(quick fit)算法,它为**那些常用大小的空闲区维护单独的链表。**例如,有一个n项的表,该表的第一项是指向大小为4KB的空闲区链表表头的指针,第二项是指向大小为8KB的空闲区链表表头的指针,第三项是指向大小为12KB的空闲区链表表头的指针,以此类推。像21KB这样的空闲区既可以放在20KB的链表中,也可以放在一个专门存放大小比较特别的空闲区的链表中。

快速适配算法寻找一个指定大小的空闲区是十分快速的,但它和所有将空闲区按大小排序的方案一样都有一个共同的缺点,即在一个进程终止或被换出时,寻找它的相邻块,查看是否可以合并的过程是非常费时的。如果不进行合并,内存将会很快分裂出大量的进程无法利用的小空闲区。

本文作者: 董续胜

本文链接: https://dxsm.github.io/p/kong-xian-nei-cun-guan-li.html

版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0许可协议。转载请注明出处!


 评论