Linux下C语言的内存池结构(分模块讲解)

一:内存池的定义和作用

        1、定义:

        内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

        2、作用:

        内存池是一种用与管理和分配内存的技术,它通过预先申请一块固定大小的内存空间,并将其划分为多个小块,提供给程序按需分配和释放。

二:内存池需要的结构体

        有一种内存池是每次都开辟相同大小的空间,实现起来类似于数组和链表,今天不讲这个类型的线程池。讲一下内种可以开辟不同内存的线程池。

        对于这种不同内存的线程池,咱们可以将其区分为大内存块和小内存块。然后通过一个池来讲这两个不同的管理起来,这样咱们的基本数据结构就清楚了

        1:小内存块

//小块内存的        在一块内存中,这个结构体中占据这块的前几个字节,然后这几个指针分别指向这个结构体的后面(last),指向这块内存的最后(end) 
//最后指向下一块大内存的地方(next)    
//小块内存也可以实现大块内存
struct mp_node_s {
	unsigned char *last;
	unsigned char *end;
	struct mp_node_s *next;
	size_t failed;
};

        2:大内存块

struct mp_large_s {
	struct mp_large_s *next;
	void *alloc;
};

        3:内存池

struct mp_pool_s {
    size_t max;                //用来区分大小块内存的
	struct mp_node_s *current;    //指向小内存块
	struct mp_large_s *large;     //指向大内存块
	struct mp_node_s head[0];     //指向一开始创建的小内存块的
};

三:实现各种函数

        要实现内存池,咱们可以想一下需要哪些函数?首先肯定是创建内存池了,有内存池之后肯定要销毁,然后就是大小快内存的开辟,开辟好之后,要对内存进行分配,当不需要之后可以释放掉,大部分的函数其实已经都出来了,还有一个函数叫重置线程池,不需要销毁线程池,,将线程池恢复到最初的模样。

        1:创建线程池。

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

	struct mp_pool_s *p;
	//posix_memalign 所有的类型都该自然对齐,因为不对齐会导致性能下降,这个函数可以动态的对齐
	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;		//将指向小块内存的指针指向这个第一块内存。。
	p->large = NULL;

	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);   //要将这个last指向mp_node_s这个结构体的最后
	p->head->end = p->head->last + size;        //要指向这块内存的最后

	p->head->failed = 0;

	return p;

}

大家可能不太理解这个小块内存的样子,没关系,上图!也就是说,这个小块内存的结构体是在这块内存的前面,然后这个last指针指向这个结构体的最后,end指针指向这块内存的最后,这样end减去last就可以得到这块内存的大小,这个next指针指向下一块内存,用来连接。

        2:线程池的销毁。

//销毁内存池
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);
		}
	}

	h = pool->head->next;      

	while (h) {            //循环释放掉小块内存
		n = h->next;
		free(h);
		h = n;
	}

	free(pool);

}

        3:重置内存池。

//重置内存池,恢复到刚创建的样子
void mp_reset_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h;
	struct mp_large_s *l;

	for (l = pool->large; l; l = l->next) {    //因为一开始并未创建大块内存,所以全部释放
		if (l->alloc) {
			free(l->alloc);
		}
	}

	pool->large = NULL;     //上面全部释放掉之后,这里置为空
    
    //对于小内存块为什么不释放掉,而是直接指向最开始的地方,这个情况需要根据实际使用情况而定。在这里可以不用释放,可以在后面直接拿来使用的。
	for (h = pool->head; h; h = h->next) {
		h->last = (unsigned char *)h + sizeof(struct mp_node_s);    //这里让指针重新指向第一快内存的结构体
	}
}

        4:分配小内存块。

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;                //下面的操作就是将这个新的结点插入进去

	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;       //返回这个已经分配好的空间

}

        5:分配大块内存。

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;
	for (large = pool->large; large; large = large->next) {
		if (large->alloc == NULL) {     //找到未分配的地址
			large->alloc = p;
			return p;
		}
		if (n ++ > 3) break;        //如果连续三块未找到,那就直接重新开辟
	}

	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;
}

     对于大块内存的样子我就直接上图。

          6:分配位置。

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) {     //先从已有的内存块中查找
				p->last = m + size;
				return m;
			}
			p = p->next;
		} while (p);

		return mp_alloc_block(pool, size);
	}

	return mp_alloc_large(pool, size);
	
}

        7:洗脏数据。

void *mp_calloc(struct mp_pool_s *pool, size_t size) {
    //将内存池所在的位置全部置为0
	void *p = mp_alloc(pool, size);
	if (p) {
		memset(p, 0, size);
	}

	return p;
}

        8:释放内存。

//将大内存直接释放掉,小内存可以直接用
void mp_free(struct mp_pool_s *pool, void *p) {

	struct mp_large_s *l;
	for (l = pool->large; l; l = l->next) {
		if (p == l->alloc) {
			free(l->alloc);
			l->alloc = NULL;

			return ;
		}
	}
	
}

最终的大小块内存在一起是什么样子呢?

以上就是全部内存池的知识了,如有讲解错误的,一定要指正我哦。https://xxetb.xetslk.com/s/2D96kH

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值