在研究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)检测内存泄漏问题。