内存池的理解(一)

内存池理解(一)

内存池就是在组件启动之间,预先申请好的一大块内存,这样在组件中申请和释放内存就可以由内存池统一 管理。

由内存池统一管理内存的好处如下

(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 可以处于三种可能状态之一:

  1. 满的:slab 的所有对象标记为使用。
  2. 空的:slab 上的所有对象标记为空闲。
  3. 部分:slab 上的对象有的标记为使用,有的标记为空闲。

slab 分配器首先尝试在部分为空的 slab 中用空闲对象来满足请求。如果不存在,则从空的 slab 中分配空闲对象。如果没有空的 slab 可用,则从连续物理页面分配新的 slab,并将其分配给 cache;从这个 slab 上,再分配对象内存。

slab 分配器提供两个主要优点:
  1. 没有因碎片而引起内存浪费。碎片不是问题,因为每个内核数据结构都有关联的 cache,每个 cache 都由一个或多个 slab 组成,而 slab 按所表示对象的大小来分块。因此,当内核请求对象内存时,slab 分配器可以返回刚好表示对象的所需内存。
  2. 可以快速满足内存请求。因此,当对象频繁地被分配和释放时,如来自内核请求的情况,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);	
		}
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值