池式结构(3)

本文主要讲内存池的原理和实现

池式结构的作用:
主要起到缓冲作用。也就是需要使用资源时,在已有的资源结构中去拿,避免重新创建资源。

背景:
在网络数据中,建立io连接后,recv收到数据后,会讲数据放置在buffer中。若数据周期较长,或者将处理数据进行解耦,放置在另一个线程中处理,这样buffer就是不可用的。我们通常会malloc一个内存,将数据拷贝进去,再在新线程中 处理数据。
问题:
当大量io事件发生时,会出现:
1、不利于内存管理;
2、内存碎片;

什么是内存池?
拿取内存时,从内存池中拿取内存,用完了就还回去

什么时候使用内存池?
服务端和客户端都可用内存池。
数据频繁的需要分配的地方,都可使用内存池。
内存池是针对小块内存的分配,因为其分配和释放的频率很高。

如何做内存池:
伙伴算法:
分配空间是2^n分配数据,回收数据会将两块合并成一起回收
做法是先分一个大块内存,然后在这个内存中分小块给其他人
1、针对4k如何分配?2^n
2、针对4K如何回收?需要将两个快连接到一起擦回收,要遵循物理内存空间。以页为单位操作,4k一页。伙伴算法是以页为单位操作。
伙伴算法适合在物理内存的分配与回收。
参考一
参考二

slab
分配4K时一开始就将其分配成若干个8,16,32,64,128,256等小空间的组成 。
参考一
参考二

实现内存池的策略
1、由整块散成小块;(伙伴算法) github可找到
2、划分好若干小块;(slab)
3、在特定业务场景里面:更实用,主要讲这个
建立fd连接时,就创建内存池给其使用,断开连接就将内存池释放。

内存池分为
4K内处理;
大于4K空间的处理;
将小于4K和大于4K的处理,连接起来;
内存池的创建;
销毁线程池;
从内存池中分配内存;

代码实现:
4K内存空间:

//4K内存
struct mp_node_s {

	unsigned char *last; //指向已经分配后的内存结尾
	unsigned char *end; //指向4k内存的结尾
	
	struct mp_node_s *next; //不同4K内存块,如何组成起来,通过指针连接起来
	size_t failed;
};

大于4K的空间:

//大于4K的内存
struct mp_large_s {
	struct mp_large_s *next;
	void *alloc; //分配块
};

内存池

//内存池
struct mp_pool_s {
	size_t max; //能分配最大的块
	struct mp_node_s *current; //当前从哪个节点分配
	struct mp_large_s *large; //大块数据
	struct mp_node_s head[0]; //指向4K小块的头节点
};

在这里插入图片描述
内存池的创建

//内存池的创建
struct mp_pool_s *mp_create_pool(size_t size) {

	struct mp_pool_s *p;
	//分配4K以上的大数据:参数:返回的指针,以多少对齐,分配大小。
	//这个空间就是4K数据前还加上结构体的大小,返回的是最开始的地方
	//放在一起是为了避免内存碎片
	int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
	if (ret) {//分配失败
		return NULL;
	}
	
	p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
	p->current = p->head; //指向4K节点一段
	p->large = NULL; //暂时没有大块

	//指向可分配的位置
	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
	p->head->end = p->head->last + size; //指向嘴末尾的位置

	p->head->failed = 0;

	return p;

}

从内存池中,分配内存大小

//分配,大小,返回开始地址
void *mp_alloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	if (size <= pool->max) { //小于等于就分配小块数据

		p = pool->current;

		do {
			
			m = mp_align_ptr(p->last, MP_ALIGNMENT);
			if ((size_t)(p->end - m) >= size) { //在4K空间中分配空间,若剩余的空间大于要分配的空间
				p->last = m + size;
				return m;
			}
			p = p->next; //在下一个4K空间中分配
		} while (p);

		return mp_alloc_block(pool, size); //分配新的4K小块数据
	}

	//否则分配大块数据内存
	return mp_alloc_large(pool, size);
	
}

分配4K以内的内存:

//分配新的4k小块数据
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *h = pool->head;
	size_t psize = (size_t)(h->end - (unsigned char *)h);
	
	//直接分配个整块
	int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
	if (ret) return NULL;

	//
	struct mp_node_s *p, *new_node, *current;
	new_node = (struct mp_node_s*)m;

	new_node->end = m + psize; //指向结尾的位置
	new_node->next = NULL;
	new_node->failed = 0; //

	m += sizeof(struct mp_node_s);
	m = mp_align_ptr(m, MP_ALIGNMENT);
	new_node->last = m + size; //

	current = pool->current;
	//遍历上一个4K空间剩下的小窗口
	//不断的去遍历小窗口的大小
	for (p = current; p->next; p = p->next) {
		if (p->failed++ > 4) { //大于尝试的失败次数,4是个经验值
			current = p->next;
		}
	}
	p->next = new_node;

	pool->current = current ? current : new_node;

	return m;

}

分配大于4K的内存:

//分配大块数据
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {

	void *p = malloc(size);
	if (p == NULL) return NULL;

	size_t n = 0;
	struct mp_large_s *large;
	//不考虑大块回收,只用last指针就好了
	//
	for (large = pool->large; large; large = large->next) {
		if (large->alloc == NULL) {//不考虑大块回收,拿到尾结点即可
			large->alloc = p;
			return p;
		}
		if (n ++ > 3) break;
	}

	//把大块的结构放在4K空间里,避免出现内存碎片
	large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}

销毁线程池

void mp_destory_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h, *n;
	struct mp_large_s *l;

	//循环释放大块数据
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

//释放 Nodes
	h = pool->head->next;

	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	free(pool);

}

面试题:
接触一个陌生的系统(服务器),htop看虚拟内存在涨,请问你怎么判断和解决问题?
1、判断是否内存池有内存泄露:打开打印信息,一般释放时会有打印信息
若没有引入内存池, tcmalloc/jemalloc全局内存池的做法来解决。
2、是否第三方lib库有内存泄漏,排查哪些业务会大量涨内存,再找对应的库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值