对第一级配置的介绍可点此第一级配置器。
接下来介绍下第二级配置器。
我们在申请内存后,系统都要分配对应的空间来管理这些空间,如下图,如果申请的内存块很小,相对的这些管理内存的空间是越浪费的。
第二级配置器对内存的申请办法:
1、如果申请的内存块够大,超过超过128bytes,则移交给第一级配置器。
2、如果申请的内存块小于128bytes,则以内存池的方式进行管理,称之为次层配置。
次层配置流程是:每次配置一大块内存,并维护对应之自由链表(free-lists)。下次若再有相同大小的内存需求,就直接从free-lists中取,如果客端释放小额去块,就由配置器回收到free-lists中。
union obj
{
union obj *free_list_link;
char client_data[1]; /**/
}
union obj* free_list_link
。由于结点结构用的是union,当在链表中进行维护时,使用union中的union obj* free_list_link
指针,当将这个结点区块分配给客户端时,就不需要链表指针了,此时整个区块是作为一块内存给客端使用,使用union中的char client_data[1]
。 enum {__ALIGN = 8}; //小型区块的上调边界
enum {__MAX_BYTES = 128}; //小型区块的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; //free-lists个数
//第二级配置器,不讨论多线程环境
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.
//ROUND_UP 将bytes 上调至8的倍数
static size_t ROUND_UP(size_t bytes)
{
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
__PRIVATE:
//free-lists的结点构造
union obj
{
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
private:
//16个free-list结点
static obj * __VOLATILE free_list[__NFREELISTS];
//根据区块大小,决定使用第几号区块,n从0开始。
static size_t FREELIST_INDEX(size_t bytes)
{
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
}
//返回一个大小为n的对象,并可能加入大小为n的其他区块到free list
static void *refill(size_t n);
// 配置一大块空间,可容纳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;
public:
static void * allocate(size_t n)
{ /*接下来描述*/}
static void deallocate(void *p, size_t n)
{/*接下来描述*/}
static void * reallocate(void *p, size_t old_sz, size_t new_sz);
//以下是 static data member的定义于初始值设定
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, };
对于空间配置函数涉及到的处理如下:
refill()
,准备为free list重新填充空间。1.1 源码如下:
/* n must be > 0 */
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;
//大于128 bytes就调用第一级配置器
if (n > (size_t) __MAX_BYTES)
{
return(malloc_alloc::allocate(n));
}
//小于128 bytes就寻找16个free lists中适当的一个。
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if (result == 0)
{
//没找到可用的free list,就准备重新填充free list
void *r = refill(ROUND_UP(n));
return r;
}
//在当前free list中找到的话,就返回,并调整free list指向。
*my_free_list = result -> free_list_link;
return (result);
};
1.2 取区块操作图如下:
free list 被取出的结构如图2:
图2
释放函数处理流程如下:
2.1 源码如下:
//p不可以是 0
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;
//大于128就调用第一级配置器
if (n > (size_t) __MAX_BYTES) {
malloc_alloc::deallocate(p, n);
return;
}
//寻找对应的free list
my_free_list = free_list + FREELIST_INDEX(n);
//调整free list,回收区块
q -> free_list_link = *my_free_list;
*my_free_list = q;
}
2.2 区块回收操作如图3:
当进行allocate()
动作时,如果发现free list中没有可用区块时,就调用refill()
,为free list重新填充空间。
chunk_alloc()
完成)。3.1 源码如下:
//返回一个大小为n的对象,并且有时候会为适当的free list增加结点。
//假设n已经适当上调至8的倍数。
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 20;
//调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点。
//参数nobjs是pass by reference传下去的,因为有可能分配不到20个节点,
//所以要返回实际节点个数
char * chunk = chunk_alloc(n, nobjs);
obj * __VOLATILE * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i;
//如果只获得一个区块,这个区块就分配给调用者用,free list无新节点增加。
if (1 == nobjs) return(chunk);
//准备调整free list,纳入新节点。
my_free_list = free_list + FREELIST_INDEX(n);
//以下在chunk 空间内建立free list。
result = (obj *)chunk; //这一块准备返回给客端
//以下将free list指向新配置的空间(取自内存池)
//因为第一块已经默认返回给客端,所以free list要移动n个位置
*my_free_list = next_obj = (obj *)(chunk + n);
for (i = 1; ; i++) //从1开始,第0个已返回给客端
{
//以下就是移动指针指向,保证每个区块指向对应大小位置的下一个区块开始
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()
调用chunk_alloc()
从内存池去空间给free list使用。
4.1 源码如下:
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
char * result;
size_t total_bytes = size * nobjs;
size_t bytes_left = end_free - start_free; //内存池剩余空间
if (bytes_left >= total_bytes)
{
//内存池剩余空间完全满足需求量
result = start_free;
start_free += total_bytes;
return(result);
}
else if (bytes_left >= size)
{
//内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块
nobjs = bytes_left/size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
}
else
{
//内存池剩余空间连一个区块的大小都无法提供
//这里申请2倍于上层请求的内存空间 + n大小的堆大小。
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
//以下试着让内存池中的残余零头还有利用价值,ROUND_UP保证了每次多分配的大小是8的倍数,
//所以内存池多出来的零头也肯定是8的倍数,是可以直接放入到free list中的。
if (bytes_left > 0)
{
//内存池中还有一些零头,先配给适当的free list。
//首先寻找适当的free list。
obj * __VOLATILE * my_free_list =
free_list + FREELIST_INDEX(bytes_left);
//调整free list,将内存池中的残余空间编入
((obj *)start_free) -> free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
}
//配置heap空间,用来补充内存池
start_free = (char *)malloc(bytes_to_get);
if (0 == start_free)
{
//heap空间不足,malloc()失败
int i;
obj * __VOLATILE * my_free_list, *p;
//试着检视我们手上拥有的东西。这个不会造成伤害。我们不打算尝试配置较小的区块,
//因为那在多进程机器上容易导致灾难。
//以下搜寻适当的free list,所谓适当是指“尚有未用区块,且区块够大”之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;
//递归调用自己,为了修正nobjs。
return(chunk_alloc(size, nobjs));
//注意,任何残余零头终将被编入适当的free list中备用
}
}
//如果出现意外,已经到处无内存可用
end_free = 0; // In case of exception.
//调用第一级配置器,看看out-of-memory机制嫩否尽点力。
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
//这会导致抛出异常或内存不足的情况获得改善。这取决于我们设置的“set_malloc_handler”。
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
//递归调用自己,为了修正nobjs。
return(chunk_alloc(size, nobjs));
}
}
4.2 chunk_alloc()
函数大概实现思路:
chunk_alloc()
以end_free - start_free
来判断内存池的水量。malloc()
从heap中配置内存,为内存池增加内存,内存大小为2*需求大小 + 随配置次数增加而愈来愈大的附加量
。4.3 当System heap
内存不足以malloc()
时,chunk_alloc()
处理策略如下: