SGI Allocator内存管理(二)

接上一篇博文继续讲。

我们来看第二级配置器__default_alloc_template.

先看它有哪些成员(代码参考自《STL源码剖析》):

template<bool threads,int inst>
class __default_alloc_template{
private:
  /*将bytes上调至__ALIGN(默认为8)的倍数*/
  static size_t ROUND_UP(size_t bytes){
return ((bytes+__ALIGN-1)&~(__ALIGN-1));
}
  /*管理的内存块*/
  union obj{
union obj* free_list_link;  
char client_data[1];
  }
  static obj* volatile free_list[__NFREELISTS];  //空闲内存链表
  /*根据bytes大小定位到free_list*/
  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的内存块*/
  static char*chunk_alloc(size_t size,int &nobjs);
  
  static char*start_free;  //内存池起始位置
  static char*end_free;  //内存池结束位置
  static size_t heap_size;  //内存池总内存大小

public:
  static void*allocate(size_t);  //分配内存
  static void deallocate(void*,size_t);  //回收内存
  static void*reallocate(void*,size_t,size_t);  //重配置内存
}

与第一级配置器相比,__default_alloc_template的内存管理更为复杂。

策略:

1.__default_alloc_template只管理一定大小的内存块(__MAX_BYTES默认为128B),当用户程序向它申请、释放或者重配置超过该大小的内存时,__default_alloc_template的相关成员函数会调用第一级配置器的相关成员函数进行处理。

这里要提一下union obj这个指向内存块的联合体,刚开始读源码的时候,我一直迷惑不解这个联合体是如何指向不同大小的内存块的。起初我以为obj就是用来分配的内存,后来发现不对,按照obj的定义,它包含一个obj*指针和一个包含一个字符的字符数组,根据联合体的性质,sizeof(obj) = 4,如果obj就是分配给用户的内存,那obj只有4B这么大。我又仔细读了一遍书上关于obj的说明:

由于union之故,从其第一字段观之,obj可视为一个指针,指向相同形式的另一个obj;从第二个字段观之,obj可被视为一个指针,指向实际区块。

从书上这句话来看,obj应该被当作指向一块实际内存的指针,再结合obj的目的(减少额外内存开销)obj实际作用应该是当某块内存处于free_list中时,obj中的free_list_link起作用——链接下一块obj,当该内存分配出去,即从free_list删除后obj中的client_data起作用,指向该块内存。所以最后一个问题,client_data如何起到作用?由于书上没有给出obj具体使用实例,这里我补充一个:

union obj* ob = (union obj*)::operator new(100);
cout<<"(*ob)'s address:"<<ob<<endl;
cout<<"ob->client_data:"<<(void*)ob->client_data<<endl;

现在很清楚了,obj存放某一块内存的首地址,由于union之故,该内存首址即client_data数组的首地址,所以client_data可以指向不同大小的内存。(ob=&obj=client_data)

2.__default_alloc_template如何管理小于128B的内存块?

以内存分配为例:

1)调用allocate(n)进行内存分配;

2)n>__MAX_BYTES,调用一级配置器进行分配:

if(n>__MAX_BYTES)

{  return(malloc_alloc::allocate(n)); }

3)否则,从free_list相应位置找到一块内存返回:

  my_free_list = free_list+FREELIST_INDEX(n);

  result = *my_free_list;

到了这一步可能会出现问题,上一篇博文讲到SGI空间配置器要解决内存不够用的问题。内存不足表现在两个方面:

(1)    用户程序向free_list申请内存时,free_list内存不足;

(2)    free_list向内存池__default_alloc_template申请内存时,__default_alloc_template内存不足。

 

4)先看__default_alloc_template如何解决问题(1):

如果result!=0,即free_list有相应大小的内存块可以分配,则将内存块从free_list中直接删除,并返回result;

否则,allocate函数转而调用refill函数,refill函数的功能是free_list向内存池剩余空间申请若干块相同大小的内存块并挂载到free_list相应位置。

refill函数部分代码如下(具体代码见《STL源码剖析》):

void* refill(size_t n)
{
int nobjs = 20;  //默认向内存池申请20个大小为n B的内存块
char* chunk = chunk_alloc(n,nobjs);  //向内存池申请nobjs块大小为n B的内存块,也有可//能申请到小于nobjs块,具体见后面chunk_alloc函//数介绍
……
if(1==nobjs) return chunk;    //如果只申请到一块n B大小的内存,则直接返回该内存
…..
/*如果申请到的内存块多余一块,则将除第一块以外的所有内存块链接到free_list相应
位置上*/
result =(obj*)chunk;
……
for(i=1;;i++)
{
  /*将各个内存块串连起来*/
}
…
return result;  //返回第一块内存
}

5)最后看free_list向内存池申请内存的函数chunk_alloc:

char* 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;
}
/*内存池内存不够分配size*nobjs大小内存,但是至少可以分配一块size大小的内存*/
 else if(bytes_left>=size)
{
  nobjs = bytes_left/size;      //nobjs变为实际分配的size大小内存块的块数
  total_bytes = size*nobjs;
  result = start_free;
  start_free +=total_bytes;
  return result;
}
/*内存池剩余空间不足size*/
else
{
  /*计算内存池需要向系统申请多少内存*/
  size_t bytes_to_get = 2*total_bytes+ROUND_UP(heap_size>>4);

  /*如果内存池还有不足size的剩余空间*/
  if(bytes_left>0)
  {
/*将内存池这些剩余内存全部分配给free_list,这步操作完成后,内存池已无内存可用*/
}
/*此时内存池需要补充新鲜血液,要向系统申请内存*/
start_free = (char*)malloc(bytes_to_get);
/*如果系统也没有充足的内存*/
if(start_free==0)
{
……
/*此时要从掌管size大小的free_list开始往后遍历一遍free_list*/
for(i =size;i<=__Max_BYTES;i+=__ALIGN)
{
….
/*如果某一free_list上有空闲内存,则将其交还给内存池,然后再递归调用此函数,分配内存,修正nobjs*/
….
return chunk_alloc(size,nobjs);
}
        /*到了这一步,无论是内存池还是free_list还是系统都没有大于等于size的内存块可用,那么只好请求第一级配置支援咯*/
       end_free = 0;
       start_free = (char*)malloc_alloc::allocate(bytes_to_get);
}
/*系统分配内存池以bytes_to_get大小的内存块,内存池修改相关数据,然后递归调用chunk_alloc*/
heap_size +=byte_to_get;
end_free = start_free+bytes_to_get;
return chunk_alloc(size,nobjs);
}
}

总结起来,chunk_alloc函数目的就是从__default_alloc_template或者它的free_list或者第一级配置器或者系统堆中至少找到一块满足size大小的内存块交给refill,否则抛出异常。

至此,SGI两级空间配置器空间管理策略介绍完了,感叹下,STL博大精深,一个空间配置器都写的如此精髓,看来学习还要加把劲了。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值