Nginx源代码分析-内存池

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

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

在Nginx中内存池以链表的方式组织,其结构如下图示:
Nginx源代码分析-内存池_第1张图片

为叙述方便,称一个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_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_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 -->
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;
            }
        }
    }
}


void
ngx_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);
    }
}


void
ngx_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);
    }
}

你可能感兴趣的:(nginx,源代码分析,内存池,nginx源代码分析,nginx内存池)