基于SGI版本的标准模板库中的空间配置器,我进行了简单的模拟,实现了一个类似空间配置器的内存池,接下来我先说说什么是空间配置器。
为什么会有内存池?
1.反复的申请小块内存,会将一大块内存切断,因为申请的不是连续空间,导致内存碎片增多,可能会出现内存足,缺不连续,无法申请大块空间问题。
2.寻找空间分配给用户,系统也做了很多判断和事情,而且malloc申请空间会带有附加信息保存申请空间大小,反复频繁的申请小块内存,会将事情复杂化,所以标准模板库中给出了空间配置器,实现了内存池来管理小块内存的申请和释放。
一级空间配置器的执行流程:调用函数(malloc)申请内存,堆内存足够申请成功直接返回起始地址,内存不足导致失败,则调用内存不足处理函数oom_malloc,然后判断_malloc_alloc_oom_handler是否为0,如果为0则证明没有实现内存不足时,释放内存的机制,直接抛出异常,如果设置了释放内存函数机制,就循环调用((*_mallic_alloc_oom_handler)())直到分配成功为止,设计难度较大。一级空间配置器实际上是利用malloc和free来实现的。
二级空间配置器是维护一个自由链表,自由链表是由一个指针数组来实现控制的,指针数组中有16个元素,首元素从0挂8字节链表,一直到15挂128字节的链表,每个元素都挂的8的倍数个字节的链表,即n*8;还有一个内存池,用首尾两个指针来维护。
自由链表中最小的都是8字节的空间,在64位系统下也足够存放一个地址,所以我们可以利用前几个字节来存放下一个结点的地址来把链表串起来,因为自由链表下的空间不一定都是连续的,所以必须分配一个地址的大小来存放下一节点的地址。
二级空间配置器的执行流程:因为申请空间默认是二级空间配置器,所以我们首先要进行判断申请字节数是否大于128,如果大于128则调用一级空间配置器,否则就将申请的空间替换为8的倍数,(提升),然后对其自由链表下进行判断有没有剩余的空闲结点,有就摘取下来返回,没有就调用填充自由链表函数,填充链表函数,如果内存池中有足够大的内存足够分配n*nobjs字节,则直接填充到自由链表下返回,并更新内存池,如果内存池中没有足够多的空间但是至少有一块n个字节则能填充多少就填充多少,返回并更新内存池,内存池中内存不足,就向系统申请填充内存池,系统调用失败则返回自由链表中寻找更大的内存块有没有空闲的分配给他,此时还没有就调用1级空间配置器,内存不足机制在一级空间配置器已经实现,具体实现代码如下;
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
typedef void(*HANDLER_FUNC) ();
template<int inst>
class _MallocAllocTemplate
{
private:
static HANDLER_FUNC __malloc_alloc_oom_handler;
public:
static void*OOM_handler(size_t n) {
void *result = NULL;
while (1)
{
if (0 == __malloc_alloc_oom_handler)
{
throw bad_alloc();
}
__malloc_alloc_oom_handler();
result = malloc(n);
if (result)
break;
}
return result;
}
static void * Allocate(size_t n)
{
void *result = malloc(n);//开空间
if (0 == result)//表明开空间失败
result = OOM_handler(n);
return result;
}
//释放空间
static void Deallocate(void *p, size_t n)
{
free(p);
}
HANDLER_FUNC SetMallocHandler(HANDLER_FUNC f)
{
HANDLER_FUNC old = f;
__malloc_alloc_oom_handler = f;
return old;
}
};
template<int inst>
HANDLER_FUNC _MallocAllocTemplate<inst>::__malloc_alloc_oom_handler = 0;
union Obj//free_list节点的结构
{
union Obj*FreeListLink;//自由链表下的内存块
char client_data[1];
};
//二级空间适配器的实现:
template<bool threads, int inst>
class _DefaultAllocTemplate
{
enum{ __ALIGN = 8 };
enum{ __MAX_BYTES = 128 };//小区快的大小
enum{ __NFREELISTS = __MAX_BYTES / __ALIGN };//free_list个数
union Obj//free_list节点的结构
{
union Obj*FreeListLink;//自由链表下的内存块
char client_data[1];
};
static Obj*FreeList[__NFREELISTS];//自由链表节点的下标从0开始
//chunk alloction state//内存块的状态
static char *_StartFree;//表明内存池的起始位置
static char *_EndFree;//内存池结束的位置
static size_t _HeapSize;//反馈向系统申请字节大小
static size_t FREELIST_INDEX(size_t bytes)
{
return (((bytes)+__ALIGN - 1) / __ALIGN - 1);//对齐到8
}
//将bytes上调到8的倍数
static size_t RoundUp(size_t bytes)
{
return (((bytes)+__ALIGN - 1)&~(__ALIGN - 1));
}
//表明从内存池中取空间给free_list
static char*ChunkAlloc(size_t size, size_t &nobjs)
{
size_t TotalBytes = size*nobjs;//表明总共需要需要的内存的大小
size_t BytesLeft = _EndFree - _StartFree;//表明内存池中剩余的内存的多少
//表明剩余的内存完全满足需求
if (BytesLeft >= TotalBytes)
{
char *ret = _StartFree;
_StartFree += TotalBytes;
return ret;
}
//表明内存池的大小不能满足需求量但是可能满足一个块的大小
else if (BytesLeft >= size)
{
nobjs = BytesLeft / size;
TotalBytes = size*nobjs;
char *ret = _StartFree;
_StartFree += TotalBytes;
return ret;
}
else
{
//表明内存池的大小连一个块的大小都提供不了;
size_t BytesToGet = 2 * TotalBytes + RoundUp(_HeapSize >> 4);
if (BytesLeft > 0)
{
size_t index = FREELIST_INDEX(BytesLeft);
//表明将内存池的剩余的内存配给自由链表上
((Obj*)_StartFree)->FreeListLink = FreeList[index];
FreeList[index] = (Obj*)_StartFree;
}
//配置heap空间,用来补充内存池
_StartFree = (char*)malloc(BytesToGet);
//表明malloc空间失败
if (_StartFree == 0)
{
//继续向后搜索free_list中剩余的内存块
for (size_t i = size; i < __NFREELISTS; ++i)
{
//free_list中还有没有被用的内存块
if (FreeList[i])
{
//释放free_list中的内存块(把内存块归还给内存池)
Obj*cur = FreeList[i];
FreeList[i] = cur->FreeListLink;
_StartFree = (char *)cur;
_EndFree = _StartFree + (i + 1)*__ALIGN;
return ChunkAlloc(size, nobjs);
}
}
//表明free_list中也没有剩余的内存块
_EndFree = 0; //(如果_EndFree不为0,下一次从内存池中取,就算抛异常以为会有内存,分配使用时程序崩溃)
//此时需要调用一级空间配置器
_StartFree = (char *)_MallocAllocTemplate<inst>::Allocate(BytesToGet);
}
_HeapSize += BytesToGet;//从系统分配的总字节数(便于下次调用时使用)
_EndFree = _StartFree + BytesToGet;
return ChunkAlloc(size, nobjs);//递归调用,为了修正nobjs
}
}
//表明从内存池上拿内存块填充自由链表
static void *Refill(size_t size)
{
size_t nobjs = 20;//申请取20个内存块
char *chunk = ChunkAlloc(size, nobjs);//表明将内存块作为free_list的新节点
//如果只获得一个内存块,就将这个内存块直接个调用者用,free_list没有新节点
if (nobjs == 1)
{
return chunk;
}
//调整free_list.将新节点链上自由链表中
size_t index = FREELIST_INDEX(size);
for (size_t i = 1; i < nobjs; ++i)
{
Obj*obj = (Obj*)(chunk + i*size);
obj->FreeListLink = FreeList[index];
FreeList[index] = obj;
}
return chunk;
}
public:
//二级空间配置器的内存的申请
//超过128字节表明大块内存需要到内存池里面申请空间
static void *Allocate(size_t n)
{
void *ret = NULL;
//大于128个字节调用一级空间配置器
if (n >__MAX_BYTES)
{
//调用一级空间配置器
return _MallocAllocTemplate<inst>::Allocate(n);
}
size_t index = FREELIST_INDEX(n);
if (FreeList[index])//自由链表有内存块
{
Obj *ret = FreeList[index];
FreeList[index] = ret->FreeListLink;
return ret;
}
else
{
//调用refill从内存池填充自由链表,并返回内存池的第一个内存块
size_t bytes = RoundUp(n);
return Refill(bytes);
}
return ret;
}
//空间释放
static void Dellocate(void *p, size_t n)
{
if (p == NULL)
{
return;
}
//表明大于128字节就调用一级空间配置器
if (n > __MAX_BYTES)
{
return _MallocAllocTemplate<inst>::Deallocate(p, n);
}
else
{
//放回free_list中
size_t index = FREELIST_INDEX(n);
((Obj*)p)->FreeListLink = FreeList[index];
FreeList[index] = (Obj*)p;
}
}
};
//静态成员变量需要在类外面进行初始化
template<bool threads, int inst>
typename _DefaultAllocTemplate<threads, inst>::Obj*
_DefaultAllocTemplate<threads, inst>::FreeList[__NFREELISTS] = { 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;