内存池设计

大耗子 2020年06月26日 170次浏览

文章链接:https://codemouse.online/archives/2020-06-26-23-50-33

内存池

在日常的写代码中,经常有需要申请内存的时候,但是频繁的申请释放会特别的浪费时间,于是衍生了内存池,由内存池帮忙管理内存,内存池帮忙统一释放,免去了用户的频繁释放,申请内存只需要从内存池中已经申请好的内存中取出,如果没有大于需求的内存,则内存池再去申请一块回来。

内存池增强了程序员对内存碎片话的管理,加快了内存的使用速度。

这个内存池的模型是参考了nginx内使用的内存池,相当于一个简化版,仅供参考。

本例子中,暂时没有写对小块内存的释放处理,只有在摧毁内存池的时候,才会去释放小块内存,这个还待优化。在大块内存中,用完就可以直接释放掉,不用加以保留。

小块内存其实可以根据使用的剩余大小做一个限制,超过一定界限,再通过设定一个flag,当没人用的时候就设定为0,这时候将内存初始化,清零一下,相当于空出一块内存,省去了申请的过程。有兴趣的可以实现一下。我说的flag也就是我小块内存的failed变量。

模型图

左边是小块内存的管理,右边是大块内存的管理。

内存池结构图

实现代码

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define MP_ALIGNMENT       		32
#define MP_PAGE_SIZE			4096
#define MP_MAX_ALLOC_FROM_POOL	(MP_PAGE_SIZE-1)

#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))

// 大块内存,每次用完删除
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; // 下一块小块内存的指针
	size_t failed; // 记录有几块内存在使用
};

struct mp_pool_s {

	size_t max; // 区分大块与小块内存的界限

	struct mp_node_s *current;
	struct mp_large_s *large;

	struct mp_node_s head[0];

};

struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_nalloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);
void mp_free(struct mp_pool_s *pool, void *p);


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

	struct mp_pool_s *p; // 分配池和小块内存的头和小块内存
	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);
	p->head->end = p->head->last + size;

	p->head->failed = 0;

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

	h = pool->head->next;

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

	free(pool);

}

// 重置内存池,释放大块内存,小块内存保留,链表节点保留
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);
		// memset(h->last, h->end - h->last);
	}

}

// 小块内存的申请
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 = current = pool->current;
	new_node->next = current;
	pool->current = new_node;
	
	return m;

}

// 申请大块内存
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {

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

	void *p;
	
	int ret = posix_memalign(&p, MP_ALIGNMENT, size);
	if (ret) {
		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;
		}

	}

	
	
	// 没找到空闲节点,去申请新节点,如果此节点大小大于初始化的max大小,就会函数嵌套循环(bug)
	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_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) {

	void *p;
	
	int ret = posix_memalign(&p, alignment, size);
	if (ret) {
		return NULL;
	}

	struct mp_large_s *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_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);
	
}

// 获取内存,但是没有地址对齐
void *mp_nalloc(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 = p->last;
			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);
	
}

// 申请内存,并清零
void *mp_calloc(struct mp_pool_s *pool, size_t size) {

	void *p = mp_alloc(pool, size);
	if (p) {
		memset(p, 0, size);
	}

	return p;
	
}


// 释放大块内存,但是没有处理小块内存
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 ;
		}
	}
	
}

使用范例

int main(int argc, char *argv[]) {

	int size = 1 << 12; // 4096

	struct mp_pool_s *p = mp_create_pool(size);

	int i = 0;
	for (i = 0;i < 10;i ++) {

		void *mp = mp_alloc(p, 512);
//		mp_free(mp);
	}

	//printf("mp_create_pool: %ld\n", p->max);
	printf("mp_align(123, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));
	//printf("mp_align_ptr(p->current, 32): %lx, p->current: %lx, mp_align(p->large, 32): %lx, p->large: %lx\n", mp_align_ptr(p->current, 32), p->current, mp_align_ptr(p->large, 32), p->large);

	int j = 0;
	for (i = 0;i < 5;i ++) {

		char *pp = mp_calloc(p, 32);
		for (j = 0;j < 32;j ++) {
			if (pp[j]) {
				printf("calloc wrong\n");
			}
			printf("calloc success\n");
		}
	}

	//printf("mp_reset_pool\n");

	for (i = 0;i < 5;i ++) {
		void *l = mp_alloc(p, 8192);
		mp_free(p, l);
	}

	mp_reset_pool(p);

	//printf("mp_destory_pool\n");
	for (i = 0;i < 58;i ++) {
		mp_alloc(p, 256);
	}

	mp_destory_pool(p);

	return 0;

}