您的位置:首页 > 其它

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

2017-09-06 19:28 417 查看

前言

在本小节中我们将主要分析第二级配置器申请/释放内存的做法,它的主要实现机制是靠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:


[b]allocate[/b]

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);
}


[b]deallocate[/b]

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
函数的源码。

[b]refill[/b]

该函数用于填充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步(取-分):

通过
chunk_alloc
函数向内存池中取空间,默认取20个大小为n的节点,如果不够,能取多少取多少。

如果只取了1个节点,直接分给用户

如果多于1个,分给用户一个,将剩余的节点加入到n对应的free_list中去

小结

在本小节中我们介绍了第二级配置器申请与释放内存的实现机制。将128以下的n分成16个free_list进行管理,申请与释放操作都在free_list上进行,当free_list没有节点可取时,通过内存池填充free_list,本节中只介绍到了填充函数,获取内存池空间的函数放在下一节详细介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: