操作系统内存最全解析!!!( 五 )


使用链表进行管理另一种记录内存使用情况的方法是,维护一个记录已分配内存段和空闲内存段的链表,段会包含进程或者是两个进程的空闲区域 。可用上面的图 c 来表示内存的使用情况 。链表中的每一项都可以代表一个 空闲区(H) 或者是进程(P)的起始标志,长度和下一个链表项的位置 。
在这个例子中,段链表(segment list)是按照地址排序的 。这种方式的优点是,当进程终止或被交换时,更新列表很简单 。一个终止进程通常有两个邻居(除了内存的顶部和底部外) 。相邻的可能是进程也可能是空闲区,它们有四种组合方式 。

操作系统内存最全解析!!!

文章插图
 
当按照地址顺序在链表中存放进程和空闲区时,有几种算法可以为创建的进程(或者从磁盘中换入的进程)分配内存 。我们先假设内存管理器知道应该分配多少内存,最简单的算法是使用 首次适配(first fit) 。内存管理器会沿着段列表进行扫描,直到找个一个足够大的空闲区为止 。除非空闲区大小和要分配的空间大小一样,否则将空闲区分为两部分,一部分供进程使用;一部分生成新的空闲区 。首次适配算法是一种速度很快的算法,因为它会尽可能的搜索链表 。
首次适配的一个小的变体是 下次适配(next fit) 。它和首次匹配的工作方式相同,只有一个不同之处那就是下次适配在每次找到合适的空闲区时就会记录当时的位置,以便下次寻找空闲区时从上次结束的地方开始搜索,而不是像首次匹配算法那样每次都会从头开始搜索 。Bays(1997) 证明了下次算法的性能略低于首次匹配算法 。
另外一个著名的并且广泛使用的算法是 最佳适配(best fit) 。最佳适配会从头到尾寻找整个链表,找出能够容纳进程的最小空闲区 。最佳适配算法会试图找出最接近实际需要的空闲区,以最好的匹配请求和可用空闲区,而不是先一次拆分一个以后可能会用到的大的空闲区 。比如现在我们需要一个大小为 2 的块,那么首次匹配算法会把这个块分配在位置 5 的空闲区,而最佳适配算法会把该块分配在位置为 18 的空闲区,如下
操作系统内存最全解析!!!

文章插图
 
那么最佳适配算法的性能如何呢?最佳适配会遍历整个链表,所以最佳适配算法的性能要比首次匹配算法差 。但是令人想不到的是,最佳适配算法要比首次匹配和下次匹配算法浪费更多的内存,因为它会产生大量无用的小缓冲区,首次匹配算法生成的空闲区会更大一些 。
最佳适配的空闲区会分裂出很多非常小的缓冲区,为了避免这一问题,可以考虑使用 最差适配(worst fit) 算法 。即总是分配最大的内存区域(所以你现在明白为什么最佳适配算法会分裂出很多小缓冲区了吧),使新分配的空闲区比较大从而可以继续使用 。仿真程序表明最差适配算法也不是一个好主意 。
如果为进程和空闲区维护各自独立的链表,那么这四个算法的速度都能得到提高 。这样,这四种算法的目标都是为了检查空闲区而不是进程 。但这种分配速度的提高的一个不可避免的代价是增加复杂度和减慢内存释放速度,因为必须将一个回收的段从进程链表中删除并插入空闲链表区 。
如果进程和空闲区使用不同的链表,那么可以按照大小对空闲区链表排序,以便提高最佳适配算法的速度 。在使用最佳适配算法搜索由小到大排列的空闲区链表时,只要找到一个合适的空闲区,则这个空闲区就是能容纳这个作业的最小空闲区,因此是最佳匹配 。因为空闲区链表以单链表形式组织,所以不需要进一步搜索 。空闲区链表按大小排序时,首次适配算法与最佳适配算法一样快,而下次适配算法在这里毫无意义 。
另一种分配算法是 快速适配(quick fit) 算法,它为那些常用大小的空闲区维护单独的链表 。例如,有一个 n 项的表,该表的第一项是指向大小为 4 KB 的空闲区链表表头指针,第二项是指向大小为 8 KB 的空闲区链表表头指针,第三项是指向大小为 12 KB 的空闲区链表表头指针,以此类推 。比如 21 KB 这样的空闲区既可以放在 20 KB 的链表中,也可以放在一个专门存放大小比较特别的空闲区链表中 。
快速匹配算法寻找一个指定代销的空闲区也是十分快速的,但它和所有将空闲区按大小排序的方案一样,都有一个共同的缺点,即在一个进程终止或被换出时,寻找它的相邻块并查看是否可以合并的过程都是非常耗时的 。如果不进行合并,内存将会很快分裂出大量进程无法利用的小空闲区 。


推荐阅读