深入理解计算机系统(再谈堆内存)

     上篇设计主要讨论隐式空闲列表,其主要特点简单,易于管理。

显式空闲列表

     隐式空闲列表提供了一些基本的分配器概念的方法。然而,因为块分配与堆块的总数呈线性关系,所以对于通用的分配器,隐式空闲列表是不合适(尽管对于堆块数量预先就知道是很小的特殊的分配器来说它是可以的)。

      一种更好的方法是将空闲块组织为某种形式的显式数据结构,因为根据定义,程序不需要一个空闲块的主体,所以实现这个数据结构的指针可以存放在这些空闲块的主体里面,例如,堆可以组织成一个双向空闲列表,在每个空闲块中,都包含一个pred(前驱)和succ(后继)指针,

     使用双向列表而不是隐式空闲列表(逻辑抽象上的列表),使首次适配的分配时间从块的总数的线性时间减少到了空闲块数量的线性时间,不过,释放一个块的时间可以是线性的,也可能是个常数,这取决于我们所选择的空闲列表中块的排序策略:

     一种方法是用后进先出(LIFO)的顺序维护列表,将新释放的块放置在列表的开始处,。使用LIFO的顺序和首次适配的放置策略,分配器就会最先检测最近使用过的块,在这种情况下,释放一个块可以在常数时间内完成,如果使用是边界标志,那么合并也可以在常数时间内完成。

     另一种方法是按照地址顺序来维护列表,其中列表中每个块的地址都小于他后继的地址,在这种情况下,释放一个块需要线性时间的搜索来定位合适的前缀。平衡点在于,按照地址排序的首次适配比LIFO排序的首次适配有更高的存储器的利用率,接近最佳适配的利用率。

 

小结: 显式列表的缺点是空闲块必须足够大,以包含所有需要的指针,以及头部和可能的尾部(用边界标志来进行常数时间内合并的就得需要),这就导致了更大的最小块的大小(32位4bytes指针),也潜在的提高了内部碎片的程度;

 

分离的空闲列表:

     一个使用单向空闲列表块的分配器需要与空闲块数量呈线性关系的时间来分配块,一种流行的减少分配时间的方法,通常叫做“分离存储”,就是维护多个空闲列表,其中每个列表的块有大致相等的大小,一般的丝路是讲所有可能的块大小分为一些等价类,也叫大小类。

     分配器维护一个空闲列表数组,每个大小类一个空闲列表,按照大小的升序排序,当分配需要一个大小为n的块时,他就搜索相应的空闲列表,如果不能找到合适的块与之匹配,继续搜索下一个列表。。。。。

    

   1:简单分离存储

      为了分配一个给定大小的块,我们检查相应的空闲列表,如果列表非空,简单的分配第一个块的全部,空闲块是不会分割以满足分配请求的,如果列表为空,分配器请求内核额外的存储器片(页大小的整数倍),将这个片分成大小相等的块,形成一个新的空闲列表。要释放一个块,直接插入相应的空闲列表的头部。

     优点:分配和释放都是常数时间。每个块大小相等,不分割,不合并。那么一个块的大小就可以从他的地址中推断出来,因为没有合并,所有已经分配块头部就不需要标志位和大小,因此已分配的块就不需要头部也不需要尾部,因为分配和释放操作都是在空闲列表的起始处开始操作,所以列表只需要是单向的。

     缺点:容易造成内部和外部碎片,因为空闲块是不会被分割的,所以可能会造成内部碎片,更糟的是,因为不会合并空闲块,所以某种访问模式会引起多的外部碎片(请求大空间块)

   2:分离适配:

      使用这种方法,分配器维护一个空闲列表的数组,每一个空闲列表是和一个大小类相关联,并且被组织成某种类型的显示或隐式的列表,每个列表包含潜在的大小不同的快,这些块是大小类中的成源(相当于一个列表维系一个块大小范围);

      请求一个块,并且对适当的空闲列表做首次适配,查找一个合适的块,如果找到一个,那么我们(可选的)分割他,并将剩余的部分插入当适当空闲列表中去,如果找不到,那么就搜索下一个更大的空闲列表。释放时,执行合并,并讲结构放置到相应的空闲列表中;

  3:伙伴系统:

     伙伴是分离适配的一种特别,维系2^m大小的空间,为每个块大小2^k维护一个分离空闲列表, 0 <= k <=m ,请求块大小向上取整最接近的2的幂,没有就向2.2^k请求,递归的二分分割,每个剩下的半块(称为伙伴)被放置在相应的空闲列表中,要释放一个大小为2^k的块,我们继续合并空闲的伙伴,当遇到一个已分配的伙伴是,停止。

     给定地址和块的大小,很容易算出他的伙伴的地址,

    一个块大小为32字节:

    xxxxxx......x00000;

    伙伴地址:

    xxxxxx......x10000

posted @ 2015-05-26 09:07  TomSun*star  阅读(998)  评论(0编辑  收藏  举报