正文2:内存池的实现及回收、检测内存泄漏的方法总结

一、大纲思维导图

在这里插入图片描述

二、内存池实现的方式

1.实现的算法
a)伙伴算法
b)slab算法
c)按连接池的配合算法
2.实现的功能模块
3.面试中关于内存池内存泄漏的问题
4.代码

三、内存池代码展示




#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))




//分配4k以上的块
struct mp_large_s {
	struct mp_large_s *next;    //指向下一块内存
	void *alloc;                //指向内存块空间
};


//分配4K以下的块
//一个节点是一页
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);

//内存池的创建
//size是节点的大小
struct mp_pool_s *mp_create_pool(size_t size) {

	struct mp_pool_s *p;
	//分配4k以上内存用posix_memalign()这个函数分配
	//第一参数:返回的指针
	//第二参数:表示分配空间是以多大的空间对齐,这里宏定义为4k
	//第三参数:表示分配的大小size
	//sizeof(struct mp_pool_s)表示结构体放在节点的第一个块前面,放在里面是为了避免内存碎片
	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->current = p->head = p+1;
	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;    //end指向节点最末尾内存块的位置

	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;

    //先释放大块的list
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	h = pool->head->next;

    //再去释放node节点
	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);
	}

}

//分配小块内存
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;//end指向这个节点结尾的位置
	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;

    //循环遍历几次
    //针对一个块尝试多次分配,不行就移动到下一块(nginx)
	for (p = current; p->next; p = p->next) {
		if (p->failed++ > 4) {  //如果尝试4次,就遗弃这个块
		                        //为什么是4?经验值
			current = p->next;
		}
	}
	p->next = new_node;

	pool->current = current ? 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;

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

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) {

    if( 0 >= size)
        return NULL;

	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);  //分配大块,返回小块的内存
//关于如果一个节点剩下128字节的空间,若分配150个字节的空间,nginx是重试4,5次才改变当前指针
}


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;

	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;

}

四、内存泄露的应对方法

1)如何判断内存泄露?

htop/top(虚拟内存一直扩大), mtrace,valgrind

2)如何判断在哪里内存泄露?

通过代码申请的时候保存指针地址,回收内存的时候删除文件来判断内存泄露在哪里
在这里插入图片描述

3)仅适用于单个文件内用malloc分配内存的内存检测(读写一个文件,写下内存分配的地址和行数,线上热更新改变宏定义)

  • 代码(linux版本)
#include <iostream>
#include <string>
#include <stdlib.h>
#include <stdio.h>

#ifdef __linux__
#include <unistd.h>
#endif //__linux__

using namespace std;


void* _malloc(size_t size,const char* file,int line)
{
	void* p = malloc(size);
	printf("_malloc-->%d %s:%d\n",size,file,line);
	
	char buff[128] = { 0 };
	sprintf(buff, "./mem/%p.mem", p);//放到mem文件夹下的文件上,名字是指针地址
	FILE* fp = fopen(buff, "w");
	fprintf(fp, "[+%s:%d] --> addr: %p , size: %ld\n", file, line,p,size);
	fflush(fp);
	fclose(fp);

	return p;
}

void _free(void* p, const char* file, int line)
{
	free(p);
	char buff[128] = { 0 };
	sprintf(buff, "./mem/%p.mem", p);//放到mem文件夹下的文件上,名字是指针地址

#ifdef __linux__
	if (unlink(buff) < 0) //文件不存在就是小于0
	{
		printf("double free %p",p):
		return;
	}
#endif// __linux__

	printf("_free, %s:%d\n", file, line);
}

#define  malloc(size)  _malloc(size,__FILE__,__LINE__)
#define  free(p)       _free(p,__FILE__,__LINE__)

void func(void)
{
	void* p1 = malloc(10);
	void* p2 = malloc(20);


	free(p1);
	void* p3 = malloc(10);
	free(p3);


}

int main()
{
	func();
	return 0;
}

4)内存泄露发生怎么做?

①方式一:单文件
1、线上系统做好热更新,把对应的内存调试打开
2、在对应的文件夹开始用fflush写文件到特定目录比如memleak下
②方式二:hook-多文件(用dlsym把调用系统的函数改成调用我们自己的函数)

1)先定义新类型
typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;
typedef void (*malloc_t)(void* size);
free_t free_f = NULL;

2)重写malloc和free函数
//__builtin_return_address()编译器提供的,返回函数调用的上一级(根据参数来定)
int enable_malloc_hook = 1;
void* malloc(size_t size)
{
	void* p = NULL;
	if(enable_malloc_hook)
	{
		enable_malloc_hook = 0;
		void* caller = __builtin_return_address(0);
		print("caller : %p\n",caller);
		p=malloc_f(size);
	}else
	{
		p=malloc_f(size);
	}
	return p;
}

void free(void* p)
{
	return free_f(size);
}

3)截获函数
static void init_hook(void)
{
	if(malloc_f == NULL)
	{
		//从动态库和可执行文件里面获得符号地址
		malloc_f = dlsym(RTLD_NEXT,"malloc");
	}
	if(free_f == NULL)
	{
		free_f = dlsym(RTLD_NEXT,"free");
	}
}

  • 打印效果
    在这里插入图片描述
    有工具可以转换下,得出函数调用地址
//leak是执行文件
addr2line -f -e leak -a 0x400789

在这里插入图片描述
可以看到分配内存的文件、函数名

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值