nginx-----内存池

nginx对内存的管理由其内部的内存池实现,nginx在/src/os/unix/ngx_alloc.h/.c中定义了基本的内存分配操作,如malloc等。内存池部分的操作在/src/core/ngx_palloc.(h/c)中实现。

一、内存池相关数据结构

一个基本的nginx内存池结构如下所示

由上图可知,nginx通过将多个内存块串联成链表以形成一个内存池的,其中每个内存块都包含了一个固定头部(如每个内存块头部的蓝色部分d),固定头部记录了每个内存块的内存使用相关信息,同时每个内存池还包含了一个头部(如链表首个元素的红色部分),其中保存了内存池的相关信息。

1、内存池中每个内存块的头部

1typedef struct { //内存池中每个节点的固有部分 2 u_char *last; //指向每个节点的实际可用空间的起始地址 3 u_char *end; //指向每个节点的实际可用空间的结束地址 4 ngx_pool_t *next; //指向内存池中的下一个节点 5 ngx_uint_t failed; //当前节点分配失败的次数 6} ngx_pool_data_t; 7

sizeof(ngx_pool_data_t)=16字节,即内存池中除了头节点外的其他每个节点的起始16字节都是用来保存该内存块的使用信息的。

2、内存池头部结构

1struct ngx_pool_s { //每个内存池的固有部分 2 ngx_pool_data_t d; //内存池当前节点的头部 3 size_t max; //内存池每次可分配的最大空间 4 ngx_pool_t *current; //指向内存池中当前可用的结点 5 ngx_chain_t *chain; 6 ngx_pool_large_t *large; //大块内存链表 7 ngx_pool_cleanup_t *cleanup; //相关资源的清理函数组成的链表 8 ngx_log_t *log; 9}; 10

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、创建内存池

1ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log) 2{ 3 ngx_pool_t *p; 4 5 p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); //分配以16字节对齐的size大小的内存块,前面包含内存池的固有部分40字节 6 p->d.last = (u_char *) p + sizeof(ngx_pool_t); //将last指针指向当前内存块中可用部分的起始地址 7 p->d.end = (u_char *) p + size; //将end指针指向当前内存块可用空间的结束地址 8 p->d.next = NULL; 9 p->d.failed = 0; 10 11 size = size - sizeof(ngx_pool_t); //当前内存块总大小减去内存池固有部分大小,即为当前内存块的剩余可用空间 12 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //每次最多可分配的内存大小 13 14 p->current = p; 15 p->chain = NULL; 16 p->large = NULL; 17 p->cleanup = NULL; 18 p->log = log; 19 20 return p; 21} 22 23

eg:当执行完**ngx_create_pool(1024,log)**后,得到的结果如下:

则当前内存池只有一个节点,在该节点的前40字节中记录了当前内存池的相关信息,而从p->d.last到p->d.end之间的984字节可用于内存分配,因此p->max=984

2、内存申请

1void * 2ngx_palloc(ngx_pool_t *pool, size_t size) 3{ 4 u_char *m; 5 ngx_pool_t *p; 6 7 if (size <= pool->max) { //1、小块分配,所需的空间可以在当前内存池中一次分配完 8 p = pool->current; //找到内存池当前可用的节点 9 do { 10 m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); //m指向当前内存块可用空间的起始地址 11 if ((size_t) (p->d.end - m) >= size) { //1、1.当前内存块的剩余可用空间大于所需空间 12 p->d.last = m + size; //更新当前内存块可用空间的起始地址 13 return m; //并返回分配结果m 14 } 15 p = p->d.next; //1、 2当前内存块中剩余空间不足以满足要求,则依次变了内存池的下一个节点,以找到有足够剩余空间的内存块,并分配内存 16 } while (p); 17 return ngx_palloc_block(pool, size); //1、3内存池中所有节点都不满足要求,则重新分配一个内存块 18 } 19 return ngx_palloc_large(pool, size); //2、大块分配,分配一个大块内存,并加入到large链表中 20} 21 22

由上可知,在nginx内存池中申请内存主要分为两种情况:

  1. 待分配的内存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指向内存池的头节点

1void 2ngx_reset_pool(ngx_pool_t *pool) 3{ 4 ngx_pool_t *p; 5 ngx_pool_large_t *l; 6 7 for (l = pool->large; l; l = l->next) { //释放所有大块内存 8 if (l->alloc) { 9 ngx_free(l->alloc); 10 } 11 } 12 13 for (p = pool; p; p = p->d.next) { //重置所有小块内存的起始地址和分配失败次数 14 p->d.last = (u_char *) p + sizeof(ngx_pool_t); 15 p->d.failed = 0; 16 } 17 18 pool->current = pool; //将内存池的current指针指向内存池的头节点 19 pool->chain = NULL; 20 pool->large = NULL; 21} 22

4、释放内存

在nginx中,用户只能主动释放大块内存,小块内存需要等待内存池销毁时才能一起释放

1ngx_int_t 2ngx_pfree(ngx_pool_t *pool, void *p) //只能释放large链表中的内存块,内存池中的其他节点只能等到内存池销毁时统一释放 3{ 4 ngx_pool_large_t *l; 5 6 for (l = pool->large; l; l = l->next) { //释放large链表中的某个大块内存p 7 if (p == l->alloc) { 8 ngx_free(l->alloc); 9 l->alloc = NULL; 10 return NGX_OK; 11 } 12 } 13 return NGX_DECLINED; 14} 15

5、销毁内存池

nginx销毁内存池所执行的主要操作包括:依次执行每个清理函数,释放所有的大块内存,释放所有的小块内存

1void 2ngx_destroy_pool(ngx_pool_t *pool) 3{ 4 ngx_pool_t *p, *n; 5 ngx_pool_large_t *l; 6 ngx_pool_cleanup_t *c; 7 8 for (c = pool->cleanup; c; c = c->next) { //依次执行每个清理函数 9 if (c->handler) { 10 c->handler(c->data); 11 } 12 } 13 14 for (l = pool->large; l; l = l->next) { //依次释放每个大块内存 15 if (l->alloc) { 16 ngx_free(l->alloc); 17 } 18 } 19 20 for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { //从内存池头节点开始,释放所有节点 21 ngx_free(p); 22 if (n == NULL) { 23 break; 24 } 25 } 26} 27

代码交流 2021