Nginx数据结构之内存池

基本结构

1. 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;
};

提供的函数

1. ngx_create_pool(): 分配并初始化一块 size 大小的内存池

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    /* 进行16字节的内存对齐分配 */
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    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;
    p->d.failed = 0;

    size = size - sizeof(ngx_pool_t);
    /* max的最大值为4095,假设一页大小为4KB.
     * 问:为什么要将pool->max字段的最大值限制在一页内存?
     * 这个字段是区分小块内存和大块内存的临界,所以这里的原因也就在于只有当
     * 分配的内存空间小于一页时才有缓存的必要(即向Nginx内存池申请),否则的
     * 话,还不如直接利用系统接口malloc()向操作系统申请。
     */
    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_pool_t 占去开头一部分(即额外的开销 overhead),Nginx实际是从该内存池里 p->d.last 指向的
起始位置开始分配给用户的。

2. ngx_palloc(): 从内存池pool中分配size大小的内存

void *ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max)
    {
        /* 第 3 个参数为 1 表示需要进行内存对齐 */
        return ngx_palloc_small(pool, size, 1);
    }    
#endif

    return ngx_palloc_large(pool, size);
}

该函数尝试从 pool 内存池里分配 size 大小的内存空间。有两种情况:

  1. 如果 size 小于等于 pool->max (称之为小块内存分配),即小于等于内存池总大小或 1 页内存(4K - 1),则调用
    ngx_palloc_small() 进行分配;
  2. 否则为大块内存分配, 即调用 ngx_palloc_large() 进行分配。

2.1 ngx_palloc_small():小块内存分配

static ngx_inline void *ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    /* current 指向当前正在使用的 ngx_pool_t 内存池首地址 */
    p = pool->current;

    do 
    {
        m = p->d.last;

        /* 进行内存对齐 */
        if (align) 
        {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        /* 若当前内存池的可用空间充足,则直接从当前内存池中分配 size 大小空间 */
        if ((size_t) (p->d.end - m) >= size)
        {
            p->d.last = m + size;

            return m;
        }

        /* 若当前内存池的可用空间不足 size,则指向下一个内存池节点 */
        p = p->d.next;

    } while (p);

    /* 若遍历完 p->d.next 所链接的内存池链表都没有足够的内存空间,则调用 ngx_palloc_block()
     * 再分配一个等同大小的内存池,并将其链接到 p->d.next 内存池链表中 */
    return ngx_palloc_block(pool, size);
}

ngx_palloc_block(): 分配新的内存池并链接到内存池链表尾部

static void *ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    /* 获取 pool 指向的内存池总大小,其中包括内存池开头占用的 sizeof(ngx_pool_t) 大小的 overhead  */
    psize = (size_t) (pool->d.end - (u_char *) pool);

    /* 分配 psize 大小并 16 字节对齐的内存空间 */
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL)
    {
        return NULL;
    }

    /* ngx_pool_t 类型的结构体指针 new 指向该新分配的内存起始地址 */
    new = (ngx_pool_t *) m;

    new->d.end    = m + psize;
    new->d.next   = NULL;
    new->d.failed = 0;

    /* 该新分配的内存池的起始为 sizeof(ngx_pool_data_t) 大小的 overhead */
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    /* 表示从新内存池中分配 size 大小的内存空间 */
    new->d.last = m + size;

    /* 遍历 pool->current 指向的内存池链表,根据 p->d.failed 值更新 pool->current */
    for (p = pool->current; p->d.next; p = p->d.next) 
    {
        /* current 字段的变动是根据统计来做的,如果从当前内存池节点分配内存总失败次数(记录在
         * 字段 p->d.failed 内) 大于等于 6 次(这是一个经验值,具体判断是 "if (p->d.failed++ > 4)"
         * 由于 p->d.failed 初始值为 0,所以当这个判断为真时,至少已经分配失败 6 次了),就将
         * current 字段移到下一个内存池节点,如果下一个内存池节点的 failed 统计数也大于等于 6 次,
         * 再下一个,依次类推,如果直到最后仍然是 failed 统计数大于等于 6 次,那么 current 字段
         * 指向刚新分配的内存池节点 */
        if (p->d.failed++ > 4) 
        {
            pool->current = p->d.next;
        }
    }

    /* 将新分配的内存池节点添加到内存池链表的尾部 */
    p->d.next = new;

    return m;
}
申请并链接新的内存池节点图

2.2 ngx_palloc_large(): 大块内存分配

static void *ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    /* 直接malloc size 大小的内存 */
    p = ngx_alloc(size, pool->log);
    if (p == NULL) 
    {
        return NULL;
    }

    n = 0;

    /* 遍历 large 指向的大块内存链表 */
    for (large = pool->large; large; large = large->next) 
    {
        /* 在内存池的使用过程中,由于大块内存可能会被释放(通过函数 ngx_pfree()),
         * 此时将空出其对应的头结构体变量 ngx_pool_large_t,所以在进行实际的链
         * 头插入操作前,会去搜索当前是否有这种情况存在。如果有,则直接将新分配的
         * 内存块设置在其 alloc 指针字段下。又如下可知,这种搜索也只是对前面 5 个
         * 链表节点进行 */
        if (large->alloc == NULL) 
        {
            large->alloc = p;
            return p;
        }

        /* 这里表示仅搜索大块内存链表中前面的 5 个节点 */
        if (n++ > 3)
        {
            break;
        }
    }

    /* 若 ngx_pool_large_t 类型的指针 large 开始时为 NULL,则从内存池 pool 中
     * 分配一块 sizeof(ngx_pool_large_t) 大小的内存,并使 large 指向该内存起始 */
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) 
    {
        ngx_free(p);
        return NULL;
    }

    /* alloc 指向上面直接 malloc 的大块内存 */
    large->alloc = p;
    /* 将该新分配的大块内存节点 large 插入到 pool->large 指向的大块内存链表头部 */
    large->next  = pool->large;
    pool->large  = large;

    return p;
}
申请第一个大块内存

继续申请大块内存

释放大块内存

3. ngx_pnalloc()

该函数与 ngx_palloc() 类似,只不过是没有进行内存对齐.

void *ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);
    }
#endif

    return ngx_palloc_large(pool, size);
}

4. ngx_pmemalign()

void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    /* 分配一块 size 大小并进行 alignment 字节对齐的内存空间 */
    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) 
    {
        return NULL;
    }

    /* 从内存池 pool 中分配一块 sizeof(ngx_pool_large_t) 大小的内存空间 */
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL)
    {
        ngx_free(p);
        return NULL;
    }

    /* 将 p 指向的新分配的大块内存挂到 large->alloc 下 */
    large->alloc = p;
    /* 然后将 large 添加到 pool->large 指向的大块内存链表头 */
    large->next  = pool->large;
    pool->large  = large;

    return p;
}

5. ngx_pfree()

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    /* 遍历 pool->large 指向的大块内存链表,在该链表中找到 p 指向的大块内存,
     * 然后释放 p 指向的内存空间,并将 alloc 置为 NULL*/
    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;
        }
    }

    /* 没有找到则返回 NGX_DECLINED */
    return NGX_DECLINED;
}

6. 其他函数

Nginx 此外提供的一套函数接口 ngx_pool_cleanup_add()、ngx_pool_run_cleanup_file()、ngx_pool_cleanup_file()、
ngx_pool_delete_file() 用于对内存与其他资源的关联管理,也就是说,从内存池里申请一块内存时,可能外部会
附加一些其他资源(比如打开的文件),这些资源的使用和申请的内存是绑定在一起的,那么在进行资源释放的时候,
希望这些资源的释放能和内存池释放一起进行(通过 handler() 回调函数),既能避免无意的资源泄漏,又省得单独
执行资源释放的麻烦。

posted @ 2018-04-23 17:27  季末的天堂  阅读(659)  评论(1编辑  收藏  举报