STL空间配置器(三)

       第二级配置器 : --->_default_alloc_template:

       第二级配置器多了一些机制,避免太多小额区块造成内存的碎片,小额区块带来的不仅仅是内存碎片,配置时额外的负担也是一个大问题。     

如下图:

第二级配置器的做法是如果区块够大,超过128bytes时,就移交第一级配置器处理。

当区块小于128bytes时,则以内存池(memory pool)管理,这种方法成为次层配置。

每次配置一大块内存,并维护对应的自由链表(free_list),下次若再有相同大小的内存需求,就直接从free_lists中拨出。如果客户端释还小额区块,则有配置器回收到free_lists中。

 

为了方便管理,SGI第二级配置器会主动将任何小额区块上调至8的倍数(例如客户端要求30bytes,那么就自动调整为32bytes),并维护16free_lists,各自管理大小分别为81624324048566472808896104112120128bytes的小额区块。

 

Free_lists的节点结构如下:

union obj
{
    union obj * free_list_link;
    char client_data[1];
}

上述obj所用的union,由于union之故,从其第一阻断观之,obj可被视为一个指针,指向相同形式的另一个obbj,从其第二字段观之,obj可被视为一个指针,指向实际区块。(达到一物两用,不会为了维护链表所必须的指针而造成内存的另一种浪费,不会造成额外负担)

如下图:



free_lists的结构如下:



第二级配置器的部分实现内容:

enum {_ALIGN = 8};  	//小型区块的上调边界
enum {_MAX_BYTES = 128}; //小型区块的上限
enum {_NFREELISTS = _MAX_BYTES/_ALIGN};  //free_lists个数
 
//以下是第二级配置器
//注意,无”template型别参数”,且第二参数完全没派上用场
//第一参数用于多线程环境下,在这里不讨论多线程环境
template <bool threads, int inst>
class __default_alloc_template
{
private:
    //ROUND_UP()将bytes提升至8的倍数
    static size_t ROUND_UP(size_t bytes)
    {
        return ((bytes + __ALIGN - 1) & ~(__ALIGN - 1));
    }
private:
    union obj       //free_lists节点构造
    {
        union obj * free_list_link;
        char client_data[1];
    };
private:
    //16个free_lists
    static obj * volatile free_list[__NFREELISTS];
 
    //根据区块大小,决定使用第n号free-list, n从1算
    static size_t FREELIST_INDEX(size_t bytes)
    {
        return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
    }
    //返回一个大小为n的对象,并可能加入大小为n的其他区块到free list
    static void *refill(size_t n);
 
    //配置一大块空间,可容纳nobjs个大小为"size"的区块
    //如果配置nobjs个区块有所不便,nobjs可能会降低
    static char* chunk_alloc(size_t size, int& nobjs);
 
    // chunk allocation state
    static char *start_free;    //内存池起始位置,只在chunk_alloc()中变化
    static char *end_free;      //内存池结束位置,只在chunk_alloc()中变化
    static size_t heap_size;    
 
public:
    static void * allocate(size_t n);
    static void deallocate(void *p, size_t n);
    static void * reallocate(void *p, size_t old_sz, size_t new_sz);
};
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;
 
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;
 
template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
 
template <bool threads, int inst>
typename __default_alloc_template<threads, inst>::obj * volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

 

(1)空间配置函数---->allocate()

身为一个配置器,_default_alloc_template拥有配置器的标准接口函数allocate(),此函数首先判断区块大小,大于128bytes就调用第一级配置器,小于128bytes就检查对应的free list。如果free list之内有可用的区块,就直接拿来用,如果没有可用的区块,就将区块大小上调至8倍数边界,然后调用refill(),准备为free list重新填充空间。

代码如下:

static void * allocate(size_t n)
{
	obj * volatile *my_free_list;
        obj * result;
	if(n > (size_t) _MAX_BYTES)
	{
		return (malloc_alloc::allocate(n))
	}
	//寻找16个free lists中适当的一个
        my_free_list = free_list + FREELIST_INDEX(n);
        result = *my_free_list;
	if(result == 0)
	{
		//没找到合适的free list,准备重新填充free list
		void *r=refill(ROUND_UP(n));
		//refill为free list重新填充空间,新的空间取自内存池(由chunk_alloc()完成)
 
		return r;
	}
        //调整free list
        *my_free_list = result->free_list_link;
        return (result);
}

图示如下:


(2)空间释放函数---->deallocate()

     身为一个配置器,_default_alloc_template拥有配置器标准接口函数deallocate()。该函数首先判断区块大小,大雨128bytes就调用第一级配置器,小于128bytes就找出对应的free list,将区块回收。

代码如下:

static void deallocate(void *p,size_t n)   //p不可以是0
{
	obj * q=(obj*)p;
	obj * volatile * my_free_list;
	//大于128bytes就调用第一级配置器
	if(n > (size_t)_MAX_BYTES)
	{
		malloc_alloc::deallocate(p,n);
		return ;
	}
	//寻找对应的free list
	my_free_list = free_list + FREELIST_INDEX(n);
	//调整free list,回收区块
	q->free_list_link = *my_free_list;
	*my_free_list = q;
}

图示如下:


(3)重新填充free lists---->refill()

        前面allocate()中说过,当他发现free list中没有可用区块了时,就调用refill(),准备为free list重新填充空间。新的空间将取自内存池(经由chunk_alloc()完成),缺省取得20个新节点(新区块),但万一内存池空间不够,获得的节点数(区块数)可能小于20

        refill()函数做了两件事:

      1)利用chunk_alloc申请空间

      2)将申请的空间第一块传给客端,其他块串起来。

代码如下:

//返回一个大小为n的对象,并且有时候会为适当的free list增加节点
//假设n已经适当调至8的倍数
template <bool thread,int inst>
void* _default_alloc_template<thread,inst>::refill(size_t n)
{
    int nobjs = 20;
    //调用chunk_alloc(),尝试取得nobjs个区块作为free_list的新节点
    //注意参数nobjs是pass by reference
    char * chunk = chunk_alloc(n, nobjs);
    obj * volatile * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;
 
    //如果只获得一个区块,这个区块就分配给调用者,free_list无新节点
    if(1 == nobjs)
    {
       return (chunk);
    }
    //否则准备调整free list,纳入新节点
    //my_free_list指向自己在free_lists中的位置
    my_free_list = free_list + FREELIST_INDEX(n);
 
    //以下在chunk空间内建立free list
    result = (obj *)chunk;      //这一块准备返回给客端
    //以下导引free_list指向新配置的空间(取自内存池)
    *my_free_list = next_obj = (obj *)(chunk + n);
    //以下将free_list的各节点串接起来
    for(i = 1; ; i++)
    {
        //从1开始,因为第0个将返回给客端
        current_obj = next_obj;
        next_obj = (obj*)((char *)next_obj + n);
        if(nobjs - 1 == i)
        {
            current_obj->free_list_link = 0;
            break;
        }
        else
        {
            current_obj->free_list_link = next_obj;
        }
 
    }
    return (result);
}

注:如果chunk_alloc()申请不到空间,chunk_alloc()会抛出异常,这个异常是由一级空间配置器oom机制抛出的。

(4)内存池(memory pool)---->chunk_alloc()

         从内存池中取空间给free list使用,是chunk_alloc()的工作。

代码如下:

//假设size已经适当的上调至8的倍数
//注意参数nobjs是pass by reference
template <bool threads,int inst>
char* _default_alloc_template<threads,inst>::chunk_alloc(size_t size,int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free;  //内存池剩余空间
    //内存池剩余空间完全满足需求量
    if(bytes_left >= total_bytes)
    {
        result = start_free;
        start_free += total_bytes;
        return (result);
    }
    //内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上区块
    else if(bytes_left >= size)
    {
        nobjs = bytes_left / size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return (result);
    }
    //内存池剩余空间连一个区块的大小都无法提供
    else
    {
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
        
        //以下试着让内存池的残余零头还有利用价值
        if(bytes_left > 0)
        {
            //内存池还有一些零头,先配置给适当的free_list
            //首先寻找适当的free_list
            obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
            //调整free_list,将内存池中的残馀空间编入
            ((obj*)start_free)->free_list_link = *my_free_list;
            *my_free_list = (obj*)start_free;
        }
 
        //配置heap空间,用来补充内存池
        start_free = (char*)malloc(bytes_to_get);
        //heap空间不足时,malloc()失败
        if(0 == start_free)
        {
		int i;
		obj *volatile * my_free_list, *p;
		//搜索适当的free_list
		//适当即"尚有未用区块。且区块足够大"
		for(i = size; i <= __MAX_BYTES; i += __ALIGN)
		{
		my_free_list = free_list + FREELIST_INDEX(i);
		p = *my_free_list;
		if(0 != p)
		{
			//free_list 内尚有未用区块
			//调整free_list以释放未用区块
			*my_free_list = p->free_list_link;
			start_free = (char*)p;
			end_free = start_free + i;
			//递归调用自己,为了修正nobjs
			return (chunk_alloc(size, nobjs));
			//注意:任何残余零头终将被编入适当的free list中备用
		}
	}
	end_free = 0;
	//如果出现意外(山穷水尽,到处都没有内存可用了)
	//调用第一级配置器,看看out-of-memory机制能否尽点力
	start_free = (char*)malloc_alloc::allocate(bytes_to_get);
	//这会导致抛出异常,或内存不足的情况获得改善
     }
     heap_size += bytes_to_get;
     end_free = start_free + bytes_to_get;
     //递归调用自己,为了修正nobjs
     return (chunk_alloc(size, nobjs));
}


上述chunk_alloc()函数以end_free - start_free来判断内存池的水量。如果水量充足,就直接调出20个区块返回给free list;如果水量不足以提供20个区块,但还是足够供应一个以上的区块,就拨出这不足20个区块的空间出去,同时nobjs参数将被修改为实际能够供应的区块数;如果内存池连一个区块空间都无法供应,对客端显然无法交代,此时便需要利用malloc()heap中配置内存,为内存池注入活水源头以应付需求。新水量的大小为需求量的2倍,再加上一个随着配置次数增加而愈来愈大的附加量。

 

举例说明:

假设程序一开始,客端就调用chunk_alloc(32,20),于是malloc()配置4032bytes区块,其中第一个交出,另19个交给free_list[3]维护,余20个留给内存池,接下来客端调用chunk_alloc(64,20),此时free_list[7]空空如也,必须向内存池要求支持,内存池只够供应(32*20)/64=1064bytes区块,就把这10个区块返回,第一个交给客端,余9个由free_list[7]维护,此时内存池全空。接下来调用chunk_alloc(96,20),此时free_list[11]空空如也,必须向内存池要求支持,而内存池此时也是空的,于是以malloc()配置40+n(附加量)96bytes区块,其中第一个交出,另19个交给free list[11]维护,余20+n(附加量)个区块留给内存池。

万一到了山穷水尽的一步,整个system heap空间都不够了(以至于无法为内存池注入活水源头)malloc()行动失败,chunk_alloc()就四处寻找有无”尚有未用区块,且区块足够大”free list,找到了就挖出一块交出,找不到就调用第一级配置器。第一级配置器其实也是用malloc()来配置内存的,但它由out_of_memory处理机制(类似于new_handler机制),或许有机会释放其他的内存拿来此处使用,如果可以,就成功,否则会发出bad_alloc异常。

 

图示如下:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值