Nginx源码研究四:NGINX的内存管理
关于nginx的内存使用,我们先看代码,下面是nginx_cycle.c中对全局数据结构cycle的初始化过程
pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); //申请16K的内存池 if (pool == NULL) { return NULL; } pool->log = log; cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t)); if (cycle == NULL) { ngx_destroy_pool(pool); return NULL; }
我们可以看到,nginx对内存分配做了封装,第一步:申请内存池;第二步:在内存池中分配内存。我们分别来看一下
1、内存池的申请
a、内存池的数据结构
我们先看一下内存池的数据结构
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; //指向最新申请的large内存指针,最新申请的内存指针的*next指针指向前一申请的内存指针 ngx_pool_cleanup_t *cleanup; ngx_log_t *log; };
内存池定义了7个成员变量,这里先看内存池定义的max的成员变量,我们从后面使用内存池分配内存可以看到,这个max成员变量,实际上确定了该段申请的内存使用的大小。max的大小不 大于内存的分页大小,目的是优化内存的存取。同时程序一旦请求内存大于max时候,就不会在内存池中分配内存,会另外分配一块大的内存供程序使用。
typedef struct { u_char *last; //所申请内存池大小的尾指针 u_char *end; //内存使用的尾指针 ngx_pool_t *next; ngx_uint_t failed; //*last- *end 是该申请内存段中可使用内存大小(*last会做对齐优化),
//failed值用来统计当请求分配内存大于该值的次数, //failed值大于4将不再考虑在该段内存分配。 } ngx_pool_data_t;
这里再看*current和 *next。*current指向的是可以使用的内存池,*next指针实际上将所有内存池构成一个内存池链。我们可以从下面的简图中看出
b、内存池的创建
下面看一下ngx_create_pool(NGX_CYCLE_POOL_SIZE, log) 函数,这是创建内存池的函数,过程不再细述:
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); //实际分配size大小的内存 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; p->current = p; p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; return p; }
c、内存池的销毁
申请的一段内存构成内存池,分成两部分,一部分是ngx_pool_t结构头,一部分是数据区,next指针指向下一段申请的内存池。current指针指向可以尝试分配内存的内存池。
对于这样的一个内存池,建立之后,存在一个销毁过程。可以从下面的代码中看出内存池链的销毁过程
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) { 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) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); 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; } } }
2、内存池上分配内存
先看在内存池上分配内存的代码
void * ngx_palloc(ngx_pool_t *pool, size_t size) { u_char *m; ngx_pool_t *p; if (size <= pool->max) { p = pool->current; //找到可以分配内存的内存池 do { m = ngx_align_ptr(p->d.last, 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); } return ngx_palloc_large(pool, size); //需要分配内存大于内存池的最大容量,则独立分配一块大内存供程序使用 }
这里面我们看一下 m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT),目的是保证分配内存地址的后面几位为0,至于几位,由unsigned long的大小来决定,unsigned long如果是4位,则保证分配内存地址后两位为00
#define NGX_ALIGNMENT sizeof(unsigned long) /* platform word */
#define ngx_align_ptr(p, a) \ (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
前面情况是在内存池链中,内存池的容量足够,而在不够的情况下,会新申请内存池。我们看一下申请过程
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size) { u_char *m; size_t psize; ngx_pool_t *p, *new, *current; psize = (size_t) (pool->d.end - (u_char *) pool); m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); //分配一块内存 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 = ngx_align_ptr(m, NGX_ALIGNMENT); //在新申请的内存池中分配内存 new->d.last = m + size; current = pool->current; //由于前面的内存池链的容量不够,分配失败,会给failed标识值加1,failed次数大于4的时候, //下次就不再考虑在该内存池上分配内存 for (p = current; p->d.next; p = p->d.next) { if (p->d.failed++ > 4) { current = p->d.next; } } p->d.next = new; //将新申请的内存池,挂在内存池链的末尾 pool->current = current ? current : new; return m; }