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在当中扮演着相当重要的角色。