nginx对内存的管理由其内部的内存池实现,nginx在/src/os/unix/ngx_alloc.h/.c中定义了基本的内存分配操作,如malloc等。内存池部分的操作在/src/core/ngx_palloc.(h/c)中实现。
一、内存池相关数据结构
一个基本的nginx内存池结构如下所示
由上图可知,nginx通过将多个内存块串联成链表以形成一个内存池的,其中每个内存块都包含了一个固定头部(如每个内存块头部的蓝色部分d),固定头部记录了每个内存块的内存使用相关信息,同时每个内存池还包含了一个头部(如链表首个元素的红色部分),其中保存了内存池的相关信息。
1、内存池中每个内存块的头部
typedef struct { //内存池中每个节点的固有部分
u_char *last; //指向每个节点的实际可用空间的起始地址
u_char *end; //指向每个节点的实际可用空间的结束地址
ngx_pool_t *next; //指向内存池中的下一个节点
ngx_uint_t failed; //当前节点分配失败的次数
} ngx_pool_data_t;
sizeof(ngx_pool_data_t)=16字节,即内存池中除了头节点外的其他每个节点的起始16字节都是用来保存该内存块的使用信息的。
2、内存池头部结构
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;
};
sizeof(struct ngx_pool_s )=40,即内存池头节点的前40个字节是用于保存整个内存池和当前节点的相关信息的。
二、内存池的相关操作
内存池主要提供了以下操作:
- 创建内存池 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);
- 内存清除(大块内存) ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
下面将依次进行介绍。
1、创建内存池
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); //分配以16字节对齐的size大小的内存块,前面包含内存池的固有部分40字节
p->d.last = (u_char *) p + sizeof(ngx_pool_t); //将last指针指向当前内存块中可用部分的起始地址
p->d.end = (u_char *) p + size; //将end指针指向当前内存块可用空间的结束地址
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; //每次最多可分配的内存大小
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
eg:当执行完ngx_create_pool(1024,log)后,得到的结果如下:
则当前内存池只有一个节点,在该节点的前40字节中记录了当前内存池的相关信息,而从p->d.last到p->d.end之间的984字节可用于内存分配,因此p->max=984
2、内存申请
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
if (size <= pool->max) { //1、小块分配,所需的空间可以在当前内存池中一次分配完
p = pool->current; //找到内存池当前可用的节点
do {
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); //m指向当前内存块可用空间的起始地址
if ((size_t) (p->d.end - m) >= size) { //1、1.当前内存块的剩余可用空间大于所需空间
p->d.last = m + size; //更新当前内存块可用空间的起始地址
return m; //并返回分配结果m
}
p = p->d.next; //1、 2当前内存块中剩余空间不足以满足要求,则依次变了内存池的下一个节点,以找到有足够剩余空间的内存块,并分配内存
} while (p);
return ngx_palloc_block(pool, size); //1、3内存池中所有节点都不满足要求,则重新分配一个内存块
}
return ngx_palloc_large(pool, size); //2、大块分配,分配一个大块内存,并加入到large链表中
}
由上可知,在nginx内存池中申请内存主要分为两种情况:
- 待分配的内存size< p->max,即可以在内存池中一次分配成功,为小块分配
所需内存size>p->max,即超过了内存池每次可分配的最大空间,为大块分配
对于小块分配,可能有以下几种情况:
1、在内存池当前可用节点中可以分配成功
2、在内存池的其他可用节点中可以分配成功
3、当前内存池中的所有节点都不满足要求,此时则需要重新创建一个内存块,由于size<= p->max,因此此时可以分配成功
对于以上三种情况,分别如下图所示。
1、 当所需内存size=300字节时,在内存池链表的当前节点中就可以分配成功,直接在当前节点中分配
2、当所需内存size=600字节时,内存池的当前节点不足以分配所需大小的内存,因此遍历链表,找到下一个满足要求的节点,在该节点中分配内存,如图所示,此时在第二个节点中可以满足要求
3、当所需内存size=800字节时,内存池所有节点都不足以分配该大小的内存,因此需要重新创建一个节点,对于一个新节点,其可用空间为1024-16=1008字节>(p->max),因此一定可以满足要求
对于大块分配,如size=1000>(p->max)时,则需要调用 ngx_palloc_large(pool, size); 分配一个大块内存,并将其插入到内存池大块内存链表large链表的头部。
另一种内存分配ngx_pnalloc()与此操作基本相同,只是不再要求返回的内存地址是字节对齐的。
3、重置内存池
重置内存池的操作主要是释放large链表中的所有大块内存,重置所有小块内存的起始地址和失败次数,并将内存池的current指向内存池的头节点
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;
}
pool->current = pool; //将内存池的current指针指向内存池的头节点
pool->chain = NULL;
pool->large = NULL;
}
4、释放内存
在nginx中,用户只能主动释放大块内存,小块内存需要等待内存池销毁时才能一起释放
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p) //只能释放large链表中的内存块,内存池中的其他节点只能等到内存池销毁时统一释放
{
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) { //释放large链表中的某个大块内存p
if (p == l->alloc) {
ngx_free(l->alloc);
l->alloc = NULL;
return NGX_OK;
}
}
return NGX_DECLINED;
}
5、销毁内存池
nginx销毁内存池所执行的主要操作包括:依次执行每个清理函数,释放所有的大块内存,释放所有的小块内存
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) { //依次执行每个清理函数
if (c->handler) {
c->handler(c->data);
}
}
for (l = pool->large; l; l = l->next) { //依次释放每个大块内存
if (l->alloc) {
ngx_free(l->alloc);
}
}
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { //从内存池头节点开始,释放所有节点
ngx_free(p);
if (n == NULL) {
break;
}
}
}