nginx内存管理采用nginx内存池+普通的malloc相结合,nginx内存池主要是分配较小的内存块,特别是分配nginx内置的描述块(如ngx_pool_t
和ngx_pool_large_t
);而ngx_alloc(malloc)主要是分配较大的内存块。这里主要是分析一下nginx内存池。
下面是nginx内存池的核心结构体
src/core/ngx_palloc.h
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;
注:ngx_pool_s被定义为nginx_pool_t
在这里先不说明结构体内每个字段的意义,咱们先从接口函数开始分析,先来分析用来初始化内存池的函数 ngx_create_pool
src/core/ngx_palloc.c
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;
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
该函数接受两个参数size和log,忽略log参数。size为创建的内存池的大小(包含内存池描述块ngx_pool_t
)。
ngx_memalign
函数其实就是一个带字节对齐的malloc函数。分配一块较大的内存块。该内存块以内存池描述块开始,d.last指向该描述块的尾部,而d.end指向内存块的底部,那么[d.last:d.end]就是内存池的空闲块(未实际分配)。d.failed是一个失败累计变量,这变量起到优化的作用,后面会详细分析它。size减去sizeof(ngx_pool_t)就是空闲块的大小。因为nginx规定,小于NGX_MAX_ALLOC_FROM_POOL
大小的为小块,从内存池中分析。current变量则是指向用于分配时查找链表的表头,所以刚开始表头指向内存池本身。
接下来分析的是分配函数ngx_pnalloc,其中p是pool的意思,意为从内存池分配空间,n为非字节对齐,所以ngx_palloc为字节对齐分配。
src/core/ngx_palloc.c
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
if (size <= pool->max) {
return ngx_palloc_small(pool, size, 1);
}
#endif
return ngx_palloc_large(pool, size);
}
该函数接受ngx_pool_t类型的参数和分配内存块大小size。如果size小于max,直接调用ngx_palloc_small分配小块内存块,否则调用ngx_palloc_large分配大块内存块。
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);
}
取当前内存池的表头current,若需要对齐则改变m。如果内存池空闲块足够,那么使空闲块缩减,即改变d.last的指向,然后直接返回内存块的指针。如果当前内存池空间不足够,则遍历内存池链表,若找不到,则调用ngx_palloc_block分配一块新的内存池,并从新内存池中分配内存块。
接下来分析分配新内存池的函数
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);
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;
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;
}
psize为内存池的大小,所以这里的内存池从调用ngx_create_pool函数时就固定了。初始化内存池,使m指向内存池描述块的位置后,再把m字节对齐,然后把d.last指向分配了size大小的内存块后的位置。接下来就是重新计算current的指向,因为链表中遍历时都是按序列的,所以通过统计分配失败次数,把current指向失败次数超4次的结点的后结点。遍历结束后,current->d.next就指向新创建的内存池,这样就能降低遍历的次数,提高效率。
接下来是分析大块内存的分配算法
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);
if (p == NULL) {
return NULL;
}
n = 0;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ > 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_alloc(实际上是malloc)分配一块大块内存块。再从首内存池描述块中的大块链表中寻找一个标记为已释放的大块内存描述块,若找到则直接利用 ,否则从内存池中分配一块大块描述块,因为结构体都是小块分配,所以都是在内存池中分配。然后使新分配的大块描述块指向新分配大块内存块,最后放入大块链表中。
最后分析内存池的销毁函数
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);
}
}
#if (NGX_DEBUG)
/*
* we could allocate the pool->log from this pool
* so we cannot use this log while free()ing the pool
*/
for (l = pool->large; l; l = l->next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
}
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p, unused: %uz", p, p->d.end - p->d.last);
if (n == NULL) {
break;
}
}
#endif
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;
}
}
}
首先执行清理操作,该操作是针对文件等资源。释放内存是有先后序列的要求,因为大块描述块是从内存池中分配的,所以必须要先释放大块描述块指向的大块内存块,然后再释放内存池。
下图是对nginx内存管理模型的总结