写在前面
上篇文章写了如何实现一个简易的 allocator 空间配置器,这篇文章来讲讲 allocator 空间分配的内部机制。
由于分配过多的“小型区块”会造成内存碎片问题,SGI 设计了双层级配置器,即第一级配置器和第二级配置器。
第一级配置器
第一级配置器以 malloc(),free(),realloc() 等 C 函数执行实际的内存配置、释放、重配置操作。
如图 1 第一级配置器部分所示,部分代码如下:
template <int inst>
class __malloc_alloc_template { // 第一级配置器
...... // 省略部分细节代码
static void * allocate(size_t n){
void *result = malloc(n); // 第一級配置器直接使用 malloc()
if (0 == result) result = oom_malloc(n);
return result;
}
static void deallocate(void *p, size_t /* n */){
free(p); // 第一級配置器直接使用 free()
}
static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz){
void * result = realloc(p, new_sz); // 第一級配置器直接使用 realloc()
if (0 == result) result = oom_realloc(p, new_sz);
return result;
}
...... // 省略部分细节代码
};
第二级配置器
第二级配置器视情况采用不同的策略:当配置区块超过 128 bytes 时,视之为“足够大”,便调用第一级配置器;当配置区块小于 128 bytes 时,视之为“过小”,为了降低额外负担,便采用内存池配置方式。
在往下看之前,我们先来聊聊 “cookie” 这个概念。
如图 2 所示,cookie 如同分配内存的“身份证”,用来记录内存大小的信息。
它所占分配内存的大小是不变的,当所需内存越小,cookie 所占整个分配内存的比例就越大,操作系统维护这块内存造成的损耗就越大;反之,操作系统维护这块内存造成的损耗就越小。
当配置区块小于 128 bytes 时,我们视之为小内存,按照以上说法操作系统维护这块内存造成的损耗就会比较大。
再者,操作系统分配内存并不是按照地址顺序一个接着一个内存分配的,它是哪里有合适的大小就取哪里的内存,这种东取一块西取一块就会造成大量的内存碎片。
那有什么方法来解决上面的问题呢?
为了解决以上的难题,第二级配置器解决的方法就是,每次都给内存池中申请一大块的内存空间,并且用多条自由链表对这块内存进行维护。
1. 自由链表
每次配置一大块内存,就直接从自由链表的表头拨出。如果释放小额区块,就由配置器回收到自由链表表头。 为了方便管理,第二级配置器会主动将任何小额区块的内存需求量上调至 8 的倍数,并维护 16 个自由链表,各自管理大小分别为 8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes 的小额区块。2. 内存池
内存池是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。 当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。最后
最后的最后,千言万语不如一图明了。。。
如果这篇内容对你有帮助,可以点个在看支持一下。同时,欢迎关注下方微信公众号『编程异思坊』,更多精彩内容等你探索!