typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t; 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; };上述各变量的解释将在下文中陆续予以介绍。
大块内存
struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };alloc 指向分配的内存地址,用malloc进行分配,而next则指向下一个大块内存结构。
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); 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; }主要就是创建第一块内存块,初始化相关变量,last与end限定可用空间, max 为 size 与 NGX_MAX_ALLOC_FROM_POOL的小者。
void * ngx_memalign(size_t alignment, size_t size, ngx_log_t *log) { return memalign(alignment, size); }
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); }
typedef void (*ngx_pool_cleanup_pt)(void *data); struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next; };handler指向清理函数,data为传递给handler的参数,next指向下一个清理结构。
ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) { ngx_pool_cleanup_t *c; c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); if (size) { c->data = ngx_palloc(p, size); if (c->data == NULL) { return NULL; } } else { c->data = NULL; } c->handler = NULL; c->next = p->cleanup; p->cleanup = c; return c; }简单来说做了两件事:其一,分配ngx_pool_cleanup_t 与 data 空间;其二,将新分配的结构串在cleanup链上。
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; } } }内存池的销毁总共分三步:
上面介绍了清理函数的调用过程,下面说一下,清理函数的挂载过程。以关闭文件描述符为例:
数据结构:
typedef struct { ngx_fd_t fd; u_char *name; ngx_log_t *log; } ngx_pool_cleanup_file_t; void ngx_pool_cleanup_file(void *data) { ngx_pool_cleanup_file_t *c = data; ngx_close_file(c->fd); }
ngx_pool_cleanup_t *cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t)); if (cln == NULL) { return NGX_ERROR; } cln->handler = ngx_pool_cleanup_file; ngx_pool_cleanup_file_t *cln_f = cln->data; cln_f->fd = /* fd */; cln_f->name = /* name */; cln_f->long = pool->log;
current 变量:
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size) { m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); new = (ngx_pool_t *) m; new->d.failed = 0; current = pool->current; 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; }
其一:ngx_palloc负责分配内存,由current变量指向目前正在使用的内存块,而current之前的内存块都已经被耗尽。
其二:ngx_palloc_block 被调用时,说明已有内存池的空闲内存不足以分配size大小,因此需要增加新内存块。而此时,不能说明内存池可用内存已经耗尽,可能由于此次申请的size过大。所以,不能轻易调整current的指向。
因此nginx有一个策略是,分配新块后,从current指向的内存块依次向后扫描,failed > 4, 则默认current指向的内存块
可用空间已经耗尽,需要调整current,否则不予调整。(failed++说明:调用ngx_palloc_block说明前面分配失败)
第一块,第二块内存已经耗尽,剩余小块不足以分配,直接舍弃。第四块是由于申请了一个过大的size导致新建内存块,但第三块仍然有未用空间,所以current不能指向第四块。
内存释放:
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) { ngx_pool_large_t *l; for (l = pool->large; l; l = l->next) { if (p == l->alloc) { ngx_free(l->alloc); l->alloc = NULL; return NGX_OK; } } return NGX_DECLINED; }可见,内存的释放只针对大块内存,仅仅释放alloc指向的内存,而不释放ngx_pool_large_t结构体,这个结构体是要被重用的。
static void * ngx_palloc_large(ngx_pool_t *pool, size_t size) { p = ngx_alloc(size, pool->log); 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(pool, sizeof(ngx_pool_large_t)); large->alloc = p; large->next = pool->large; pool->large = large; return p; }从头开始查找,若找到空余位置,则放置大块内存。如果前三块都没有空闲ngx_pool_large_t结构体,则重新分配。
1. 避免内存泄露:分配的内存会在请求结束时一次性销毁
2. 加快分配速度:减少系统调用次数,快速分配内存
3. 减少内存碎片:其一,大小内存块区别对待,降低内部碎片; 其二,一次申请一片连续区域,减少外部碎片。