剖析nginx的内存池源码,讲解原理实现以及该内存池设计的应用场景
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) // 4K
#define NGX_DEFAULT_POOL_SIZE (16 * 1024)
#define NGX_POOL_ALIGNMENT 16
#define NGX_MIN_POOL_SIZE \
ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \
NGX_POOL_ALIGNMENT)
#define ngx_align(d, a) (((d) + (a - 1)) & ~(a - 1)) // 向上调正到邻接 a 的倍数上
// nginx内存池的主结构体类型
struct ngx_pool_s {
ngx_pool_data_t d; // 内存池的数据头
size_t max; // 小块内存分配的最大值
ngx_pool_t *current; // 小块内存池入口指针
ngx_chain_t *chain;
ngx_pool_large_t *large; // 大块内存分配入口指针
ngx_pool_cleanup_t *cleanup; // 清理函数handler的入口指针
ngx_log_t *log;
};
typedef struct ngx_pool_s ngx_pool_t;
// 小块内存数据头信息
typedef struct {
u_char *last; // 可分配内存开始位置
u_char *end; // 可分配内存末尾位置
ngx_pool_t *next; // 保存下一个内存池的地址
ngx_uint_t failed; // 记录当前内存池分配失败的次数
} ngx_pool_data_t;
typedef struct ngx_pool_large_s ngx_pool_large_t;
// 大块内存类型定义
struct ngx_pool_large_s {
ngx_pool_large_t *next; // 下一个大块内存
void *alloc; // 记录分配的大块内存的起始地址
};
typedef void (*ngx_pool_cleanup_pt)(void *data); // 清理回调函数的类型定义
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
// 清理操作的类型定义,包括一个清理回调函数,传给回调函数的数据和下一个清理操作的地址
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // 清理回调函数
void *data; // 传递给回调函数的指针
ngx_pool_cleanup_t *next; // 指向下一个清理操作
};
函数接口
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
void ngx_destroy_pool(ngx_pool_t *pool);
void ngx_reset_pool(ngx_pool_t *pool);
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
void ngx_pool_cleanup_file(void *data);
void ngx_pool_delete_file(void *data);
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); // 开辟内存
if (p == NULL) {
return NULL;
}
p->d.last = (u_char *) p + sizeof(ngx_pool_t); // 起始地址 + 头部地址
p->d.end = (u_char *) p + size; // 末尾地址
p->d.next = NULL;
p->d.failed = 0;
size = size - sizeof(ngx_pool_t); // 可以用的空间
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; // 最大能开辟一个页面,max记录 内存池 分配的最大字节数
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) { // 小块内存分配 pool->max <= 定义的页面
return ngx_palloc_small(pool, size, 1);
}
#endif
return ngx_palloc_large(pool, size);
}
void *
ngx_pnalloc(ngx_pool_t *pool, size_t size) // 内存不对齐版本
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 0);
}
#endif
return ngx_palloc_large(pool, size);
}
struct ngx_pool_s {
ngx_pool_data_t d;
size_t max;
ngx_pool_t *current;
ngx_chain_t *chain;
ngx_pool_large_t *large;
ngx_pool_cleanup_t *cleanup;
ngx_log_t *log;
};
typedef struct {
u_char *last;
u_char *end;
ngx_pool_t *next;
ngx_uint_t failed;
} ngx_pool_data_t;
小块内存池无回收函数
static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
u_char *m;
ngx_pool_t *p;
p = pool->current;
do {
m = p->d.last;
if (align) { // 考虑内存对齐
m = ngx_align_ptr(m, NGX_ALIGNMENT);
}
if ((size_t) (p->d.end - m) >= size) { // 内存池剩余的 大于 申请的空间
p->d.last = m + size;
return m;
}
p = p->d.next; // 不够了,指向下一个
} while (p);
// 没有分配成功
return ngx_palloc_block(pool, size);
}
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new;
psize = (size_t) (pool->d.end - (u_char *) pool); // 计算 pool size
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); // 分配内存,m 指向起始地址
if (m == NULL) {
return NULL;
}
new = (ngx_pool_t *) m;
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
m += sizeof(ngx_pool_data_t); // m指向了 data_t后面,保留了data_t,没保留了除了data_t外的其他头部信息
m = ngx_align_ptr(m, NGX_ALIGNMENT); // 调整指针,到每个整数倍(sizeof(unsigned long) ),与平台相关
new->d.last = m + size; // 分配内存
for (p = pool->current; p->d.next; p = p->d.next) { // 遍历所有内存块
if (p->d.failed++ > 4) { // 分配次数超过四次,就不再使用这块内存
pool->current = p->d.next;
}
}
p->d.next = new; // 接到原来的内存链表当中
return m;
}
存在一些不准确 小块内存 与 原内存池一样大
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
p = ngx_alloc(size, pool->log); // 直接调用malloc
if (p == NULL) {
return NULL;
}
n = 0;
for (large = pool->large; large; large = large->next) { // 遍历large链表
if (large->alloc == NULL) { // 如果alloc为空(在free的时候设为空)
large->alloc = p; // 直接放在这里
return p;
}
if (n++ > 3) { // 只遍历3次
break;
}
}
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); // 在小块内存池上分配 大块内存池的内存头
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large; // 头插法
pool->large = large;
return p;
}
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p", l->alloc);
ngx_free(l->alloc);
l->alloc = NULL;
return NGX_OK;
}
}
return NGX_DECLINED;
}
小块内存池无法归还 —— 因为 分配内存池 只有 两个指针(last指针,end指针),移动last指针即可分配(分配效率块),不能回收(无法移动last部指针回收)
nginx大块内存分配 ——> 内存释放ngx_free函数
nginx小块内存分配 ——> 没有提供任何的内存释放函数,实际上,从小块内存的分配方式来看(直接通过last指针偏移来分配内存),它也没法进行小块内存的回收
此时Nginx可以调用ngx_reset_pool重置内存池了,等待下—次该客户端的请求
分配内存池很快,不易free,有内存碎片,对于短连接很有用 —— 不适用于长连接
void
ngx_reset_pool(ngx_pool_t *pool)
{
ngx_pool_t *p;
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc);
}
}
/* 原写法存在一定优化空间
for (p = pool; p; p = p->d.next) { // 遍历小块内存
p->d.last = (u_char *) p + sizeof(ngx_pool_t); // 移动到头部数据后
p->d.failed = 0;
}
*/
// 处理第一块内存池
p = pool;
p->d.last = (uchar *) p + sizeof(ngx_pool_t);
p->d.failed = 0;
// 处理后续内存池
for (p = p.next; p; p = p->d.next) { // 遍历小块内存
p->d.last = (u_char *) p + sizeof(ngx_pool_data_t); // 移动到头部数据后
p->d.failed = 0;
}
pool->current = pool;
pool->chain = NULL;
pool->large = NULL; // 大块内存池的内存头 没有用了
}
管理外部资源
需要预置一个回调函数 —— 析构对象
typedef void (*ngx_pool_cleanup_pt)(void *data); // 函数指针 —— ngx_pool_claenup_pt
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt handler; // —— 回调函数
void *data;
ngx_pool_cleanup_t *next;
};
// typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) // 插入一个 ngx_pool_cleanup_s
{
ngx_pool_cleanup_t *c;
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); // 开辟cleanup_t的空间
if (c == NULL) {
return NULL;
}
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = NULL;
c->next = p->cleanup; // 头插法
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
void release(void *p) {
free(p);
}
stData *pData = ngx_alloc(512) // stData里有一个 char*成员
pData->p = (char*)malloc(12);
strcpy(pData->p, "hello world");
ngx_pool_cleanup_t *pclean = ngx_pool_cleanup_add(pool, sizeof(char*));
pclean->handler = &release;
pclean->data = pData->p;
先对大快内存操作
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
for (c = pool->cleanup; c; c = c->next) { // 先遍历cleanup,执行外部资源的清除操作
if (c->handler) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"run cleanup: %p", c);
c->handler(c->data);
}
}
for (l = pool->large; l; l = l->next) { // free 大块内存
if (l->alloc) {
ngx_free(l->alloc);
}
}
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { // free 小块内存
ngx_free(p);
if (n == NULL) {
break;
}
}
}
编译
gcc -c -g -I src/core -I src/event -I src/event/modules -I src/os/unix -I objs -I src/http -I src/http/modules -o ngx_testpool.o ngx_testpool.c
gcc -o ngx_testpool ngx_testpool.o objs/src/core/ngx_palloc.o objs/src/os/unix/ngx_alloc.o // 生成可执行文件
#include
#include
#include
#include
#include
#include
#include
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
const char *fmt, ...)
{
}
typedef struct Data stData;
struct Data
{
char *ptr;
FILE *pfile;
};
void func1(char *p)
{
printf("free ptr mem!");
free(p);
}
void func2(FILE *pf)
{
printf("close file!");
fclose(pf);
}
void main()
{
// 512 - sizeof(ngx_pool_t) 、 4096 => max
ngx_pool_t *pool = ngx_create_pool(512, NULL);
if(pool == NULL)
{
printf("ngx_create_pool fail...");
return;
}
void *p1 = ngx_palloc(pool, 128); // 从小块内存池分配的
if(p1 == NULL)
{
printf("ngx_palloc 128 bytes fail...");
return;
}
stData *p2 = ngx_palloc(pool, 512); // 从大块内存池分配的
if(p2 == NULL)
{
printf("ngx_palloc 512 bytes fail...");
return;
}
p2->ptr = malloc(12);
strcpy(p2->ptr, "hello world");
p2->pfile = fopen("data.txt", "w");
ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(pool, sizeof(char*));
c1->handler = func1;
c1->data = p2->ptr;
ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool, sizeof(FILE*));
c2->handler = func2;
c2->data = p2->pfile;
ngx_destroy_pool(pool); // 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存
return;
}