SGISTL源码阅读二 空间配置器中(第二级配置器__default_alloc_template)

SGISTL源码阅读二 空间配置器中(第二级配置器__default_alloc_template)

引入

SGI空间配置器的做法是,如果区块够大,超过了128bytes,就移交给第一级配置器处理。当区块小于128bytes时就是第二级配置器要做的事情了。
SGI的第二级配置器维护了16个__free-lists__,各自管理的大小分别是8,16,24,32,···,128bytws的小额区块,以内存池管理(后续文章中将详细分析内存池)。
我们先从图示了解第二级配置器的__free-lists__。

union obj {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this. */
  };

在这里插入图片描述

深入源码

template <bool threads, int inst>
class __default_alloc_template {

private:
  // Really we should use static const int x = N
  // instead of enum { x = N }, but few compilers accept the former.
# ifndef __SUNPRO_CC
    enum {__ALIGN = 8};                          //每过8bytes上调一个free-list
    enum {__MAX_BYTES = 128};                    //上限值128bytes
    enum {__NFREELISTS = __MAX_BYTES/__ALIGN};   //free-list的个数(128/8=16)
# endif
	//将申请空间的大小上调至8的倍数
	static size_t ROUND_UP(size_t bytes) {
    	//先将申请大小+8,再通过与运算将后三位(二进制111为十进制8)抹去,则为8的倍数
      	return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
	}
__PRIVATE:
	//free-lists节点结构
    //因为使用的是联合体,节省了内存的开销
	union obj {
        union obj * free_list_link;
        //client_data的地址就是obj的地址,不同的是指向的对象的类型不一样,一个是obj,一个是char。
        //而client_data是给用户使用的,这样就可以避免强制转换了。
        char client_data[1];    /* The client sees this.        */
	};
private:
	//16个free-lists
    //__VOLATILE是指每次取值都从内存中取
    static obj * __VOLATILE free_list[__NFREELISTS];
    
	//该函数根据区块大小,返回应该使用第几号free-list(从0开始计数)
    static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }

  // Returns an object of size n, and optionally adds to size n free list.
  //但会一个大小为n的对象,并可能加入大小为n的其他区块到free-list
  static void *refill(size_t n);
  
  // Allocates a chunk for nobjs of size "size".  nobjs may be reduced
  // if it is inconvenient to allocate the requested number.
  //配置一大块空间,可容纳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;

(对加了下划线的命名方式一直都有一种莫名的畏惧,这种命名代表的是会被内部调用,而不是给用户直接使用)
通过以上源代码的阅读,我们对free-list有了一定的了解,它就是由obj联合体构成的16个长度的数组,每一个obj联合体又链接起来(就是链表,只是这里用的是联合体而不是结构体,好处是能够节省内存),在使用之前要将size ROUND_UP为8的倍数,FREELIST_INDEX可以帮我们找到具体位置。
在文章之后将详述static void *refill(size_t n);

public:

    /* n must be > 0      */
    static void * allocate(size_t n)
    {
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;
    //如果n大于128bytes,则使用第一级空间配置器,否则使用第二级空间配置器
    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    //获取my_free_list的位置
    my_free_list = free_list + FREELIST_INDEX(n);

    result = *my_free_list;
    if (result == 0) {
        //如果没有找到可用大小的free-list,就重新填充
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //调整free-lists
    //将result所指向的区块从free-lists中取出供使用
    *my_free_list = result -> free_list_link;
    return (result);
    };

    /* p may not be 0 */
    static void deallocate(void *p, size_t n)
    {
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;
	//如果n大于128bytes,则使用第一级空间配置器,否则使用第二级空间配置器
    if (n > (size_t) __MAX_BYTES) {
        malloc_alloc::deallocate(p, n);
        return;
    }
    //寻找对应的free-list
    my_free_list = free_list + FREELIST_INDEX(n);
    //调整free-lists,将释放的区块回收
    q -> free_list_link = *my_free_list;
    *my_free_list = q;
    }

以上源码为第二级空间配置器的空间配置函数allocate()和空间释放函数deallocate()
这两个函数的其实并不难理解,首先判断区块的大小,如果小于128bytes才使用第二级空间配置器,从free-list中取出(对应于allocate)或放回(对应于deallocate)区块。
下面将为大家介绍refill,它用来解决free-lists中没有可用区块的情况。

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    //默认申请20个新的节点
    //chunk_alloc函数是从内存池里面取空间出来并返回首地址
    //nobjs传的是引用,会根据内存池满不满足要求而改变,之后会详细介绍内存池
    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);
    //寻找对应的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);
}

refill的做法是像内存池申请20个新的obj,但是我们很容易想到的是内存池中的容量也可能会不足,所以nobjs是通过传引用,如果内存池中的容量不够,那么nobjs的大小也会随之而改变。
如果只申请到了一个节点,那么直接返回给用户,如果有多,则将第一个节点返回给用户,后续节点fill到free-lists中去。
你可能会疑惑,这里一定能够申请到节点吗?如果申请失败了怎么办?为什么这里没有相应解决措施?
这是内存池所要解决的问题,后面会介绍到!

总结

我们了解了SGISTL第二级空间配置器,free-lists在当中扮演着相当重要的角色。

猜你喜欢

转载自blog.csdn.net/lyn_00/article/details/83958274