之前对于配置器的原理及一级配置器的介绍请看博文
这里写链接内容
下来我们直接介绍二级空间配置器。
二级空间配置器
我们通过之前的学习,已经知道。
如果所要申请的空间大于128字节,则直接交至一级空间配置器处理,如果小于128字节,则使用二级空间配置器。
二级空间配置器对内存的管理减少了小区块造成的内存碎片,它主要通过一个链表管理:
它是用一个16(128/8)个元素的自由链表来管理的,每个位置下挂载着大小为8的倍数个字节(分别为8、16、24、32、48、56、64、72、80、88、96、104、112、120、128字节),每次将所需内存提升到8的倍数。
free_list它的节点结构为一个共用体:
union obj
{
union obj * free_list_link;
char client_data[1];
};
二级空间配置器结构如下图所示:
如图所示:
在链表中空间没有分配出去之前,该空间中一直存放的是所指向的下一个节点的地址,当该空间分配出去时,让对应的my_free_list指向它的下一个节点,然后将它内部的值改为要放入用户的data值。
如何分配内存
它的内存分配主要分为以下几种情况:
1、对应的free_list不为空
所需空间大小提升为8的倍数后(如需要13bytes空间,我们会给它分配16bytes大小),所对应的free_list不为空时,它会像上图所示那样直接从对应的free_list中拔出,第一位置向后移动指向。
2、对应的free_list为空,其内存池不为空时:
(1)先检验它剩余空间是否够20个节点大小(即所需内存大小(提升后) * 20),若足够则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下,这样下次再有相同大小的内存需求时,可直接从 free-list 中拨出。
(2)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中。
(3)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中(找到相应的free_list),然后再给内存池申请内存。
3、内存池为空,申请内存
此时二级空间配置器会使用malloc()从heap上申请内存,(一次所申请的内存大小为2 * 所需节点内存大小(提升后)* 20 + 一段额外空间)。
4、malloc没有成功
在第三种情况下,如果malloc()失败了,说明heap上没有足够空间分配给我们了,这时,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从任意一个比它所需节点空间大的free_list中拔除一个节点来使用。
5、查找失败,调用一级空间配置器
第四种情况下,如果查找失败了,说明比其大的free_list中都没有自由区块了,此时会调动一级空间配置器oom_allocate()。
(一级空间配置器文首给出了博文链接)
(内存池的概念请看博文这里写链接内容)
部分源码分析
按照上面内存分配的步骤,我们一步步来看:
/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t _S_round_up(size_t __bytes)
{
return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
}
/*返回 __bytes 大小的小额区块位于 free-list 中的编号*/
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
}
根据用户所需内存块大小,申请内存:
static void* allocate(size_t __n) //分配大小为 __n 的区块
{
void* __ret = 0;
if (__n > (size_t)_MAX_BYTES) {
__ret = malloc_alloc::allocate(__n); //大于128 bytes 就调用第一级配置器
}
else { //__n 大小区块对应的位置:free-lists 首地址 + __n 位于free-lists 中的编号
_Obj* __STL_VOLATILE* __my_free_list //这里是二级指针,便于调整 free-lists
= _S_free_list + _S_freelist_index(__n);
# ifndef _NOTHREADS
_Lock __lock_instance;
# endif
_Obj* __RESTRICT __result = *__my_free_list; //将对应位置的区块拨出(第一个)
if (__result == 0) //如果 free-lists 中没有对应大小的区块
__ret = _S_refill(_S_round_up(__n)); //调用 _S_refill()
else {
*__my_free_list = __result->_M_free_list_link;//这个结构有点类似链式哈希表结构,这里是指向下一块空闲内存块
//二级指针调整 free-lists,拨出去的区块就不属于该链表了
__ret = __result;
}
}
return __ret;
};
如果 free-list 中没有对应大小的区块,转去调用 _S_refill():
template <bool __threads, int __inst>
void* //重新填充__n大小的区块进 free-list
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
int __nobjs = 20; //缺省取得 20 个新区块
char* __chunk = _S_chunk_alloc(__n, __nobjs); //调用_S_chunk_alloc()
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;
/*如果只获得一个新区块,直接划给用户,free-list 仍然无新节点*/
if (1 == __nobjs) return(__chunk);
__my_free_list = _S_free_list + _S_freelist_index(__n);
__result = (_Obj*)__chunk; //这一块返回给客端(分配出来的第一块)
*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
/*接下来的区块(拨出去了__n大小给用户)填补进 free-list*/
for (__i = 1;; __i++) {
__current_obj = __next_obj;
__next_obj = (_Obj*)((char*)__next_obj + __n);
/*将分配的连续大块"分割"成__n bytes大小的区块*/
if (__nobjs - 1 == __i) { //如果新区块填补完毕
__current_obj->_M_free_list_link = 0; //free-list 最后位置指向0
break;
}
else {//把_M_free_list_link当做链表的 next 指针理解
__current_obj->_M_free_list_link = __next_obj; //将各节点串联起来填进空闲链表
}
}
return(__result);
}
给对应的哈希桶申请空间:
static char* _S_start_free; //内存池起始位置
static char* _S_end_free; //内存池末端位置
static size_t _S_heap_size; //堆空间容量
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
int& __nobjs)
{
char* __result;
size_t __total_bytes = __size * __nobjs; //需要内存的总额大小
size_t __bytes_left = _S_end_free - _S_start_free; //内存池中还剩余多少可用内存
if (__bytes_left >= __total_bytes) { //剩余可用量大于需求量,直接划分出去
__result = _S_start_free; //内存池的首地址
_S_start_free += __total_bytes; //调整内存池位置
return(__result);
}
//内存池剩余空间不能完全满足需求,但至少可供应一个及以上的区块
else if (__bytes_left >= __size) {
__nobjs = (int)(__bytes_left / __size); //调整划分个数,划分出去最大量
__total_bytes = __size * __nobjs; //同上
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
}
else { //内存池剩余空间连一个区块都无法提供
size_t __bytes_to_get =//配置大小为总需求量的两倍再加上一个随配置次数逐渐增加的附加量
2 * __total_bytes + _S_round_up(_S_heap_size >> 4); /
if (__bytes_left > 0) { //充分利用内存池中的剩余空间
_Obj* __STL_VOLATILE* __my_free_list = //剩余空间寻找适当的free-list
_S_free_list + _S_freelist_index(__bytes_left);
//调整 free-list,将内存池中残余空间编入 free-list 对应位置中
((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;
*__my_free_list = (_Obj*)_S_start_free;
}
_S_start_free = (char*)malloc(__bytes_to_get); //配置heap空间,用来补充内存池
if (0 == _S_start_free) { //system heap 空间不足,分配失败
size_t __i;
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __p;
for (__i = __size; //起始大小为需求区块大小
__i <= (size_t)_MAX_BYTES;
__i += (size_t)_ALIGN) { //以 8 为步长搜寻整个 free-list
__my_free_list = _S_free_list + _S_freelist_index(__i); //找到 __i大小区块在free-list 中的位置
__p = *__my_free_list;
if (0 != __p) { //如果 free-list 中该区块未使用
*__my_free_list = __p->_M_free_list_link; //调整 free-list,释放第一位置块
_S_start_free = (char*)__p; //编入内存池
_S_end_free = _S_start_free + __i;
return(_S_chunk_alloc(__size, __nobjs)); //递归调用
/*该for循环的作用就是从 free-list 中划出大于需求区块(单个)的未用空间区块到内存池,然后再从内存池中取出。
由于从大于__size 的区块开始搜寻,所以如果 free-list 中搜寻到,那么只需动用该搜寻区块的第一位置区块即可,
最后取出的空间也可能是单个区块,也可能是多个区块(取决于 free-list 未用区块的最小区块(大于__size)的大小)*/
}
}
_S_end_free = 0; //表示到处无内存可用,for循环中free-list没有搜寻到适当的未用空间
_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); //调用第一级配置器
/*要么内存不足的问题获得改善,要么抛出 bad_alloc 异常*/
}
_S_heap_size += __bytes_to_get; //调用第一级配置器后,内存不足问题获得改善,调整堆空间
_S_end_free = _S_start_free + __bytes_to_get; //编入内存池
return(_S_chunk_alloc(__size, __nobjs)); //重新调用 _S_chunk_alloc()
}
}
释放空间
当用户从二级空间配置器中申请的内存被释放时,要将其回收然后插入对应的free_list:
/*空间释放后被编入 free-list*/
static void deallocate(void* __p, size_t __n)
{
if (__n > (size_t)_MAX_BYTES) //大于 128 就调用第一级配置器
malloc_alloc::deallocate(__p, __n);
else {
_Obj* __STL_VOLATILE* __my_free_list //定位到对应的 free-list
= _S_free_list + _S_freelist_index(__n);
_Obj* __q = (_Obj*)__p;
# ifndef _NOTHREADS
_Lock __lock_instance;
# endif
__q->_M_free_list_link = *__my_free_list; //调整 free-list 回收区块
*__my_free_list = __q; //回收的区块是挂接到free-list 对应区块的第一位置,而不是添加到尾端
}
}
模拟实现
我们测试时加入一级空间配置器的代码:
//一级空间配置器 >128
//函数指针
typedef void(*pMallocHandle)();
template<int inst>
class MallocAllocTemplate
{
public:
static void* Allocate(size_t size)
{
void* result = malloc(size);
//申请失败调用OOM_Malloc()函数
if (NULL == result)
{
result = OOM_Malloc(size);
}
__TRACE_DEBUG("一级空间配置器:%d\n", size);
return result;
}
//重新分配空间
static void* ReAllocate(void* p, size_t, size_t newSize) //三个参数
{
void* result = realloc(p, newSize);
if (NULL == result)
{
result = OOM_Realloc(p, newSize);
}
return result;
}
static void DeAllocate(void* p, size_t) //底层是两个参数
{
__TRACE_DEBUG("一级释放\n");
free(p);
}
private:
static void* OOM_Malloc(size_t size)
{
pMallocHandle mallocHandle;
void* res;
for (;;)
{
mallocHandle = _mallocHandle;
if (NULL == mallocHandle)
throw std::bad_alloc();
//尝试去释放已经获取,但是不用的堆空间
mallocHandle();
//尝试重新分配内存
res = malloc(size);
if (res)
return res;
}
}
static void* OOM_Realloc(void *p, size_t size)
{
pMallocHandle mallocHandle;
void* res;
__TRACE_DEBUG("一级:第一次申请失败,OOM处理:%d\n", size);
for (;;)
{
mallocHandle = _mallocHandle;
if (NULL == mallocHandle)
throw std::bad_alloc();
//尝试去释放已经获取但是不用的堆空间
mallocHandle();
res = realloc(p, size);
if (res)
return res;
}
}
//若申请失败,释放方法
static pMallocHandle SetMallocHandle(pMallocHandle mallocHandle)
{
pMallocHandle old = _mallocHandle;
_mallocHandle = mallocHandle;
return old;
}
private:
static pMallocHandle _mallocHandle;
};
pMallocHandle MallocAllocTemplate<0>::_mallocHandle = NULL;
根据上面的分析,二级空间配置器的代码如下:
//<=128个字节
//交给二级配置器
//小的内存块,减少频繁的向系统malloc
template<int inst>
class DefaultAllocateTemplate
{
public:
//申请内存
static void* Allocate(size_t size)
{
if (size > 128)
return MallocAllocTemplate<0>::Allocate(size);
__TRACE_DEBUG("二级空间配置器:%d\n", size);
size_t index = FreeListIndex(size); //求出对应的索引
if (_freeList[index] == NULL) //链表没有,从内存池申请填充
{
__TRACE_DEBUG("二级:%d桶中(%d)没有可用空间,需要内存池补充\n", index, size);
return ReFill(ROUND_UP(size));
}
//将对应哈希桶中链表中第一块拨出给用户
void* result = _freeList[index];
_freeList[index] = _freeList[index]->_freeListLink;
return result;
}
//释放内存
static void DeAllocate(void *ptr, size_t size)
{
if (size > 128)
return MallocAllocTemplate<0>::DeAllocate(ptr, size);
__TRACE_DEBUG("二级空间配置器释放:%d\n", size);
size_t index = FreeListIndex(size);
((OBJ*)ptr)->_freeListLink = _freeList[index];
_freeList[index] = (OBJ*)ptr;
}
private:
//向上对齐为8的整数倍
static size_t ROUND_UP(size_t bytes)
{
return (((bytes)+_ALIGN - 1)&~(_ALIGN - 1));
}
//bytes对应的哈希桶的下标
static size_t FreeListIndex(size_t bytes)
{
return (((bytes)+_ALIGN - 1) / _ALIGN - 1);
}
//给对应的桶里填充内存块
static void* ReFill(size_t size)
{
size_t nobjs = 20;
char* chunk = (char*)ChunkAlloc(size, nobjs);
if (nobjs == 1) //只有一个直接给用户
return chunk;
size_t index = FreeListIndex(size);
//将剩下的内存块管理起来,链接在对应的桶里
OBJ* cur = (OBJ*)(chunk + size);
__TRACE_DEBUG("二级:内存池补充%d个小块内存\n", nobjs);
while (--nobjs)
{
cur->_freeListLink = _freeList[index]; //头插每个size大小的内存块
_freeList[index] = cur;
cur = (OBJ*)((char*)cur + size);
}
return chunk;
}
//给桶申请空间
static void* ChunkAlloc(size_t size, size_t& nobjs)
{
size_t TotalBytes = size*nobjs;
size_t LeftBytes = _endFree - _startFree; //内存池剩余空间
char *result;
//内存池空间足够 nobjs=20
if (LeftBytes >= TotalBytes)
{
__TRACE_DEBUG("二级:内存池可以提供20个%d字节的内存块\n", size)
result = _startFree;
_startFree += TotalBytes;
return result;
}
//小于需要的20块,只能提供至少一块内存 1<=nobjs<20
else if (LeftBytes >= size)
{
nobjs = LeftBytes / size;
__TRACE_DEBUG("二级:内存池不太够,可以提供%d个%d字节的内存块\n", nobjs, size);
result = _startFree;
_startFree += nobjs*size;
return result;
}
//一块内存都不够,给内存池补充空间
else
{
__TRACE_DEBUG("二级:内存池一个%d字节的小块内存都不能提供\n", size);
//1.将内存池剩余的空间挂到对应的链表中
size_t index = FreeListIndex(LeftBytes);
if (LeftBytes > 0)
{
OBJ* cur = (OBJ*)_startFree;
cur->_freeListLink = _freeList[index];
_freeList[index] = cur;
}
//2.在堆中给内存池申请空间
size_t Getbytes = 2 * TotalBytes + ROUND_UP(_heapSize >> 4); //系统堆给内存池分配空间的大小
__TRACE_DEBUG("二级:内存池不够了,需要在堆申请%d字节的内存块\n", Getbytes);
_startFree = (char*)malloc(Getbytes);
//2.1申请失败
if (_startFree == NULL)
{
__TRACE_DEBUG("二级:内存池不够且在堆中申请失败,在链表中找更大的内存块,%d字节\n", (index + 1)*_ALIGN);
//在二级空间配置器中找更大的内存块
size_t index = FreeListIndex(size);
for (int i = index; i < _NFREELISTS; i++)
{
OBJ* Cur = _freeList[index];
if (Cur != NULL)
{
_startFree = (char*)Cur;
_freeList[index] = Cur->_freeListLink;
_endFree = _startFree + (index + 1)*_ALIGN;
return ChunkAlloc(size, nobjs);
}
}
__TRACE_DEBUG("二级:山穷水尽了,向一级空间配置器申请,%d字节的内存块\n", Getbytes);
_endFree = NULL; //避免异常,置空
_startFree = (char*)MallocAllocTemplate<0>::Allocate(Getbytes);
}
//2.2在堆中申请成功
__TRACE_DEBUG("二级:在堆中申请成功了%d字节的内存块\n", Getbytes);
_heapSize += Getbytes;
_endFree = _startFree + Getbytes;
return (ChunkAlloc(size, nobjs));
}
}
private:
enum { _ALIGN = 8 };
enum { _MAX_BYTES = 128 };
enum { _NFREELISTS = _MAX_BYTES / _ALIGN };
union OBJ
{
OBJ* _freeListLink;
char clientData[1]; //
};
private:
static char* _startFree; //标记内存池的起始地址
static char* _endFree; //标记内存池的结束地址
static size_t _heapSize; //之前向堆中申请的字节数
static OBJ* _freeList[_NFREELISTS]; //存放OBJ的指针数组
};
//静态成员变量需要在类外初始化
template<int inst>
char* DefaultAllocateTemplate<inst>::_startFree = NULL;
template<int inst>
char* DefaultAllocateTemplate<inst>::_endFree = NULL;
template<int inst>
size_t DefaultAllocateTemplate<inst>::_heapSize = NULL;
template<int inst>
typename DefaultAllocateTemplate<inst>::OBJ* DefaultAllocateTemplate<inst>::\
_freeList[DefaultAllocateTemplate<inst>::_NFREELISTS] = { 0 };
为了方便测试,我们将空间配置器封装为一个类:
//给空间配置器起别名
#ifdef USE_MALLOC
typedef MallocAllocTemplate<0> _Alloc;
#else
typedef DefaultAllocateTemplate<0> _Alloc;
#endif
template <class T, class Alloc>
class SimpleAllocate
{
public:
static void* Allocate(size_t n) //申请n个类型为T的字节大小
{
return (0 == n) ? 0 : Alloc::Allocate(n*sizeof(T));
}
static void* Allocate(void) //申请T字节大小
{
return (0 == n) ? 0 : Alloc::Allocate(sizeof(T));
}
static void DeAllocate(void* p, size_t n)
{
Alloc::DeAllocate(p, n*sizeof(T));
}
static void deallocate(void *p)
{
Alloc::Deallocate(p, sizeof(T));
}
};
我们用一个特殊的测试函数测试,上述代码中有很多__TRACE_DEBUG包含的信息,这样测试方便我们知道代码的运行及数据的变化是否正确:
#define _DEBUG_
static string GetFileName(const string& path)
{
char ch = '/';
#ifdef _WIN32
ch = '\\';
#endif
size_t pos = path.rfind(ch);
if (pos == string::npos)
return path;
else
return path.substr(pos + 1);
}
//用于调试追踪的trace log
inline static void _trace_debug(const char * funcName, const char * fileName, int line, char* format, ...)
{
#ifdef _DEBUG_
fprintf(stdout, "[%s:%d]%s", GetFileName(fileName).c_str(), line, funcName);
// 输出用户信息
va_list args;
va_start(args, format);
vfprintf(stdout, format, args);
va_end(args);
#endif
}
#define __TRACE_DEBUG(...) _trace_debug(__FUNCDNAME__, __FILE__, __LINE__, __VA_ARGS__);
测试:
//测试
void TestAlloc()
{
//一级
int *p1 = (int*)SimpleAllocate<int, _Alloc>::Allocate(100);
SimpleAllocate<int, _Alloc>::DeAllocate(p1, 100);
//二级
p1 = (int*)SimpleAllocate<int, _Alloc>::Allocate(2);
SimpleAllocate<int, _Alloc>::DeAllocate(p1, 2);
p1 = (int*)SimpleAllocate<int, _Alloc>::Allocate(5); //内存池--->16
int *p2 = (int*)SimpleAllocate <int, _Alloc>::Allocate(10);
SimpleAllocate<int, _Alloc>::DeAllocate(p1, 5);
SimpleAllocate<int, _Alloc>::DeAllocate(p2, 10);
}
观察结果:
读者可以自行计算一下。