文章目录
一、大纲思维导图
二、内存池实现的方式
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
可以看到分配内存的文件、函数名