接下来,分析Nginx内存池的内存分配和释放。
6. 内存操作
6.1内存分配
void * ngx_palloc(ngx_pool_t * pool,size_t size); void * ngx_pnalloc(ngx_pool_t * pool,size_t size); void * ngx_pcalloc(ngx_pool_t * pool,size_t size);对于这三个内存分配函数,内存分配方式类似,但也有部分区别,
ngx_palloc:分配的内存会对齐;
ngx_pnalloc:分配的内存不会对齐;
ngx_pcalloc:会分配的内存进行清0。
ngx_palloc 函数首先会判断当前申请的内存大小是否超过当前内存池pool的max, 如果大于则使用ngx_palloc_large在大链表里分配一段内存并返回; 如果小于则尝试从链表的pool-> current开始遍历链表,找到出一个可以分配的内存,当链表里的任何一个节点都无法分配内存的时候,就调用ngx_palloc_block生成链表里一个新的节点,并且在新节点里分配内存并返回,同时,还会将pool->当前指针指向新的位置(从链表里面pool-> d.failed小于等于4的节点里找出)。
首先,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 ){ ///更新last,然后返回前面保存的last 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); }
ngx_pnalloc函数不要求内存对齐,其他和ngx_palloc相同。
void * ngx_pnalloc(ngx_pool_t * pool,size_t size) { u_char * m; ngx_pool_t * p; if(size <= pool-> max){ p = pool-> current; do{ m = p-> d.last; 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); }
内存分配函数ngx_calloc,
void * ngx_pcalloc(ngx_pool_t * pool,size_t size) { void * p; p = ngx_palloc(pool,size); if(p){ ngx_memzero(p,size); } return p; }
当链表里的任何一个节点都无法分配内存的时候,就调用 ngx_palloc_block 生成链表里的一个新的节点,并在新的节点里分配内存并返回。
/ *重新分配一块内存池,然后链接到当前内存池的数据区指针* / static void * ngx_palloc_block(ngx_pool_t * pool,size_t size) { u_char * m; 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; //在ngx_palloc中分配内存是从目前开始的, //而这里也就是设置电流为比较新分配的内存 current = pool->current; / *这里遍历所有的子内存池, *这里主要是通过失败来标记重新分配子内存池的次数, *然后找出最后一个大于4的,标记它 *当失败大于4说明我们至少请求了4次内存分配,都不能满足我们的请求, *此时我们就假设老的内存都已经没有空间了,因此我们就从比较新的内存块开始 * / for(p = current; p-> d.next; p = p-> d.next){ if(p-> d.failed ++> 4){ current = p->d.next; } } ///链接到最后一个内存池后面 p-> d.next = new; ///如果current为空,则current就为new pool-> current = current?current:new; return m; }
如果当前申请的内存大小超过pool- > MAX,就进行申请大块内存。
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; ///开始遍历大链表,如果有ALLOC(也就是内存区指针)为空, //则直接指针赋值然后返回一般第一次请求大块内存都会。 for(large = pool->large; large; large => next){ if(large->alloc == NULL){ large-> alloc = p; return p; } if(n ++> 3){ break; } } //执行ngx_pool_large_t结构体的分配,用于来管理large内存块 large = ngx_palloc(pool,sizeof(ngx_pool_large_t)); if(large == NULL){ ngx_free(P); returnNULL; } //然后链接数据区指针p到大。这里可以看到直接插入到大链表的头。 large-> alloc = p; large-> next = pool-> large; pool-> large = large; return p; }
其中,调用了ngx_alloc进行内存分配,实际上ngx_alloc函数就是调用了的malloc的函数。
6.2内存释放
在nginx中,只有大块内存提供了免费的接口,而内存池中的小块内存从来不释放的,小块内存只有当整个内存池被desrtoy掉时,才会被释放。
ngx_pfree只用于释放大内存。从内存池的大型链表中找到p,然后自由掉它。
ngx_int_t ngx_pfree(ngx_pool_t * pool,void * p) { ngx_pool_large_t * 1; //从内存池的大型链表中找到p,然后free掉它。 for(l = pool-> large; l; l = l->next) { if(p == l->alloc){ ngX_log_debug1(NGX_LOG_DEBUG_ALLOC,pool-> log,0, “free:%p”,l-> alloc); ngx_free(1-> ALLOC); l-> alloc = NULL; return NGX_OK; } } return NGX_DECLINED; }接下来,ngx_poll_cleanup_add函数就是添加一个nax_pool_cleanup_t到当前的内存池上,然后返回。
而ngx_pool_cleanup_t这个主要是当内存池销毁时,我们可能需要做一些清理工作,此时就能添加这些清理处理程序到内存池中,然后当内存池要释放就会自动调用。
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(c == NULL){ return NULL; } if(size){ c-> data = ngx_palloc(p,size); if(c-> data == NULL){ return NULL; } } else { c-> data = NULL; } c-> handler = NULL; //添加一个ngx_pool_cleanup_t到当前的内存池上 c-> next = p-> cleanup; p-> cleanup = c; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC,p-> log,0,“add cleanup:%p”,c); return c; }
对内存池进行文件清理操作,即执行处理程序ngx_pool_cleanup_file。
void ngx_pool_run_cleanup_file(ngx_pool_t * p,ngx_fd_t fd) { ngx_pool_cleanup_t * c; ngx_pool_cleanup_file_t * cf; for(c = p-> cleanup; C; c = c-> next){ if(c-> handler == ngx_pool_cleanup_file){ cf = c-> data; if(cf-> fd == fd){ c-> handler(cf); c-> handler = NULL; return ; } } } }
函数ngx_pool_cleanup_file为关闭数据指定的文件句柄。
void ngx_pool_cleanup_file(void * data) { ngx_pool_cleanup_file_t * c = data; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC,c-> log,0,“文件清理:fd:%d”, c-> FD); if(ngx_close_file(c-> fd)== NGX_FILE_ERROR){ ngx_log_error(NGX_LOG_ALERT,c-> log,ngx_errno, ngx_close_file_n“\”%s \“失败”,c->名称); } }
函数ngx_pool_delete_file为删除数据指定的文件句柄。
void ngx_pool_delete_file(void * data) { ngx_pool_cleanup_file_t * c = data; ngx_err_t err; if(ngx_delete_file(c-> name)== NGX_FILE_ERROR){ err = ngx_errno; if(err!= NGX_ENOENT){ ngx_log_error(NGX_LOG_CRIT,c-> log,err, ngx_delete_file_n“\”%s \“失败”,c->name); } } if(ngx_close_file(c-> fd)== NGX_FILE_ERROR){ ngx_log_error(NGX_LOG_ALERT,c-> log,ngx_errno, ngx_close_file_n“\”%s \“失败”,c->名称); } }
--------------------------------------------------
参考资料:
“nginx的源码剖析” - simohayha