侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

Nginx源代码分析-内存池

2022-06-18 星期六 / 0 评论 / 0 点赞 / 77 阅读 / 7607 字

本文分析基于Nginx-1.2.6,与旧版本或将来版本可能有些许出入,但应该差别不大,可做参考内存池是一种内存分配方式.通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:

本文分析基于Nginx-1.2.6,与旧版本或将来版本可能有些许出入,但应该差别不大,可做参考

.

内存池是一种内存分配方式.通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,先申请分配一定数量的的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。通过内存池统一分配和释放内存,也可以减少内存泄漏的风险。

.

在Nginx中内存池以链表的方式组织,其结构如下图示:

为叙述方便,称一个ngx_pool_t为一个内存池,多个内存池组织成的链表为内存池链。nginx的内存分配和释放多在内存池链中进行。

注意ngx_pool_large_t结构体和ngx_pool_cleanup_t结构体所占用的内存也在蓝色的used内存之中,为画图清晰,才没有标明,而大块内存是内存池链之外的内存。

<!-- lang: cpp -->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;};

如图所示,ngx_pool_t结构体的第一个成员d是描述内存池信息的一个结构,类型为ngx_poot_data_t,其中的pool->d.last指向未使用内存的起始位置,pool->d.end指向末尾位置,next指向下一个内存池,failed的意思是内存池链分配内存失败的次数。pool->max记录了当前内存池链中每个内存池的最大可分配内存量,是一常量,在创建时初始化。

##创建内存池链 ngx_create_pool(size_t size, ngx_log_t *log)##创建内存池链的过程比较简单,就是分配size大小的空间,并对空间开头位置的ngx_pool_t结构体进行初始化,此过程的结果是生成了一个链表(内存池链),其中只有一个元素(内存池)。

##内存管理##

内存池链分配内存的过程分两种情况:

  • (A)当请求的内存太大时(大于pool->max),调用ngx/_palloc/_large在内存池外进行大块内存分配,并用pool->large指向的链表记录下来首地址,并返回;
  • (B)当请求的内存不太大时(小于等于pool->max),从pool->current开始进行遍历内存池链,

*    (B1)若某个内存池中空闲内存够大可满足请求,则把这个内存池的pool->d.last后移,并返回划分出的内存首地址,*    (B2)若遍历结束仍没有足够大的空闲内存供分配时,调用ngx/_palloc/_block函数新建一个内存池加到当前内存池链的结尾,并在这个新的内存池中划分出适当大小的内存返回。

在(B2)情况下,调用ngx_palloc_block函数,在函数中进行了一个重要的操作,即把内存池链中从pool->current开始的每个内存池的failed加1,若某个内存池的failed次数超过4次,测将pool->current后移,这样的好处是,若内存池的failde次数超4次时就表明这个内存池已经没多少空间供分配了,以后再分配内存时就不必在这个内存池上花费时间了,提高分配效率。

ngx_palloc和ngx_pnalloc的唯一区别是前者分配的内存是按NGX_ALIGNMENT大小字节对齐的。

<!-- lang: cpp -->#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */

ngx_pcalloc是在ngx_palloc的基础上,对分配的内存初始化为0。

ngx_pfree根据pool->large链表中的大块内存分配记录,对相应的大块内存进行释放。

<!-- lang: cpp -->ngx_int_tngx_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_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,                           "free: %p", l->alloc);            ngx_free(l->alloc);            l->alloc = NULL;            return NGX_OK;        }    }    return NGX_DECLINED;}

##销毁内存池链ngx_destroy_pool(ngx_pool_t *pool) ##

内存池链的首节点中的cleanup指向的是一ngx_pool_cleanup_t链表,记录了销毁内存池链之前需要进行的操作和相应参数,类似于析构函数,即在真正释放内存之前进行些清理操作,比如关闭文件等。

其实在ngx_pool_cleanup_add中,内存池链并没有注册相应的清理函数,而只是新建ngx_pool_cleanup_t节点加入到cleanup所指向链表的开头,此节点中并无内容,所以使用时还需要在这个函数调用之后进行赋值操作。

<!-- lang: cpp -->struct ngx_pool_cleanup_s {    ngx_pool_cleanup_pt   handler;//是一个函数指针    void                 *data;    ngx_pool_cleanup_t   *next;};

销毁内存池链的操作在函数ngx_destroy_pool中,共有三个操作,首先遍历cleanup链表分别执行对应的清理操作,然后遍历large链表释放所有分配的大块内存,最后释放内存池所占用的内存。

在ngx_palloc.c文件中还有个ngx_reset_pool函数,函数中首先释放所有大块内存,然后复位pool->d.last位置,但疑惑的是,这个函数中并未对pool->current和pool->d.failed进行操作,其作用不三不四,不怎么懂。

此外针对文件清理这样典型的清理操作,此文件中有对应的三个函数(后面两个是ngx_pool_cleanup_pt类型的handler) 。

<!-- lang: cpp -->voidngx_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;            }        }    }}voidngx_pool_cleanup_file(void *data){    ngx_pool_cleanup_file_t  *c = data;    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: 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/" failed", c->name);    }}voidngx_pool_delete_file(void *data){    ngx_pool_cleanup_file_t  *c = data;    ngx_err_t  err;    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",                   c->fd, c->name);    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/" failed", 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/" failed", c->name);    }}

广告 广告

评论区