对于c++用户来说,new和delete很熟悉,这两个函数可以分别完成内存的申请和释放,和c里面的malloc和free如出一辙,SGI 有一个标准空间适配器,同时还有一个特殊空间适配器,标准空间适配器为:std::allocator,这个适配器只是对new和delete的浅层包装,所以没有什么技术含量,所以在SGI中从没使用过这个标准适配器。另一个空间适配器是std:alloc,这是一个具有次分配能力的特殊空间适配器,它具有一级和二级适配器,它们协调工作。
Std::alloc的主要思想是:定义一个空间大小阈值,128bytes,如果申请的空间大于128bytes,那么就调用第一级空间适配器来完成分配工作,如果小于128bytes,那么就调用第二级空间适配器来完成。对于第一级适配器,直接调用malloc和free来完成分配与释放内存的工作(没有调用new和delete),最为重要的是,第一级适配器具有new-handle机制,用户可以指定当出现out-of-memory时的处理函数,在SGI里面,当第一级alloc失败时,会接着调用oom_alloc函数来尝试分配内存,如果oom发现没有指定new-handler函数的话,那就无能为力了!会抛出__THROW_BAD_ALLOC这个异常。
其中:这里所谓的两级并没有高低之分,它们之间的区别就是看你想要申请内存空间的大小。如果申请的内存大小超过128,那么空间配置器就自动调用一级空间配置器。反之调用二级空间配置器。而且在这里要说明的是空间配置器默认使用的是一级空间配置器。
static void * allocate(size_t n)
{
void *result = malloc(n);//直接使用第一级分配器,直接使用malloc
if (0 == result)
result = oom_malloc(n);//第一级分配器失效了,那就使 用oom(out of memeory)
return result;//将分配的空间以void*的方式返回,用户可以随意转化为需要的类型
}
一级空间配置器就比较简单了,STL源码中的一级空间配置器命名为class __malloc_alloc_template ,它很简单,就是对malloc,free,realloc等系统分配函数的一层封装。
下面是仿照new_handle做的一个oom_malloc
template
void * __malloc_alloc_template::oom_malloc(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 = malloc(n);
//分配内存
if (result)
return(result);
//如果内存已经得到满足的话,那么就可以返回了,如果不满足,那么 //就继续释放,分配....
}
}
其中的__malloc_alloc_oom_handler就是由用户设定的new-handler,我们可以通过下面的函数来设定
这个句柄:
static void (* set_malloc_handler(void (*f)()))()
{
void (* old)() = __malloc_alloc_oom_handler;//old handler of outing of memory
__malloc_alloc_oom_handler = f;//new handler for hander the out of memory
return(old);
}
- <span style="font-family:Microsoft YaHei;font-size:14px;">template<bool threads,int inst>
- class DefaultAllocTemplate//二级空间配置器
- {
- private:
- enum{ ALIGN = 8 };
- enum{ MAX_BYTES = 128 };
- enum{ FREELISTSIZE = MAX_BYTES / ALIGN };
- public:
- static void* Allocate(size_t n)
- {
- if (n > MAX_BYTES)
- {
- return MallocAllocTemplate<inst>::Allocate(n);
- }
- void* ret = NULL;
- size_t index = GetFreeListIndex(n);
- if (FreeList[index])//自由链表上有内存块
- {
- obj* cur = FreeList[index];
- ret = cur;
- FreeList[index] = cur->listLink;
- }
- else //调用refill从内存池填充自由链表并返回内存池的第一个内存块
- {
- size_t bytes = GetRoundUpNum(n);
- return Refill(bytes);
- }
- return ret;
- }
- static void* Reallocate(void* p, size_t oldsize, size_t newsize)
- {
- void* ret = NULL;
- if (oldsize > (size_t)MAX_BYTES&&newsize > (size_t)MAX_BYTES)
- return (realloc(p, newsize));
- if (GetRoundUpNum(oldsize) == GetRoundUpNum(newsize))
- return p;
- ret = Allocate(newsize);
- size_t copysize = oldsize > newsize ? newsize : oldsize;
- memcopy(ret, p, copysize);
- DeAllocate(p, oldsize);
- return ret;
- }
- static void Deallocate(void* p, size_t n)
- {
- if (n > MAX_BYTES)//如果大于MAX_BYTES直接交还给一级空间配置器释放
- return MallocAllocTemplate<inst>::Deallocate(p, n);
- else//放回二级空间配置器的自由链表
- {
- size_t index = GetFreeListIndex(n);
- obj* tmp = (obj*)p;
- tmp->listLink = FreeList[index];
- Freelist[index] = tmp;
- }
- }
- public:
- union obj
- {
- union obj* listLink;//自由链表中指向下一个内存快的指针
- char clientData[1];//调试用
- };
- static size_t GetFreeListIndex(size_t bytes)//得到所需内存块在自由链表中的下标
- {
- return ((bytes + ALIGN - 1) / ALIGN - 1);
- }
- static size_t GetRoundUpNum(size_t bytes)//得到内存块大小的向上对齐数
- {
- return (bytes + ALIGN - 1)&~(ALIGN - 1);
- }
- static void* Refill(size_t n)//从内存池拿出内存填充自由链表
- {
- int nobjs = 20;//申请20个n大小的内存块
- char* chunk = ChunkAlloc(n, nobjs);
- if (nobj == 1)//只分配到一个内存
- {
- return chunk;
- }
- obj* ret = NULL;
- obj* cur = NULL;
- size_t index = GetFreeListIndex(n);
- ret = (obj*)chunk;
- cur = (obj*)(chunk + n);
- //将nobj-2个内存块挂到自由链表上
- FreeList[index] = cur;
- for (int i = 2; i < nobjs; ++i)
- {
- cur->listLink = (obj*)(chunk + n*i);
- cur = cur->listLink;
- }
- cur->listLink = NULL;
- return ret;
- }
- static char* ChunkAlloc(size_t size, int& nobjs)
- {
- char* ret = NULL;
- size_t Leftbytes = endFree - startFree;
- size_t Needbytes = size * nobjs;
- if (Leftbytes >= Needbytes)
- {
- ret = startFree;
- startFree += Needbytes;
- }
- else if (Leftbytes >= size)//至少能分配到uoge内存块
- {
- ret = startFree;
- nobjs = Leftbytes / size;
- startFree += nobjs*size;
- }
- else //一个内存块都分配不出来
- {
- if (Leftbytes > 0)
- {
- size_t index = GetFreeListIndex(Leftbytes);
- ((obj*)startFree)->listLink = FreeList[index];
- FreeList[index] = (obj*)startFree;
- startFree = NULL;
- }
- //向操作系统申请2倍Needbytes加上已分配的heapsize/8的内存到内存池
- size_t getBytes = 2 * Needbytes + GetRoundUpNum(heapSize >> 4);
- startFree = (char*)malloc(getBytes);
- if (startFree == NULL)//从系统堆中分配内存失败
- {
- for (int i = size; i < MAX_BYTES; i += ALIGN)
- {
- obj* head = FreeList[GetFreeListIndex(i)];
- if (head)
- {
- startFree = (char*)head;
- head = head->listLink;
- endFree = startFree + i;
- return ChunkAlloc(size, nobjs);
- }
- }
- //最后的一根救命稻草,找一级空间配置器分配内存
- //(其他进程归还内存,调用自定义的句柄处理函数释放内存)
- startFree = MallocAllocTemplate<inst>::Allocate(getBytes);
- }
- heapSize += getBytes;//从系统堆分配的总字节数(可以用于下次分配时进行调节)
- endFree = startFree + getBytes;
- return ChunkAlloc(size, nobjs);//递归调用获取内存
- }
- return ret;
- }
- static obj* volatile FreeList[FREELISTSIZE];
- static char* startFree;
- static char* endFree;
- static size_t heapSize;
- };
- //typename表示DefaultAllocTemplate<threads, inst>是一个类型,
- //如果不标识,编译器对此模板一无所知
- template<bool threads, int inst>
- typename DefaultAllocTemplate<threads, inst>::obj* volatile
- DefaultAllocTemplate<threads, inst>::FreeList[FREELISTSIZE] = { 0 };
- template<bool threads, int inst>
- char* DefaultAllocTemplate<threads, inst>::startFree = 0;
- template<bool threads, int inst>
- char* DefaultAllocTemplate<threads, inst>::endFree = 0;
- template<bool threads, int inst>
- size_t DefaultAllocTemplate<threads, inst>::heapSize = 0;</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">template<bool threads,int inst>
class DefaultAllocTemplate//二级空间配置器
{
private:
enum{ ALIGN = 8 };
enum{ MAX_BYTES = 128 };
enum{ FREELISTSIZE = MAX_BYTES / ALIGN };
public:
static void* Allocate(size_t n)
{
if (n > MAX_BYTES)
{
return MallocAllocTemplate<inst>::Allocate(n);
}
void* ret = NULL;
size_t index = GetFreeListIndex(n);
if (FreeList[index])//自由链表上有内存块
{
obj* cur = FreeList[index];
ret = cur;
FreeList[index] = cur->listLink;
}
else //调用refill从内存池填充自由链表并返回内存池的第一个内存块
{
size_t bytes = GetRoundUpNum(n);
return Refill(bytes);
}
return ret;
}
static void* Reallocate(void* p, size_t oldsize, size_t newsize)
{
void* ret = NULL;
if (oldsize > (size_t)MAX_BYTES&&newsize > (size_t)MAX_BYTES)
return (realloc(p, newsize));
if (GetRoundUpNum(oldsize) == GetRoundUpNum(newsize))
return p;
ret = Allocate(newsize);
size_t copysize = oldsize > newsize ? newsize : oldsize;
memcopy(ret, p, copysize);
DeAllocate(p, oldsize);
return ret;
}
static void Deallocate(void* p, size_t n)
{
if (n > MAX_BYTES)//如果大于MAX_BYTES直接交还给一级空间配置器释放
return MallocAllocTemplate<inst>::Deallocate(p, n);
else//放回二级空间配置器的自由链表
{
size_t index = GetFreeListIndex(n);
obj* tmp = (obj*)p;
tmp->listLink = FreeList[index];
Freelist[index] = tmp;
}
}
public:
union obj
{
union obj* listLink;//自由链表中指向下一个内存快的指针
char clientData[1];//调试用
};
static size_t GetFreeListIndex(size_t bytes)//得到所需内存块在自由链表中的下标
{
return ((bytes + ALIGN - 1) / ALIGN - 1);
}
static size_t GetRoundUpNum(size_t bytes)//得到内存块大小的向上对齐数
{
return (bytes + ALIGN - 1)&~(ALIGN - 1);
}
static void* Refill(size_t n)//从内存池拿出内存填充自由链表
{
int nobjs = 20;//申请20个n大小的内存块
char* chunk = ChunkAlloc(n, nobjs);
if (nobj == 1)//只分配到一个内存
{
return chunk;
}
obj* ret = NULL;
obj* cur = NULL;
size_t index = GetFreeListIndex(n);
ret = (obj*)chunk;
cur = (obj*)(chunk + n);
//将nobj-2个内存块挂到自由链表上
FreeList[index] = cur;
for (int i = 2; i < nobjs; ++i)
{
cur->listLink = (obj*)(chunk + n*i);
cur = cur->listLink;
}
cur->listLink = NULL;
return ret;
}
static char* ChunkAlloc(size_t size, int& nobjs)
{
char* ret = NULL;
size_t Leftbytes = endFree - startFree;
size_t Needbytes = size * nobjs;
if (Leftbytes >= Needbytes)
{
ret = startFree;
startFree += Needbytes;
}
else if (Leftbytes >= size)//至少能分配到uoge内存块
{
ret = startFree;
nobjs = Leftbytes / size;
startFree += nobjs*size;
}
else //一个内存块都分配不出来
{
if (Leftbytes > 0)
{
size_t index = GetFreeListIndex(Leftbytes);
((obj*)startFree)->listLink = FreeList[index];
FreeList[index] = (obj*)startFree;
startFree = NULL;
}
//向操作系统申请2倍Needbytes加上已分配的heapsize/8的内存到内存池
size_t getBytes = 2 * Needbytes + GetRoundUpNum(heapSize >> 4);
startFree = (char*)malloc(getBytes);
if (startFree == NULL)//从系统堆中分配内存失败
{
for (int i = size; i < MAX_BYTES; i += ALIGN)
{
obj* head = FreeList[GetFreeListIndex(i)];
if (head)
{
startFree = (char*)head;
head = head->listLink;
endFree = startFree + i;
return ChunkAlloc(size, nobjs);
}
}
//最后的一根救命稻草,找一级空间配置器分配内存
//(其他进程归还内存,调用自定义的句柄处理函数释放内存)
startFree = MallocAllocTemplate<inst>::Allocate(getBytes);
}
heapSize += getBytes;//从系统堆分配的总字节数(可以用于下次分配时进行调节)
endFree = startFree + getBytes;
return ChunkAlloc(size, nobjs);//递归调用获取内存
}
return ret;
}
static obj* volatile FreeList[FREELISTSIZE];
static char* startFree;
static char* endFree;
static size_t heapSize;
};
//typename表示DefaultAllocTemplate<threads, inst>是一个类型,
//如果不标识,编译器对此模板一无所知
template<bool threads, int inst>
typename DefaultAllocTemplate<threads, inst>::obj* volatile
DefaultAllocTemplate<threads, inst>::FreeList[FREELISTSIZE] = { 0 };
template<bool threads, int inst>
char* DefaultAllocTemplate<threads, inst>::startFree = 0;
template<bool threads, int inst>
char* DefaultAllocTemplate<threads, inst>::endFree = 0;
template<bool threads, int inst>
size_t DefaultAllocTemplate<threads, inst>::heapSize = 0;</span>
这个代码是我自己提取出来的源码框架,但是他已经可以实现所有的功能。
首先需要说明的是二级空间配置器是由一个内存池和自由链表配合实现的。
- <span style="font-family:Microsoft YaHei;font-size:14px;"> static obj* volatile FreeList[FREELISTSIZE];//维护自由链表
- static char* startFree;//维护内存池
- static char* endFree;</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> static obj* volatile FreeList[FREELISTSIZE];//维护自由链表
static char* startFree;//维护内存池
static char* endFree;</span>
srartFree就相当于水位线的一种东西,它标志着内存池的大小。
自由链表中其实是一个大小为16的指针数组,间隔为8的倍数。各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104, 112,120,128 字节的小额区块。在每个下标下挂着一个链表,把同样大小的内存块链接在一起。此处特别像哈希桶。
自由链表结构:
- <span style="font-family:Microsoft YaHei;font-size:14px;"> union obj
- {
- union obj* listLink;//自由链表中指向下一个内存快的指针
- char clientData[1];//调试用
- };</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> union obj
{
union obj* listLink;//自由链表中指向下一个内存快的指针
char clientData[1];//调试用
};</span>
这个结构可以看做是从一个内存块中抠出4个字节大小来,当这个内存块空闲时,它存储了下个空闲块,当这个内存块交付给用户时,它存储的时用户的数据。因此,allocator中的空闲块链表可以表示成:
obj* free_list[16];
obj* 是4个字节那么大,但是大部分内存块大于4。我们想要做的只是将一块块内存链接起来,我们不用看到内存里所有的东西,所以我们可以只用强转为obj*就可以实现大内存块的链接。
二级空间配置器是为频繁分配小内存而生的一种算法。其实就是消除一级空间配置器的外碎片问题。
操作系统频繁分配内存和回收内存的时候。这些6M,4M的小内存无法利用造成了外部碎片。
二级空间配置器就比较复杂了,现在我们来分析他的那些重要的函数:
Allocate()中:
- <span style="font-family:Microsoft YaHei;font-size:14px;"><span style="white-space:pre"> </span>static size_t GetFreeListIndex(size_t bytes)//得到所需内存块在自由链表中的下标
- <span style="white-space:pre"> </span>{
- <span style="white-space:pre"> </span>return ((bytes + ALIGN - 1) / ALIGN - 1);
- <span style="white-space:pre"> </span>}</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"><span style="white-space:pre"> </span>static size_t GetFreeListIndex(size_t bytes)//得到所需内存块在自由链表中的下标
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>return ((bytes + ALIGN - 1) / ALIGN - 1);
<span style="white-space:pre"> </span>}</span>
此函数和源码中的FREELIST_INDEX(n)是一样的,它就是找到需要分配的内存块在自由链表中的什么地方,它的实现是((bytes + ALIGN - 1) / ALIGN - 1)。它其实是把药分配的内存大小提升一个数量级(+7,每间隔8为一个数量级),然后除以8,可以算到要找的内存块下标的下一个下标,减一,刚好久找到合适的下标处,取出一块内存块。
- <span style="font-family:Microsoft YaHei;font-size:14px;"> static size_t GetRoundUpNum(size_t bytes)//得到内存块大小的向上对齐数
- {
- return (bytes + ALIGN - 1)&~(ALIGN - 1);
- }</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> static size_t GetRoundUpNum(size_t bytes)//得到内存块大小的向上对齐数
{
return (bytes + ALIGN - 1)&~(ALIGN - 1);
}</span>
此函数是得到所需内存块大小的向上对齐数。在自由链表中,我们的内存块大小总是8的倍数,但是并不是每次所需内存大小都是8的倍数。所以我们就要取比所需大小大或相等的内存块,这就是向上取整。&~(ALIGN - 1)相当于将低8位置0,只取高8位,高8位总是8的倍数,正好符合题意。
Allocate中最重要的两个函数static void* Refill(size_t n)和static char* ChunkAlloc(size_t size, int& nobjs):
- <span style="font-family:Microsoft YaHei;font-size:14px;">static void* Refill(size_t n)//从内存池拿出内存填充自由链表
- {
- int nobjs = 20;//申请20个n大小的内存块
- char* chunk = ChunkAlloc(n, nobjs);
- if (nobj == 1)//只分配到一个内存
- {
- return chunk;
- }
- obj* ret = NULL;
- obj* cur = NULL;
- size_t index = GetFreeListIndex(n);
- ret = (obj*)chunk;
- cur = (obj*)(chunk + n);
- //将nobj-2个内存块挂到自由链表上
- FreeList[index] = cur;
- for (int i = 2; i < nobjs; ++i)
- {
- cur->listLink = (obj*)(chunk + n*i);
- cur = cur->listLink;
- }
- cur->listLink = NULL;
- return ret;
- }</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">static void* Refill(size_t n)//从内存池拿出内存填充自由链表
{
int nobjs = 20;//申请20个n大小的内存块
char* chunk = ChunkAlloc(n, nobjs);
if (nobj == 1)//只分配到一个内存
{
return chunk;
}
obj* ret = NULL;
obj* cur = NULL;
size_t index = GetFreeListIndex(n);
ret = (obj*)chunk;
cur = (obj*)(chunk + n);
//将nobj-2个内存块挂到自由链表上
FreeList[index] = cur;
for (int i = 2; i < nobjs; ++i)
{
cur->listLink = (obj*)(chunk + n*i);
cur = cur->listLink;
}
cur->listLink = NULL;
return ret;
}</span>
当在自由链表的下标处没有内存块时,我们就必须调用refill去填充自由链表。申请时一般一次性申请20个内存块大小的内存。通过移动startFree指针将内存池内的一段内存给“切割”出来,然后按照大小切成小块挂在自由链表下面。。返回第一块内存块给用户,其余的都挂在自由链表下,方便下次分配,根据局部性原理,这将极大地提升了分配内存空间的效率。
- <span style="font-family:Microsoft YaHei;font-size:14px;">static char* ChunkAlloc(size_t size, int& nobjs)
- {
- char* ret = NULL;
- size_t Leftbytes = endFree - startFree;
- size_t Needbytes = size * nobjs;
- if (Leftbytes >= Needbytes)
- {
- ret = startFree;
- startFree += Needbytes;
- }
- else if (Leftbytes >= size)//至少能分配到一个内存块
- {
- ret = startFree;
- nobjs = Leftbytes / size;
- startFree += nobjs*size;
- }
- else //一个内存块都分配不出来
- {
- if (Leftbytes > 0)
- {
- size_t index = GetFreeListIndex(Leftbytes);
- ((obj*)startFree)->listLink = FreeList[index];
- FreeList[index] = (obj*)startFree;
- startFree = NULL;
- }
- //向操作系统申请2倍Needbytes加上已分配的heapsize/8的内存到内存池
- size_t getBytes = 2 * Needbytes + GetRoundUpNum(heapSize >> 4);
- startFree = (char*)malloc(getBytes);
- if (startFree == NULL)//从系统堆中分配内存失败
- {
- for (int i = size; i < MAX_BYTES; i += ALIGN)
- {
- obj* head = FreeList[GetFreeListIndex(i)];
- if (head)
- {
- startFree = (char*)head;
- head = head->listLink;
- endFree = startFree + i;
- return ChunkAlloc(size, nobjs);
- }
- }
- //最后的一根救命稻草,找一级空间配置器分配内存
- //(其他进程归还内存,调用自定义的句柄处理函数释放内存)
- startFree = MallocAllocTemplate<inst>::Allocate(getBytes);
- }
- heapSize += getBytes;//从系统堆分配的总字节数(可以用于下次分配时进行调节)
- endFree = startFree + getBytes;
- return ChunkAlloc(size, nobjs);//递归调用获取内存
- }
- return ret;
- }</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">static char* ChunkAlloc(size_t size, int& nobjs)
{
char* ret = NULL;
size_t Leftbytes = endFree - startFree;
size_t Needbytes = size * nobjs;
if (Leftbytes >= Needbytes)
{
ret = startFree;
startFree += Needbytes;
}
else if (Leftbytes >= size)//至少能分配到一个内存块
{
ret = startFree;
nobjs = Leftbytes / size;
startFree += nobjs*size;
}
else //一个内存块都分配不出来
{
if (Leftbytes > 0)
{
size_t index = GetFreeListIndex(Leftbytes);
((obj*)startFree)->listLink = FreeList[index];
FreeList[index] = (obj*)startFree;
startFree = NULL;
}
//向操作系统申请2倍Needbytes加上已分配的heapsize/8的内存到内存池
size_t getBytes = 2 * Needbytes + GetRoundUpNum(heapSize >> 4);
startFree = (char*)malloc(getBytes);
if (startFree == NULL)//从系统堆中分配内存失败
{
for (int i = size; i < MAX_BYTES; i += ALIGN)
{
obj* head = FreeList[GetFreeListIndex(i)];
if (head)
{
startFree = (char*)head;
head = head->listLink;
endFree = startFree + i;
return ChunkAlloc(size, nobjs);
}
}
//最后的一根救命稻草,找一级空间配置器分配内存
//(其他进程归还内存,调用自定义的句柄处理函数释放内存)
startFree = MallocAllocTemplate<inst>::Allocate(getBytes);
}
heapSize += getBytes;//从系统堆分配的总字节数(可以用于下次分配时进行调节)
endFree = startFree + getBytes;
return ChunkAlloc(size, nobjs);//递归调用获取内存
}
return ret;
}</span>
ChunkAlloc要做的就是去找操作系统要内存,依次性要20个,但是我们要考虑很多情况:
- 内存池里有足够20块大的内存
- 内存池里有小于20块大于等于1块的内存大小
- 内存池里1块内存那么大都没有
- 空间配置器里所有的成员都是静态的。是为了在外面通过作用域就可以调用,而不需要构造对象。
- 空间配置器可以使用于大部分的数据结构,如List,vector等。
- 对于自由链表的初始化时特别容易错的。
template<bool threads, int inst>
typename DefaultAllocTemplate<threads, inst>::obj* volatile
DefaultAllocTemplate<threads, inst>::FreeList[FREELISTSIZE] = { 0 };
注意到typename了吗。它就为了完成一个功能,到苏编译器DefaultAllocTemplate<threads, inst>是一个类型,不然会出现如下错误:
这是难以发现的错误,编译器认识DefaultAllocTemplate<threads, inst>,报一个搞不懂的错误。