??为了自身使用的方便,Nginx封装了很多有用的数据结构,比如ngx_str_t ,ngx_array_t, ngx_pool_t 等等,对于内存池,nginx设计的十分精炼,值得我们学习,本文介绍内存池基本知识,nginx内存池的结构和关键代码,并用一个实际的代码例子作了进一步的讲解
?
一、内存池概述
? ? 内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存。
? ?内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减少程序员在编写代码中对内存的关注等
? ?目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等
二、nginx内存池综述
? ? ?nginx为每一个层级都会创建一个内存池,进行内存的管理,比如一个模板,tcp连接,http请求等,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操作系统。
? ? ?在分配的内存上,nginx有小块内存和大块内存的概念,小块内存 nginx在分配的时候会尝试在当前的内存池节点中分配,而大块内存会调用系统函数malloc向操作系统申请
? ? ?在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_pool 和 ngx_reset_pool的时候一并释放
? ? ?区分小块内存和大块内存的原因有2个,
? ? ?1、针对大块内存 ?如果它的生命周期远远短于所属的内存池,那么提供一个单独的释放函数是十分有意义的,但不区分大块内存和小块内存,针对大的内存块 便会无法提前释放了
? ? ?2、大块内存与小块内存的界限是一页内存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值通过调用getpagesize()获得),大于一页的内存在物理上不一定是连续的,所以如果分配的内存大于一页的话,从内存池中使用,和向操作系统重新申请效率差不多是等价的
? ? ? nginx内存池提供的函数主要有以下几个
? ? ?
三、nginx内存池详解
? ? nginx使用了ngx_pool_s用于表示整个内存池对象,ngx_pool_data_t表示单个内存池节点的分配信息,ngx_pool_large_s表示大块内存
它们的结构和含义如下
struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};
next: ? 指向下一个大块内存
alloc:指向分配的大块内存
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;
};
d:内存池的节点的数据分配情况
max: ? ? ?单个内存池节点容量的最大值
current: 指向当前的内存池节点
chain: 指向一个ngx_chain_t结构
large: ?指向大块内存链表
cleanup:释放内存池的callback
log: ? ? 用于输出日志
typedef struct {
? ?u_char *last;
? ?u_char *end;
? ?ngx_pool_t *next;
? ?ngx_uint_t failed;
} ngx_pool_data_t;
last: ? ?内存池节点已分配的末位地址,下一次分配会尝试从此开始
end: 内存池节点的结束位置
next:next指向下一个内存池节点
failed: 当前内存池节点分配失败次数
? ? ? ?nginx 内存池示意图1
? ? 在分配内存的时候,nginx会判断当前要分配的内存是小块内存还是大块内存,大块内存调用ngx_palloc_large进行分配,小块内存nginx先会尝试从内存池的当前节点(p->current)中分配,如果内存池当前节点的剩余空间不足,nginx会调用ngx_palloc_block新创建一个内存池节点并从中分配,
如果内存池当前节点的分配失败次数已经大于等于6次(p->d.failed++ > 4),则将当前内存池节点前移一个
(这里有个需要注意的地方,当当前内存节点的剩余空间不够分配时,nginx会重新创建一个ngx_pool_t对象,并且将pool.d->next指向新的ngx_pool_t,新分配的ngx_pool_t对象只用到了ngx_pool_data_t区域,并没有头部信息,头部信息部分已经被当做内存分配区域了)
? ? ? ? ? ? ? ? ?nginx 内存池示意图2(新建了一个内存池节点和分配了2个大块内存,其中一个已经释放)?
关键代码
创建内存池代码
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 ); //间接调用了posix_memalign分配内存 ???? 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; //由于当前内存池只有一个节点所以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; } |
ngx_palloc分配函数代码
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); //分配大块内存 } |
消耗内存池
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); ???????? } ???? } ???? #if (NGX_DEBUG) ???? /* ???? * we could allocate the pool->log from this pool ???? * so we cannot use this log while free()ing the pool ???? */ ???? 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 ?(p = pool, n = pool->d.next;? /* void */ ; p = n, n = n->d.next) { ???????? ngx_free(p); //间接调用free释放内存 ???????? if ?(n == NULL) { ???????????? break ; ???????? } ???? } } |