STL的内存配置器考虑到了小型的区块可能造成内存破碎问题,SGI STL 设计了双层级配置器,第一层配置器直接使用malloc() 和 free().第二层配置器则视情况采用不同的策略:但配置区块超过 128 bytes时,调用第一级配置器。当配置区块小于 128 bytes时,采用复杂的 memory pool 方式。
class __malloc_alloc_template {
private:
static void * allocate(size_t n)
{
void *result = malloc(n); //第一级配置器,直接使用malloc()
//如果内存不足,则调用内存不足处理函数oom_alloc()来申请内存
if (0 == result) result = oom_malloc(n);
return result;
}
static void deallocate(void *p, size_t /* n */)
{
free(p); //第一级配置器直接使用 free()
}
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(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; } //如果自己没有定义oom处理函数,则编译器毫不客气的抛出异常。
(*my_malloc_handler)(); //执行自定义的oom处理函数
result = malloc(n); //重新分配空间
if (result) return(result); //如果分配到了,返回指向内存的 指针
}
}
}
通过代码可以看出在第一级配置器中是用了原生的配置器malloc来分配内存,free来释放内存,当内存不足时去调用oom_malloc函数去申请内存,具体的申请函数的方法需要自己去定义。
维护了16个自由链表(free lists),负责16种小型区块的次配置能力。内存池以malloc()配置而得。如果内存不足,则调用第一级配置器
如果需求大于128kb,也转调用第一级内存配置器。
第二级配置器维护了16个free_lists的结构,每一个free_lists的结构负责管理与之相关大小的内存区块。
各自管理的大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块。
free_lists的节点结构如下:
union obj{
union obj * free_list_link; // for memory use
char client_date[1]; // for users use
};
static void * allocate(size_t n)
{
obj * __VOLATILE * my_free_list;
obj * __RESTRICT result;
//要申请的空间大于128bytes就调用第一级配置
if (n > (size_t) __MAX_BYTES) {
return(malloc_alloc::allocate(n));
}
//寻找 16 个free lists中恰当的一个
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if (result == 0) {
//没找到可用的free list,准备新填充free list
void *r = refill(ROUND_UP(n));
return r;
}
*my_free_list = result -> free_list_link;
return (result);
};
从代码中,可以看出分为三种情况。
第一种,申请空间大于128bytes,则调用第一级配置器分配;
第二种,free_list中内存区块不足,需要从内存池中申请内存区块。
第三种,从free_list中获取合适的区块。
第一种情况就是直接调用默认的第一级配置器。
第二种情况是通过refill函数实现,下面会具体展开讲述。
第三种情况则是通过链表来实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZwnBdPv3-1590139655521)(C:\Users\andyhkmo\Desktop\学习笔记\photo\STL_allocator.png)]
static void deallocate(void *p, size_t n)
{
obj *q = (obj *)p;
obj * __VOLATILE * my_free_list;
//如果要释放的字节数大于128,则调第一级配置器
if (n > (size_t) __MAX_BYTES) {
malloc_alloc::deallocate(p, n);
return;
}
//寻找对应的位置
my_free_list = free_list + FREELIST_INDEX(n);
//以下两步将待释放的块加到链表上
q -> free_list_link = *my_free_list;
*my_free_list = q;
}
从代码可以看到,施放过程比较简单,如果大于128bytes,调用第一级配置器free接口释放内存。
反之,把该区块回收到链表中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mh9i2WZm-1590139655525)(C:\Users\andyhkmo\Desktop\学习笔记\photo\STL_allocator1.png)]
在刚刚阐述的allocator分配内存操作中,当链表中不足以分配内存时,则需要通过refill操作去向内存池申请内存。而refill操作主要通过chunk_alloc去实现从内存池中取空间给free_list使用。
// 从内存池取出空间给free_list使用(第n号)(假定size已经适当调整为8的倍数)
char *alloc::chunk_alloc(size_t size, int &n_objs)
{
char *result;
size_t total_bytes = size * n_objs; // 需要的内存总量
size_t bytes_left = end_free - start_free; // 内存池剩余空间
if (bytes_left >= total_bytes) {
// 内存池剩余空间空间完全满足需要
result = start_free;
/* 更新start_free指针 */
start_free += total_bytes;
return result;
} else if (bytes_left >= size) {
// 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块
n_objs = bytes_left / size; // 计算可以供应几个区块
/* 更新start_free指针 */
total_bytes = n_objs * size;
result = start_free;
start_free += total_bytes;
return result;
} else {
// 内存池剩余空间连一个区块的大小都不满足
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
// 试着让内存池中残余零头还有利用价值
if ( bytes_left > 0 ) {
// 将内存池残余内存配给适当的free_list(更小的size的list);
obj **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);
if (nullptr == start_free) {
obj *p, **my_free_list;
// heap空间不足,搜寻适当的free_list("未用区块,且区块够大")
for (int i = size; i <= _MAX_BYTES; i += _ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_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, n_objs);
}
}
end_free = 0;
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
return chunk_alloc(size, n_objs);
}
}
分为以下几种情况:
内存池中恰好有足够大的空间,则直接返回;
内存池中不能完全满足需求量,但是足够供应一个以上的区块,则返回足够的区块大小。
内存池中一个区块都不能够满足,
3.1 首先把剩余的内存空间分配到合适的桶中(free_list)
3.2 然后用malloc去申请2倍的需求内存大小。
3.3 如果申请成功,则返回需求的内存空间大小。
3.4 如果申请不成功,则需要从freelist(大于需求的块)中查看,是否有空闲的块还没使用,递归查询。