一个简单的内存池

在研究nginx源码,以下代码完全摘抄自nginx内存池源码。

mem_pool.h

#ifndef _MEM_POOL_H
#define _MEM_POOL_H

typedef  unsigned char  	u_char;
typedef  unsigned int  		u_int;


/*计算宏mem_align(1, 64)=64,只要输入d<64,则结果总是64,如果输入d=65,则结果为128,以此类推。*/
#define mem_align(d, a)     (((d) + (a - 1)) & ~(a - 1))

 将 m 对齐到内存对齐地址 
#define mem_align_ptr(p, a)                                                    \
    (u_char *) (((uint) (p) + ((uint) a - 1)) & ~((uint) a - 1))
	
typedef struct mem_pool_s  		mem_pool;
typedef struct pool_data_s 		pool_data;
typedef struct pool_large_s		pool_large;
typedef struct pool_cleanup_s  	pool_cleanup;

typedef void (*pool_cleanup_pt)(void *data);

typedef struct buf_s
{
	u_char					*pos;//指向从内存池里分配的内存
	u_char 					*last;//指向有效内容的结束
}buf_t;	

typedef struct chain_s
{
	buf_t 					*buf;
	struct chain_s			*next;
}chain_t;

struct pool_cleanup_s 
{
	pool_cleanup_pt   		handler;  // 当前 cleanup 数据的回调函数
    void                 	*data;     // 内存的真正地址
    pool_cleanup   			*next;     // 指向下一块 cleanup 内存的指针
};

struct pool_large_s 
{
    pool_large     			*next;     // 指向下一块 large 内存的指针
    void                	*alloc;    // 内存的真正地址
};
//内存块的描述结构
struct pool_data_s
{
    u_char        			*last;     // 当前 pool 中用完的数据的结尾指针,即可用数据的开始指针
    u_char        			*end;      // 当前 pool 的结尾指针
    mem_pool           	 	*next;     // 指向下一个 pool 的指针
    int            		 	failed;   // 当前 pool 内存不足以分配的次数
};

//一个内存池的头部信息
struct mem_pool_s
{
	pool_data 				d; //包含pool的数据区指针的结构体
	size_t    				max;//当前pool最大可分配的内存大小(Bytes),区别大内存还是小内存
	mem_pool 				*current;//指向当前pool的起始地址
	chain_t 				*chain;//构成内存池链表
	pool_large 				*large;// pool 中指向大数据快的指针(大数据快是指 size > max 的数据块)
	pool_cleanup			*cleanup;//用于释放内存的结构
};


mem_pool*	create_pool(size_t size);
void 		destroy_pool(mem_pool *pool);
void		reset_pool(mem_pool *pool);
void*		palloc(mem_pool *pool, size_t size);
void*		pnalloc(mem_pool *pool, size_t size);

#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mem_pool.h"

/*从内存池中最大能申请的内存大小,设置为一个系统分页的大小
  可一通过getconf PAGE_SIZE命令或者 getpagesize()函数来获取
  系统一个分页的大小*/
#define  MAX_ALLOC_FROM_POOL  (1024 * 4)

#define  MEM_ALIGNMENT       sizeof(unsigned long)

#define  POOL_ALIGNMENT       16


static void *palloc_block(mem_pool *pool, size_t size);
static void *palloc_large(mem_pool *pool, size_t size);

/*创建一个内存池*/
mem_pool* create_pool(size_t size)
{
	void *s;
	mem_pool *p;
	int n;
	
	/*int posix_memalign (void **memptr, size_t alignment, size_t size)
	成功时会返回size字节的动态内存,并且这块内存的地址是alignment的倍数。参数alignment必须是2的幂,
	还是void指针的大小的倍数。返回的内存块的地址放在了memptr里面,函数返回值是0.*/
	n = posix_memalign(&s, POOL_ALIGNMENT, size);
	if(n != 0)
	{
		printf("create_pool malloc error!\n");
		return NULL;
	}
	
	p = (mem_pool*)s;
	p->d.last = (u_char*)p + sizeof(mem_pool);
	p->d.end  = (u_char*)p + size;
	p->d.next = NULL;
	p->d.failed = 0;
	
	size = size - sizeof(mem_pool);
	p->max = (size < MAX_ALLOC_FROM_POOL) ? size : MAX_ALLOC_FROM_POOL;
	
	p->current = p;
	p->chain = NULL;
	p->large = NULL;
	p->cleanup = NULL;
	
	return p;
}
/*销毁一个内存池*/
void destroy_pool(mem_pool *pool)
{
    mem_pool      *p, *n;
    pool_large    *l;
    pool_cleanup  *c;

    for (c = pool->cleanup; c != NULL; c = c->next) 
	{
        if (c->handler) 
		{  
            c->handler(c->data);
        }
    }

    for (l = pool->large; l != NULL; l = l->next) 
	{
        if (l->alloc) 
		{
            free(l->alloc);
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) 
	{
        free(p);
        if (n == NULL) 
		{
            break;
        }
    }
}

/*重置内存池
  只是释放大内存*/
void reset_pool(mem_pool *pool)
{
    mem_pool    *p;
    pool_large  *l;

    for (l = pool->large; l; l = l->next) 
	{
        if (l->alloc) 
		{
            free(l->alloc);
        }
    }

    for (p = pool; p; p = p->d.next) 
	{
        p->d.last = (u_char *) p + sizeof(mem_pool);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

/*从内存池中获取一块内存*/
void *palloc(mem_pool *pool, size_t size)
{
    void      *m;
    mem_pool	*p;

	//判断size是小于pool中的最大可使用内存,小块内存分配
    if (size <= pool->max) 
	{
		//从current所在的pool数据节点开始往后遍历寻找那个节点可以分配size内存
		//current字段是变动的
        p = pool->current;
        do 
		{
            m = mem_align_ptr(p->d.last, MEM_ALIGNMENT);
            if ((size_t)(p->d.end - (u_char*)m) >= size) 
			{
                p->d.last = m + size;
                return m;
            }
            p = p->d.next;
        } while (p);
		//没有符合的就申请内存块
        return palloc_block(pool, size);
    }
	//申请大内存
    return palloc_large(pool, size);
}

//palloc和palloc的区别是分片小块内存时是否需要内存对齐
void *pnalloc(mem_pool *pool, size_t size)
{
    u_char      *m;
    mem_pool  	*p;

    if (size <= pool->max) {

        p = pool->current;

        do {
            m = p->d.last;
            if ((size_t)(p->d.end - m) >= size) 
			{
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        } while (p);

        return palloc_block(pool, size);
    }

    return palloc_large(pool, size);
}

//新增一块内存块
static void *palloc_block(mem_pool *pool, size_t size)
{
	int 		n;
    u_char      *m;
    size_t      psize;
    mem_pool  	*p, *curr;

    // 先前的整个 pool 的大小
    psize = (size_t) (pool->d.end - (u_char *)pool);

     在内存对齐了的前提下,新分配一块内存
	n = posix_memalign(&m, POOL_ALIGNMENT, psize);
    if (n != 0) 
	{
		printf("palloc_block posix_memalign error!\n");
        return NULL;
    }

    p = (mem_pool *)m;

    p->d.end = m + psize;
    p->d.next = NULL;
    p->d.failed = 0;

	//m 指向要分配的内存
    m += sizeof(pool_data);
    m = mem_align_ptr(m, MEM_ALIGNMENT);
    p->d.last = m + size; 

    // 判断在当前 pool 分配内存的失败次数,即:不能复用当前 pool 的次数,
    // 如果大于 6 次,这放弃在此 pool 上再次尝试分配内存,以提高效率
    //如果失败次数大于6(不等于6),则更新current指针,放弃对老pool的内存进行再使用
    for (curr = pool->current; curr->d.next != NULL; curr = curr->d.next) 
	{
        if (curr->d.failed++ > 4) //d.failed初始值为0
		{
            pool->current = curr->d.next;// 更新 current 指针, 每次从pool中分配内存的时候都是从curren开始遍历pool节点获取内存的
        }
    }

    // 让旧指针数据区的 next 指向新分配的 pool
    curr->d.next = p;

    return m;
}


static void *palloc_large(mem_pool *pool, size_t size)
{
    void            *p;
    uint	         n = 0;
    pool_large  	*large;

    p = malloc(size);
    if (p == NULL) 
	{
		printf("palloc_large malloc error!\n");
        return NULL;
    }

    // 查找largt链表上空余的large 指针
    for (large = pool->large; large != NULL; large = large->next) 
	{
        if (large->alloc == NULL) 
		{
            large->alloc = p;
            return p;
        }

        /*
         // 如果当前 large 后串的 large 内存块数目大于 3 (不等于3),
        // 则直接去下一步分配新内存,不再查找了
        */
        if (n++ > 3) 
		{//也就是说如果pool->large头后面连续4个large的alloc指针都被用了,则重新申请一个新的pool_larg并放到pool->large头部
            break;
        }
    }

    large = palloc(pool, sizeof(pool_large));
    if (large == NULL) 
	{
        free(p);
        return NULL;
    }

    // 将新分配的 large 串到链表后面
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

//释放一个大内存块
int pfree(mem_pool *pool, void *p)
{
    pool_large  *l;

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

            return 0;
        }
    }

    return -1;
}


void *pcalloc(mem_pool *pool, size_t size)
{

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

    return p;
}

一个测试程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mem_pool.h"

int main()
{
	mem_pool *pool;
	char *p, *s, *l;
	
	pool = create_pool(1024);
	if(pool == NULL)
	{
		printf("create pool error!\n");
		return -1;
	}
	
	p = palloc(pool, 500);
	if(p == NULL)
	{
		printf("mem_palloc error!\n");
		return -1;
	}
	
	memset(p, 'a', 500);
	
	s = palloc(pool, 500);
	if(s == NULL)
	{
		printf("mem_palloc error!\n");
		return -1;
	}
	
	memset(s, 's', 500);
	
	l = palloc(pool, 1024);
	if(l == NULL)
	{
		printf("mem_palloc error!\n");
		return -1;
	}
	
	memset(l, 'l', 1024);
	
	reset_pool(pool);
	
	destroy_pool(pool);
	return 0;
}

nginx 内存管理如上图所示。

优点:非常适合分配大小不定的内存,内存利用率高,内存对齐效率高,提高程序性能。

缺点:没有释放小内存块的操作,如果一直申请内存时间长了会导致内存用尽,不利于内存检测工具(Valgrind)检测内存泄漏问题。

猜你喜欢

转载自blog.csdn.net/u014608280/article/details/86494122
今日推荐