文章目录
一个包装接口simple_alloc:
作用:SGI STL为了使配置器的接口能够符合STL的标准规范
template<class T, class Alloc>
class simple_alloc
{
public:
static T *allocate(size_t n)
{ return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
static T *allocate(void)
{ return (T*) Alloc::allocate(sizeof (T)); }
static void deallocate(T *p, size_t n)
{ if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
static void deallocate(T *p)
{ Alloc::deallocate(p, sizeof (T)); }
};
双层级配置器
- 第一级配置直接使用malloc()和free()
- 第二级配置器则视情况采用不同的策略,当配置区大于128bytes时,直接调用第一级配置器;当配置区块小于128bytes时,借助第一级配置器,使用一个memory pool(内存池)来实现。使用第一级配置器还是第二级配置器,由一个宏定义来控制。SGI中默认使用第二级配置器。
第一级配置器(__malloc_alloc_template)
一级空间配置器调用malloc()函数分配空间,如果分配成功直接返回这块空间,如果空房间分配失败并且用户自己定义了内存不足处理函数,就调用内存不足处理函数的,然后尝试去开辟新空间,如果内存还是分配不成功就继续调用内存不足处理函数直到成功分配到内存为止,如果用户没有定义内存处理函数,那么就会抛出一个异常。
一级空间配置器_malloc_alloc_template源码分析
// 基于malloc()的配置器.通常比第二级配置器(__default_alloc_template)慢.
// 通常是线程安全的,并且对存储空间的使用更加高效.
#ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG
# ifdef __DECLARE_GLOBALS_HERE
void (* __malloc_alloc_oom_handler)() = 0; // 内存不足处理函数指针
// g++ 2.7.2 does not handle static template data members.
# else
extern void (* __malloc_alloc_oom_handler)();
# endif
#endif
template <int inst> // inst为预留参数
class __malloc_alloc_template {
private: // 用来处理内存不足的函数和函数指针
static void *oom_malloc(size_t);
static void *oom_realloc(void *, size_t);
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
static void (* __malloc_alloc_oom_handler)();
#endif
public:
static void * allocate(size_t n)
{
void *result = malloc(n); // 直接调用malloc()开辟空间
if (0 == result)
result = oom_malloc(n); // 内存申请失败,调用内存不足处理函数
return result;
}
static void deallocate(void *p, size_t /* n */)
{
free(p); // 直接调用free()函数释放空间
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz); // 直接使用realloc()开辟空间
if (0 == result)
result = oom_realloc(p, new_sz); // 内存申请失败,调用内存不足处理函数
return result;//内存申请成功就返回这块空间
}
// 指定内存不足处理函数句柄
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return(old);
}
};
//初始化hander
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0; // 初值为0,需用户自己设定
#endif
// 内存不足处理函数:malloc()申请内存失败
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
void (* my_malloc_handler)();
void *result;
for (;;) { //如果内存申请不成功,反复执行内存不足处理函数
//将my_malloc_handler函数指针指向用户定义的内存不足处理函数
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler) {
__THROW_BAD_ALLOC; } // 用户未设定处理函数,直接抛出异常
(*my_malloc_handler)(); // 调用内存不足处理函数
result = malloc(n); // 再次尝试申请内存
if (result)
return(result); // 申请成功
}
}
// 内存不足处理函数:realloc()申请内存失败(与oom_malloc()类似)
template <int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{
void (* my_malloc_handler)();
void *result;
for (;;) {
my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
(*my_malloc_handler)();
result = realloc(p, n);
if (result) return(result);
}
}
typedef __malloc_alloc_template<0> malloc_alloc; // inst直接被指定为0
内存不足处理函数应该做哪些工作
- 删除无用的内存,使系统具有更多的内存可以使用,为下一步的内存申请做准备
- 抛出自定义异常
- 不再返回,调用abort或exit退出程序
内碎片
因为内存对齐/访问效率(CPU取址次数)而产生 如 用户需要3字节,实际得到4或者8字节的问题,其中的碎片是浪费掉的。
外碎片
系统中内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费
第二级配置器(__default_alloc_template)
二级空间配置器的基本结构是由一块事先分配好内存的内存池和一个给对象拨内存块的free_list所组成的
二级空间配置器执行过程
- 二级空间配置器在索要内存时调用allocate函数,如果索要的内存大于128字节,调用一级空间配置器,否则allocate查free_list数组,从free_list中某个元素指向的链表中取出一块内存后返回。
- 当free_list没有合适的内存区块时调用refill函数。
- refill调用_S_chunk_alloc,_S_chunk_alloc分配一段内存给内存池,把内存池残余的空间编入free_list。
- _S_chunk_alloc 调用完毕,回到refill,refill查free_list数组,从free_list中某个元素指向的链表中取出一块内存后返回给allocate,allocate再返回给客户端。
二级空间配置器源码分析
enum{__ALIGN = 8};//最小申请的空间的大小
enum{__MAX_BYTES = 128};//能最大申请的空间的大小
//SGI第二配置器有16个free_lists
enum{__NFREELISTS = __MAX_BYTES / __ALIGN};//free_lists的个数
template<bool threads, int inst>
class __default_alloc_template
{
private:
//将用户申请的空间的大小上调至8的倍数
static size_t ROUND_UP(size_t bytes)
{
return (((bytes) + __ALIGN-1)&~(__ALIGN - 1));
}
private:
//16个free_lists的节点结构
//union能够实现一物两用的效果,1.obj可被视为一个指针,指向相同形式的另一个obj。2.obj可被视为一个指针,指向实际区块。
union obj{
union obj * free_list_link;//指向下一个节点的指针
char client_data[1];//记录此时节点数据
};
private:
static obj * volatile free_list[__NFREELISTS];//16个free_lists
//计算客户申请的空间的大小在哪个free_lists自由链表
static size_t FREELIST_INDEX(size_t bytes)
{
return (((bytes) + __ALIGN-1)/ __ALIGN - 1);
}
static char *start_free;//内存池起始位置
static char *end_free;//内存池结束位置
static size_t heap_size;//附加量
private:
static void *refill(size_t n);
static char *chunk_alloc(size_t size, int &nobjs);
public:
//空间配置函数
static void *allocate(size_t n)
{
obj * volatile * my_free_list;
obj * result;
//客户申请的空间大于128采用一级空间配置器进行空间的申请
if(n > (size_t __MAX_BYTES){
return (malloc_alloc::allocate(n));
}
//my_free_list这个二级指针指向客户申请空间大小适合的自由链表free_lists
my_free_list = free_list + FREELIST_INDEX(n);
//*my_free_list这个指针指向的是相对应的还没有给客户端分配的自由链表的起始
//位置
result = *my_free_list;
//没有找到可用的free_list,准备查看有没有可用的内存池来填充自由链表
if(0 == result){
void *r = refill(ROUND_UP(n));
return r;
}
//找到可用的自由链表,调整自由链表,让*my_free_list这个指针指向还没有给客 //户分配的自由链表的起始位置,也就是result的下一个节点的位置
*my_free_list = result->free_list_link;
return result;
}
//空间释放函数
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * volatile * my_free_list;
if(n > (size_t)__MAX_BYTES){
malloc_allloc::deallocate(p, n); 释放的内存大于128字节直接调用一级配置器进行释放
return;
}
//释放已经给用户分配出去的p空间,也就是让p空间重新连接到自由链表上
//也就是先让p空间的指针指向下个节点也就是自由链表的起始位置,然后让自由链 //表的起始位置重新指向p空间
my_free_list = free_list + FREELISTS_INDEX(n);
q->free_list_link = *my_free_list;
*my_free_list = q;
}
};
//对__default_alloc_template这个类内的数据成员的定义与初值设定
template<bool threads, int inst>
char *__default_alloc_template<tnreads, 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>::head_size = 0;
//这里的数据成员free_list,由于free_list的类型obj * volatile是模板类型声明的
//并且这个函数名也是模板类型内声明的,所以这里对free_list的定义与处值的设定
//用两个模板类__default_alloc_template<threads, inst>
template<bool threads, int inst>
__default_alloc_template<thread, inst>::obj * volatile
__default_alloc_template<threads, inst>::free_list[__NFREELISTA]=
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};
//重新填充函数,当发现自由链表没有可用区块了,就调用这个函数从内存池中找到空间
//重新填充自由链表
template<bool threads, int inst>
void *__default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = 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);
}
my_free_list = free_list + NFREELIST_INDEX[n];
result = (obj *)chunk;//这一块准备返回给客户
//chunk指向的是新free_list的起始位置,这里让*my_free_list指向新配置的
//空间,取自内存池
*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){
//最后一个节点的指针指向NULL
current_obj->free_list_link = 0;
break;
}else{
current_obj->free_list_link = next_obj;
}
}
return (result);
}
//从内存池中取出空间给free_list使用
template<bool threads, int inst>
char *__defaule_alloc_template<threads, inst>::chunk_alloc(size_t size, int &nobjs)
{
char *result;
size_t total_bytes = size * nobjs;//需要给free_list分配的空间大小
size_t bytes_left = end_free - start_free;//内存池剩余空间大小
//内存池的空间足够满足需求量
if(bytes_left >= total_bytes){
result = start_free;//result得到新的free_list的起始位置
start_free += total_bytes;
return result;
}
//内存池剩余空间不能够满足需求量,但是足够供应一个或一个以上的区块
else if(bytes_left >= size){//size为一个区块的大小
nobjs = bytes_left / size;//nobjs代表几个大小为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);
//内存池还有一些零头,先分配给适当的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;
}
//内存池一点零头都没有了,就配置heap空间
start_free = (char *)malloc(bytes_to_get);
//如果系统的heap空间不足,malloc()失败
if(0 == start_free){
int i;
obj * volatile * my_free_list, *p;
//遍历搜索看还有没有用的free_list吗,
for(i = size; i < __MAX_BYTES; i += __ALIGN){
my_free_list = free_list + FREELIST_INDEX[i];
p = *my_free_list;
//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);
}
}
//到处没有内存可用则调用一级空间配置器
end_free = 0;
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
//递归调用自己为了修正nobjs
return chunk_alloc(size,nobjs);
}
}
解释:假设用户申请的空间大于128bytes。那么就调用第一级的空间配置器,假设小于128bytes,那么就使用memory pool来进行处理,在用户使用的时候。都是使用再次封装好的了simple_alloc模板类,这里着重说使用空间小于128时。使用的是__default_alloc_template的情况,这里有一个数组free_list,数组中每个元素都是一个链表,数组每个元素相应的都是逐渐增大的小的内存空间,假设申请空间时。在free_list相关位置能够找到合适的空间时就直接使用。在没有合适的空间时,就须要refill了,就是又一次填充free_list。在refill中首先调用chunk_alloc尝试取得20个区块free_list的新节点,事实上在chunk_alloc中是申请了2倍的空间,当中一半是给free_list。一半给memory pool,给了free_list的部分在refill中整合各个节点空间。在memory pool是为了下一层更好的使用。
举例:如果程序以開始。client就调用chun_alloc(32,20),于是malloc配置40个(2倍的空间)32bytes区块。当中1个叫出来让用户使用,另外19个交给free_list[3]维护,剩余20个留给内存池,接下来client调用chunk_alloc(64,20),此时free_list[7]空空如也。必须向内存池要求支持。内存池仅仅够供应(32*20)/64 =10个64bytes区块,就把这10个区块返回,第一个交给client。剩余9个由free_list[7]维护。此时内存池全空。接下来在调用chunk_alloc(96,20),此时free_list[11]空空如也。
必须向内存要求支持。而内存池此时也是空的。于是以malloc()配置40+n(附加量)个9bytes区块。当中第1个交出,另外19个交给free_list[11]维护。剩余20+n(附加量)个区块留给内存