nginx 是通过资源集中管理的方式管理资源的,即打开所有即将要用的资源,以备随时取用,无论是文件还是内存。
这样做的好处是避免了每次创建、打开资源造成的性能消耗。
用来集中申请内存资源并进行内存资源的管理和分配。
nginx内存池的声明和定义分别在src/core/ngx_palloc.h
和 src/core/ngx_palloc.c
中
nginx内存池就是一个巨大的单链表结构,由ngx_pool_data_t结构域指定当前内存池节点的终止地址、下一内存池链表节点等信息。
nginx内存池的设计原则:只分配不释放,并没有一个用来释放内存池中数据的操作函数。这样做的好处是维护内存池结构的间接性而无需关心内存碎片的产生。 每次分配工作都在第一个节点的 current 域指向的内存池节点进行,一旦连续4次分配失败,这也就意味着 current 指向的内存池节点剩余空间已经不足,然后便会让 current 指向内存池链表上的下一届点,这样保证了其分配工作的正常进行,而无需每次都遍历整个内存池链表。
struct ngx_pool_s {
ngx_pool_data_t d; // 内存池数据库,节点数据,包含pool的数据区指针的结构体 pool->d.last ~ pool->d.end 中的内存区便是可用数据区
size_t max; // 数据块大小,小块内存最大值,一次最多从pool中开辟的最大空间
ngx_pool_t *current; // 内存池中可以申请内存的第一个节点 pool当前正在pool的指针 current 永远指向此pool的开始地址。(current的意思是当前的pool地址)
/* pool 中的 chain 指向一个 ngx_chain_t 数据,其值是由宏 ngx_free_chain 进行赋予的,指向之前用完了的,
可以释放的ngx_chain_t数据。由函数ngx_alloc_chain_link进行使用。*/
ngx_chain_t *chain; // pool当前可用的 ngx_chain_t 数据,注意:由 ngx_free_chain 赋值 ngx_alloc_chain_link
ngx_pool_large_t *large; // 节点中大内存块指针, pool中指向大数据快的指针(大数据快是指 size > max 的数据块)
ngx_pool_cleanup_t *cleanup; // pool中指向 ngx_pool_cleanup_t 数据块的指针 //cleanup在ngx_pool_cleanup_add赋值
ngx_log_t *log; // pool中指向 ngx_log_t 的指针,用于写日志的ngx_event_accept会赋值
};
typedef struct {
u_char *last; // 申请过的内存的尾地址,可申请的首地址 pool->d.last ~ pool->d.end 中的内存区便是可用数据区。
u_char *end; // 当前内存池节点可以申请的内存的最终位置
ngx_pool_t *next; // 下一个内存池节点ngx_pool_t,见ngx_palloc_block
ngx_uint_t failed; // 当前节点申请内存失败的次数。如果发现从当前pool中分配内存失败四次,则使用下一个pool,见ngx_palloc_block
} ngx_pool_data_t;
ngx_pool_s中的大块内存成员(large)
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc; // 申请的内存块地址
};
ngx_pool_s中的cleanup成员
ngx_pool_cleanup_t与ngx_http_cleanup_pt是不同的,ngx_pool_cleanup_t仅在所有的内存池销毁时才会被调用来清理资源,它何时释放资源将视所使用的内存池而定,而ngx_http_cleanup_pt是在ngx_http_request_t结构体释放时被调用来释放资源的。
如果我们需要添加自己的回调函数,则需要调用ngx_pool_cleanup_add来得到一个ngx_pool_cleanup_t,然后设置handler为我们的清理函数,并设置data为我们要清理的数据。这样在ngx_destroy_pool中会循环调用handler清理数据;
比如:我们可以将一个打开的文件描述符作为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来处理文件描述符资源了。
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; // 当前cleanup数据的回调函数 ngx_destory_pool中执行
void *data; // 内存的真正地址 回调时,将此数据传入回调函数; ngx_pool_cleanup_add中开辟空间
ngx_pool_cleanup_t *next; // 指向下一块 cleanup 内存的指针
};
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
// 创建内存池
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
// 销毁内存池
/*
内存池的销毁工作进行了一下三步:
1. 循环调用所有的析构函数
2. 释放所有的大内存结构
3. 释放内存池链上其他内存池
*/
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);
内存池分配的函数主要有以下几个:
// 内存申请(对齐)
void *ngx_palloc(ngx_pool_t *pool, size_t size);
// 还有一个封装了ngx_palloc的函数ngx_pcalloc,它多做了一件事,就是把ngx_palloc申请到的内存块全部置为0,虽然,多数情况下更适合用ngx_pcalloc来分配内存。
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
// 内存申请(不对齐)
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
// 以指定对齐方式分配内存
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
// 当内存不足时,nginx 会根据待分配内存的大小,执行下面两个函数之一,来扩充内存池
// 分配新的内存池
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size);
// 为大块内存申请分配内存池
static void * ngx_palloc_large(ngx_pool_t *pool, size_t size);
对于小块内存,如果当前内存池已经不足以分配,则会在当前内存池链上创建一个新的内存池,用来分配新的空间申请。
对于大于max的内存申请,则在整个内存空间malloc一段内存,使用ngx_large_t结构指向它,并相应的ngx_large_t变量存储到内存池中形成大内存结构链表,由内存池结构的large域指向链表的起始地址。