SGISTL源码探究-第二级配置器

本文详细解析了二级内存配置器的工作原理,对于小于128字节的内存请求,通过16个free-list进行管理;大于128字节的请求则交由一级配置器处理。当free-list为空时,通过内存池重新填充。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

在本小节中我们将主要分析第二级配置器申请/释放内存的做法,它的主要实现机制是靠free_lists以及内存池来实现。内存池放在单独的一小节进行讲解。

第二级配置器(__default_alloc_template)

引入

第二级配置器要比第一级复杂的多。它的做法是,如果要求申请的空间大于128bytes,则让第一级配置器去做,如果小于128bytes,则由内存池提供,而第二级配置器只负责维护16个free-list,各自管理的大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128,当申请/释放空间时,大小进行调整为8的倍数,直接操作合适的free-list就行了。比如申请13bytes,自动调整为16bytes,然后从管理16bytes的free-list中取一个给用户,释放时,直接再挂在free-list上就行了。这样做有益于管理。

深入源码

下面直接看代码:

enum {__ALIGN = 8};//上调的边界,即每过8bytes上调一个free-list
enum {__MAX_BYTES} = 128}; //分配的空间大小上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN} //free-lists的个数

template<int inst>
class __default_alloc_template
{
  private:
    //该函数的作用是将申请的空间上调至8的倍数
    static size_t ROUND_UP(size_t bytes)
    {
        //这种写法很巧妙,先加上8,然后将结果的二进制中最后3位(1,2,4)抹去,这样剩下的(8,16,32...)都是8的倍数。
        //进而如果我们想要4的倍数,那么就(a+4)&(~(4 - 1))。get到了新技能。
      return (((bytes) + __ALIGN) & ~(__ALIGN - 1));
    }
  private:
    union obj
    {
      union obj * free_list_link;//用于连接free-list中的结点
      char client_data[1];
      //这里有一个char类型的指针,它的作用最初让我很疑惑,因为空间配置器的源码里面并没有用到它,后面在网上查阅了一下,大概明白了它的意图:由于联合体的特性(成员共用内存空间),client_data的地址就是obj的地址,不同的是指向的对象的类型不一样,一个是obj,一个是char,而client_data是给用户使用的,这样就可以避免强制转换了。
    };
  private:
    //16个free_lists
    static obj *volatile free_list[__NFREELISTS];
        //根据申请空间的大小决定使用第n号free_list,n从0开始
    static size_t FREELIST_INDEX(size_t bytes)
    {
      return (((bytes) + __ALIGN - 1) / __ALIGN- 1);
    }
    //重新填充free lists,返回一个大小为n的对象。
    static void *refill(size_t n);
    //分配size*nobjs大小的空间,如果nobjs太大了,可能会降低,所以传入引用
    static char *chunk_alloc(size_t size, int &nobjs);
    //内存池起始位置,只在chunk_alloc()中变化
    static char *start_free;
    //内存池结束位置,只在chunk_alloc()中变化
    static char *end_free;

    static size_t heap_size;
  public:
allocate
      static void *allocate(size_t n)
      {
        obj * volatile *my_free_list;
        obj *result;
        //若大于128bytes,交给一级配置器处理
        if(n > (size_t) __MAX_BYTES)
        {
          return (malloc_alloc::allocate(n));
        }
        //找到n对应的free_list
        my_free_list = free_list + FREELIST_INDEX(n);
        result = *my_free_list;
        //该free_list为空,重新将其填充
        if(result == 0)
        {
          void *r = refill(ROUND_UP(n));
          return r;
        }
        //调整free_list,类似链表的head=head->next操作,将撤出链表的空间块返回给用户
        *my_free_list = result->free_list_link;
        return (result);
      }
deallocate
      static void deallocate(void *p, size_t n)
      {
        obj *q = (obj *)p;
        obj * volatile * my_free_list;

        //若大于128bytes,交给一级配置器处理
        if(n > (size_t) __MAX_BYTES)
        {
          malloc_alloc::deallocate(p, n);
          return;
        }
        //找到n对应的free_list
        my_free_list = free_list + FREELIST_INDEX(n);
        //将该块空间重新加入free_list(链表的头插法)
        q->free_list_link = *my_free_list;
        *my_free_list = q;
      }
      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> __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};

在第二级配置器中的allocate函数中,当n对应的free_list并没有空间时,调用了refill函数,该函数从内存池中取空间,是连接free_list和内存池之间的枢纽。我们先看看refill函数的源码。

refill

该函数用于填充free_list,前面调用的时候只传入了需要分配的大小n,然后返回了。看起来似乎和free_list没什么关系,但是其实并不是。源码如下:

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
  int nobjs = 20;
  //默认申请20个新的节点
  //chunk_alloc函数是从内存池里面取空间出来返回
  //nobjs传的是引用,会根据内存池满不满足要求而改变(比如只够4个节点的,那nobjs会改成4)
  char * chunk = chunk_alloc(n, nobjs);
  obj * volatile * my_free_list;
  obj * result;
  obj * current_obj, * next_obj;
  int i;
  //如果内存池中只够1个节点,那返回直接给用户
  if (1 == nobjs) return (chunk);
  //根据n找到对应的free_list
  my_free_list = free_list + FREELIST_INDEX(n);
  /* result指向从内存池中申请的空间的首地址
   * my_free_list以及next_obj指向下一个节点
   * 这是因为会拿一个节点给用户,剩下的节点,加入到free_list中
   */
  result = (obj *)chunk;
  *my_free_list = next_obj = (obj*)(chunk + n);
  //将剩余的节点依次加入到free_list中去
  for(i = 1; ; i++)
  {
    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);
}

该函数的主要逻辑分2步(取-分):

  1. 通过chunk_alloc函数向内存池中取空间,默认取20个大小为n的节点,如果不够,能取多少取多少。
  2. 如果只取了1个节点,直接分给用户
    如果多于1个,分给用户一个,将剩余的节点加入到n对应的free_list中去

小结

在本小节中我们介绍了第二级配置器申请与释放内存的实现机制。将128以下的n分成16个free_list进行管理,申请与释放操作都在free_list上进行,当free_list没有节点可取时,通过内存池填充free_list,本节中只介绍到了填充函数,获取内存池空间的函数放在下一节详细介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值