这里只介绍nginx_pool主要的大小内存申请、回收及其高效的内存分配机制具体的实现。nginx
这个函数是内存池的建立函数。 第一个参数是内存池的大小(一次最大可申请的小块空间大小),其实实际的小块空间单次最大可申请大小还须要用size减去sizeof(ngx_pool_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; //日志信息 }; /*内存池数据块信息*/ typedef struct { u_char *last; //这一块内存块中能够分配出去的内存地址 u_char *end; //指向这一块内存最后 ngx_pool_t *next; //下一个内存块 ngx_uint_t failed; //在这一块内存块上分配失败的次数 } ngx_pool_data_t;
因此size最小应该为sizeof(ngx_pool_t),其最大不能超过NGX_MAX_ALLOC_FROM_POOL:函数
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
也就是最大不能够超过ngx_pagesize - 1。第二个参数是日志信息参数。ui
ngx_memalign()是为内存池的建立申请内存的函数,它实际调用的是ngx_alloc()函数:
#define ngx_memalign(alignment, size, log) ngx_alloc(size, log)
ngx_alloc()函数的实现:
void *ngx_alloc(size_t size, ngx_log_t *log) { void *p; p = malloc(size); //具体申请内存是经过malloc if (p == NULL) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "malloc(%uz) failed", size); } ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size); return p; }
与ngx_alloc()函数类似的一个函数ngx_calloc(),比ngx_alloc多了一步初始化的操做,这一点和malloc和calloc比较类似。this
p->d.last = (u_char *) p + sizeof(ngx_pool_t);// 1 p->d.end = (u_char *) p + size;// 2 p->d.next = NULL;// 3 p->d.failed = 0;// 4 size = size - sizeof(ngx_pool_t);// 5 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;// 6 p->current = p;// 7 p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log;
1:数据块last指针指向ngx_pool_s结构体下方,表示可用空间的开头。2:数据块end指针指向内存块底部,表示可用空间的边界。debug
3:下一个可用内存块指针置NULL,由于内存池是增加的,刚开始只有一块小内存,内存块链是一个不够再申请一个,这样一个个增长上去的。因此这里就是NULL。指针
4:failed这一块内存块上分配失败的次数置零,当次数超过必定值时,这一块的内存上的current就会指向一个下一个失败次数低于限制的可用的内存块。下一次直接能够从可用的内存块上分配,减小遍历内存块链的时间,我认为这是ngx_pool在内存分配上采起的一个高效的措施。日志
5:这个size是表示当前内存块上实际可用的空间大小。减去ngx_pool_s的大小就是剩下的实际能够用来分配的大小。code
6:max用来区分申请的大小是小块内存仍是大块内存。这里对它的大小作了一个限制,就是最大为ngx_pagesize - 1。对象
7:current表示下一个能够分配的内存块,初始化为当前的内存块,由于当前内存块是可用的。内存
第一个参数是内存池头部结构体指针,第二个是要申请的大小,第三个参数是一个内存对齐的标志,1就是须要按内存对齐申请,0就是不内存对齐。
这是内存对齐函数,其实是一个宏:
#define ngx_align_ptr(p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
其中a是sizeof(unsigned long),也就是说32位系统按照四字节对齐,64位系统按照8字节对齐。这个对齐方式是这样的,拿32位系统,按照四字节对齐方式:p + 3(0000 0000 0000 0011),若是p的最低两位不是0(没有对齐),便会向上进位,而后再与上~(0000 0000 0000 0011) = (1111 1111 1111 1100),这样最低位的全为0且会大于原来p,不会越界。若是最低两位是0,那么加上3再与上~(3),就仍是原来的值。这样就实现了内存对齐。那么为何要内存对齐呢?由于内存对齐,能够大大增长cpu读取内存的效率,减小cpu没必要要的I/O次数。
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; //分配申请的小内存 /*这里的循环就是把current移到failed次数低于四次的小内存块上*/ 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; }
大块内存分配函数。第一个参数是内存池头pool,第二个参数是大小。
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_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; //当前结构体插入到大块内存链
这一步是在大块内存链上找一个空的大块内存结构体把当前的大块内存地址放到里面进行管理,这里的n,就是又一个高效的地方,为了减小遍历链表的时间,只要遍历超过三次,便再也不查找,会在小块内存上申请一个小空间来管理这块大内存,而后把大块内存结构体插入到当前的链表上。
大块内存释放函数。第一个参数是内存池pool,第二个是要释放的内存地址。
这里的大块内存是用malloc申请的,因此释放要用free。释放以后,大块内存的结构体不用释放,由于是从小块内存上申请的空间,因此是没办法释放的,这样也有一个好处就是之后的大块内存能够节省申请空间的时间,更加高效。
内存池销毁函数。若是说内存池是一个对象,那么销毁函数就是一个析构函数,把全部资源释放,可是仍是须要把小块内存上的全部对象都析构。而后再free大块的内存,最后就是释放全部小块内存。下面是源码:
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); } } /*debugger模式下对内存池的跟踪*/ #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; } } }
内存池重置函数。参数只有一个要重置的内存池。
reset和destory的区别在于,reset不用释放全部小块内存,重置以后能够继续使用;destory以后,这个内存池的全部资源都释放,内存池已经不存在了。因此reset须要释放全部大块内存释放,而后把全部的小块重置为可分配状态。源码:
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; pool->chain = NULL; pool->large = NULL; }