之前对于配置器的原理及一级配置器的介绍请看博文
这里写链接内容
下来我们直接介绍二级空间配置器。
二级空间配置器
我们通过之前的学习,已经知道。
如果所要申请的空间大于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