内存池理解(一)
内存池就是在组件启动之间,预先申请好的一大块内存,这样在组件中申请和释放内存就可以由内存池统一 管理。
由内存池统一管理内存的好处如下
(1)可以一定程度有效的防止内存泄漏和内存碎片化
(2)当组件出现内存泄漏的时候,有通用的内存池管理可以较为方便的定位组件内存泄漏点
内存池的实现有几种方式
1.伙伴算法
伙伴算法,简而言之,就是将内存分成若干块,然后尽可能以最适合的方式满足程序内存需求的一种内存管理算法,伙伴算法的一大优势是它能够完全避免外部碎片的产生。申请时,伙伴算法会给程序分配一个较大的内存空间,即保证所有大块内存都能得到满足。很明显分配比需求还大的内存空间,会产生内部碎片。所以伙伴算法虽然能够完全避免外部碎片的产生,但这恰恰是以产生内部碎片为代价的。
Linux 便是采用这著名的伙伴系统算法来解决外部碎片的问题。把所有的空闲页框分组为 11 块链表,每一块链表分别包含大小为1,2,4,8,16,32,64,128,256,512 和 1024 个连续的页框。对1024 个页框的最大请求对应着 4MB 大小的连续RAM 块。每一块的第一个页框的物理地址是该块大小的整数倍。例如,大小为 16个页框的块,其起始地址是 16 * 2^12 (2^12 = 4096,这是一个常规页的大小)的倍数。
下面通过一个简单的例子来说明该算法的工作原理:
假设要请求一个256(129~256)个页框的块。算法先在256个页框的链表中检查是否有一个空闲块。如果没有这样的块,算法会查找下一个更大的页块,也就是,在512个页框的链表中找一个空闲块。如果存在这样的块,内核就把512的页框分成两等分,一般用作满足需求,另一半则插入到256个页框的链表中。如果在512个页框的块链表中也没找到空闲块,就继续找更大的块——1024个页框的块。如果这样的块存在,内核就把1024个页框块的256个页框用作请求,然后剩余的768个页框中拿512个插入到512个页框的链表中,再把最后的256个插入到256个页框的链表中。如果1024个页框的链表还是空的,算法就放弃并发出错误信号。
简而言之,就是在分配内存时,首先从空闲的内存中搜索比申请的内存大的最小的内存块。如果这样的内存块存在,则将这块内存标记为“已用”,同时将该内存分配给应用程序。如果这样的内存不存在,则操作系统将寻找更大块的空闲内存,然后将这块内存平分成两部分,一部分返回给程序使用,另一部分作为空闲的内存块等待下一次被分配。
2.slab模式
slab 可以处于三种可能状态之一:
- 满的:slab 的所有对象标记为使用。
- 空的:slab 上的所有对象标记为空闲。
- 部分:slab 上的对象有的标记为使用,有的标记为空闲。
slab 分配器首先尝试在部分为空的 slab 中用空闲对象来满足请求。如果不存在,则从空的 slab 中分配空闲对象。如果没有空的 slab 可用,则从连续物理页面分配新的 slab,并将其分配给 cache;从这个 slab 上,再分配对象内存。
slab 分配器提供两个主要优点:
- 没有因碎片而引起内存浪费。碎片不是问题,因为每个内核数据结构都有关联的 cache,每个 cache 都由一个或多个 slab 组成,而 slab 按所表示对象的大小来分块。因此,当内核请求对象内存时,slab 分配器可以返回刚好表示对象的所需内存。
- 可以快速满足内存请求。因此,当对象频繁地被分配和释放时,如来自内核请求的情况,slab 分配方案在管理内存时特别有效。分配和释放内存的动作可能是一个耗时过程。然而,由于对象已预先创建,因此可以从 cache 中快速分配。再者,当内核用完对象并释放它时,它被标记为空闲并返回到 cache,从而立即可用于后续的内核请求。
3.如果不考虑小块内存的释放,则可以不对小块内存的释放做工作,这样对小块内存管理则更为简单,示例代码如下:
#define PAGE_SIZE 4096
#define MP_ALGMENT 4
//大块内存结构
struct mp_large_s{
struct mp_large_s *next;
void *alloc;
};
//小块内存结构
struct mp_node_s{
unsigned char* last;
unsigned char* end;
struct mp_node_s *next;
int failed;
};
//内存池结构
struct mp_pool_s{
int max;
struct mp_large_s *large;
struct mp_node_s *head;
struct mp_node_s *current;
};
关键接口实现如下:
//内存池创建
struct mp_pool_s* mp_create(int size){
struct mp_pool_s *p;
int ret = posix_meaglign(&p,MP_ALGMENT,sizeof(struct mp_pool_s) + size);
if(ret){
return NULL;
}
p->large = NULL;
p->current = p->head = p + 1;
p->head->last = (unsigned char*)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size - sizeof(struct mp_node_s);
return p;
}
//大块内存申请
void* mp_allloc_large(struct mp_pool_s* pool,int nsize){
unsigned char* m;
int ret = posix_memalign(&m,MP_ALGMENT,nsize);
if(ret){
return NULL;
}
struct mp_large_s *large = pool->large;
for(;large; large = large->next){
if(large->alloc == NULL){
large->alloc = m;
return m;
}
}
large = mp_alloc(pool,sizeof(struct mp_large_s));
large->alloc = m;
large->next = NULL;
return m;
}
//小块内存申请
void* mp_allloc_block(struct mp_pool_s* pool,int nsize){
unsigned char* m;
int ret = posix_memalign(&m,MP_ALGMENT,PAGE_SIZE);
if(ret){
return NULL;
}
struct mp_node_s *new_node = (struct mp_node_s*)m;
new_node->end = m + PAGE_SIZE;
new_node->last = m + sizeof(struct mp_node_s) + nsize;
new_node->next = NULL;
struct mp_node_s *p, *current = pool->current;
for(p = current; p->next; p = p->next){
if(p->failed++ > 4){
current = p->next;
}
}
p->next = new_node;
pool->current = current ? current:new_node;
return m + sizeof(struct mp_node_s);
}
//对外的内存申请接口
void *mp_alloc(struct mp_pool_s* pool,int nsize){
if(nsize <= 0){
return NULL;
}
if(nsize > PAGE_SIZE - sizeof(struct mp_node_s)){
return mp_allloc_large(pool,nsize);
}else{
struct mp_node_s* p = pool->current;
unsigned char* m = p->last;
if(p->end - m >= nsize){
p->last =m + nsize;
return m;
}else{
return mp_allloc_block(pool,nsize);
}
}
}