您的位置:首页 > 其它

【STL】空间配置器(二):二级空间配置器

2016-12-07 17:23 381 查看
SGI STL一级空间配置器:

http://blog.csdn.net/bit_clearoff/article/details/53503846

前面我们已经分析过了SGI STL一级空间配置器的实现以及其中的相关机制,今天我们主要来分析一下空间配置器中的重头戏:二级空间配置器。我们先来用图描述一下二级空间配置器的结构:



上图是二级空间配置器的基本结构是有一块事先分配好内存的内存池和一个给对象拨内存块的free_list所组成的,为什么要引入内存池而不是直接从操作系统中给free_list分配内存呢?这时为了解决内存碎片和操作系统不断的分配小内存块所存在的性能上的问题,详情大家可以看一下

http://blog.csdn.net/bit_clearoff/article/details/53503846

这篇文章,这里只简单的提一下内存碎片是由于操作系统调用malloc函数不断地开辟和释放小内存块,然后当要开辟一块大内存块时,上面的小内存块不足以使用,从而是这些小内存块成为了内存碎片。而内存池就可以很好的解决这个问题,当我们开辟小内存块时,就在自由链表中找到合适的内存空间供给对象使用,如果free_list的合适的位置上没有内存空间了,便会从内存池中拨出空间;在SGI STL的二级空间配置器中,默认>128bytes的内存空间为大块内存,这时候会直接调用一级空间配置器去分配大块内存空间。下面我们将会详细的讲下这个过程

SGI STL空间配置器的实现机制



二级空间配置器源码分析

我在源码中进行了相关的解读

二级空间配置器的定义

template <bool threads, int inst>
class __default_alloc_template


free_list的相关参数

# ifndef __SUNPRO_CC
//小型区块的上调边界
//为什么上调至8呢,因为32位系统的指针4bytes
//64位系统下的指针为8bytes这里取最大值
enum {__ALIGN = 8};
//free_list的区块上限(128bytes)
enum {__MAX_BYTES = 128};
//free_list数组中自由链表的个数
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
# endif


将要开辟的区块大小上调至8的倍数

static size_t ROUND_UP(size_t bytes) {
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}


free_list中的节点结构

union obj {
//将节点链起来的指针
union obj * free_list_link;
//这个参数暂不解释
char client_data[1];    /* The client sees this.        */
};


free_list[]的定义

# ifdef __SUNPRO_CC
static obj * __VOLATILE free_list[];
// Specifying a size results in duplicate def for 4.1
# else
static obj * __VOLATILE free_list[__NFREELISTS];
# endif


free_list中的volatile关键字有是为了防止free_list发生不可预知的变化关于此关键字大家可以看看这篇文章

https://www.zhihu.com/question/31459750

二级配置器中的Memory pool相关指针和参数

// Chunk allocation state.
//指向在内存池中未分配给free_list的内存空间的首地址
static char *start_free;
//指向内存池的末地址
static char *end_free;
//内存池的总空间大小
static size_t heap_size;


二级空间配置器中分配内存的函数

static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;

//如果所要开辟的区块的大小大于128bytes调用一级空间配置器
if (n > (size_t) __MAX_BYTES) {
return(malloc_alloc::allocate(n));
}

//如果所开辟的区块的大小小于128bytes
//首先在free_list[]中找到要开辟区块的大小所对应的free_list的位置
//FREELIST_INDEX(n)返回将n上调至8的倍数后在free_list[]中所对应的下标
my_free_list = free_list + FREELIST_INDEX(n);
// Acquire the lock here with a constructor call.
// This ensures that it is released in exit or during stack
// unwinding.
#       ifndef _NOTHREADS
/*REFERENCED*/
lock lock_instance;
#       endif
//解引用找到对应的free_list的首地址
//这里需要注意free_list上挂的都是未分配出去的内存
result = *my_free_list;
//如果result为NULL说明free_list现在已经没有内存空间了
//这时需要调用refill向内存池申请内存空间
if (result == 0) {
void *r = refill(ROUND_UP(n));
//申请到空间后直接返回申请到空间的首地址
//这里如何获得申请到的空间的首地址将在下面的refill函数中体现
return r;
}
//如果此时free_list上有空间,则拨出一块空间给对象使用
*my_free_list = result -> free_list_link;
//返回这块空间
return (result);
};


下面是使用完毕后的内存空间如何返还给free_list,即对象的释放空间操作

/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;

//如果释放的是大块内存大于128bytes,直接调用一级空间配置器deallocate()函数去free这块空间
if (n > (size_t) __MAX_BYTES) {
malloc_alloc::deallocate(p, n);
return;
}

//如果是小内存块的释放,则还是先找到所释放内存块在free_list[]中的位置
my_free_list = free_list + FREELIST_INDEX(n);
// acquire lock
#       ifndef _NOTHREADS
/*REFERENCED*/
lock lock_instance;
#       endif /* _NOTHREADS */
//把要返还给free_list的内存直接头插到free_list上
q -> free_list_link = *my_free_list;
//调整free_list上的首地址,即返还给free_list中的这个节点
*my_free_list = q;
// lock is released here
}


下面是从内存池向free_list中重新填充内存块函数



template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
//默认填充20个(n字节上调至8的整数倍)的内存块
int nobjs = 20;
//chunk_alloc(size_t size, int& nobjs)注意这里是引用传值
//这个函数的作用是尝试取得nobjs个(n字节上调至8的整数倍)的内存块作为free_list的新节点
//这里需要注意取得的不一定是20个区块,如果内存池的空间不够,它所获得的区块数目可能小于20个
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);
//如果不为1则找到区块在free_list[]中所对应位置
my_free_list = free_list + FREELIST_INDEX(n);

/* Build free list in chunk */
//从头拨出1个申请好的区块在下面返还给对象
result = (obj *)chunk;
//把剩余的区块全部链在free_list[FREELIST_INDEX]下面
*my_free_list = next_obj = (obj *)(chunk + n);
for (i = 1; ; i++) {
current_obj = next_obj;
//这里需要注意的是chunck_alloc返回的空间类型为char*
next_obj = (obj *)((char *)next_obj + n);
//如果已经链上的节点的个数等于从内存池申请的节点的个数-1
//终止循环
if (nobjs - 1 == i) {
current_obj -> free_list_link = 0;
break;
} else {
current_obj -> free_list_link = next_obj;
}
}
return(result);
}


下面是二级空间配置器的重头戏,从内存池中获取区块



template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
char * result;

//tolal_bytes是所要尝试申请的区块的大小
size_t total_bytes = size * nobjs;

//计算剩余区块的大小
size_t bytes_left = end_free - start_free;

//如果剩余的区块的大小》所申请的区块的大小
//直接返回total_bytes个大小的空间
//start_free向后移动total_bytes个单位
if (bytes_left >= total_bytes) {
result = start_free;
start_free += total_bytes;
return(result);
}
//如果(一个区块的大小《剩余的区块的大小《所申请的区块的大小)
//直接返回剩余的区块中所申请区块大小的整数倍个空间
//start_free向后移动返回的内存空间bytes个单位
else if (bytes_left >= size) {
nobjs = bytes_left/size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
}
//如果剩余空间的大小小于<一个区块的话
//内存池需要向操作系统来申请空间
else {
//向操作系统申请空间的大小
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
// Try to make use of the left-over piece.
//内存池向操作系统申请内存空间之前,先把剩余空间分配给free_list
if (bytes_left > 0) {
obj * __VOLATILE * my_free_list =
free_list + FREELIST_INDEX(bytes_left);

((obj *)start_free) -> free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}

//操作系统用malloc给内存池分配空间
start_free = (char *)malloc(bytes_to_get);
//如果操作系统分配不出这么大的内存空间
if (0 == start_free) {
int i;
obj * __VOLATILE * my_free_list, *p;
// Try to make do with what we have.  That can't
// hurt.  We do not try smaller requests, since that tends
// to result in disaster on multi-process machines.
//我们现在free_list中查找没有使用过的内存块,并且它足够大
for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if (0 != p) {
*my_free_list = p -> free_list_link;
start_free = (char *)p;
end_free = start_free + i;
return(chunk_alloc(size, nobjs));
// Any leftover piece will eventually make it to the
// right free list.
}
}
//如果free_list中也没有内存块了
end_free = 0;   // In case of exception.
//试着调用一级空间配置器
//看看它的oom_alloac()是否能为我们解决问题
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
// This should either throw an
// exception or remedy the situation.  Thus we assume it
// succeeded.
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return(chunk_alloc(size, nobjs));
}
}


看到这里相信大家都能对整个控制配置器的执行流程有所了解了,如果以上内容有误,希望能得到大家的指导。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息