1、内存池关键数据结构

1.1 描述内存池的总数据结构

// 内存池数据结构

struct ngx_pool_s

{

    ngx_pool_data_t d; // 内存池的数据区域

    size_t max; // 最大每次可分配内存

    ngx_pool_t *current; // 指向当前的内存池指针地址。ngx_pool_t链表上最后一个缓存池结构

    ngx_chain_t *chain; // 缓冲区链表

    ngx_pool_large_t *large; // 存储大数据的链表

    ngx_pool_cleanup_t *cleanup; // 可自定义回调函数,清除内存块分配的内存

    ngx_log_t *log; // 日志

}

1.2 内存池数据区描述数据结构

// 把一个未命名的结构直接取名ngx_pool_data_t

typedef struct

{

    u_char *last; // 该内存池内,可用内存的起始地址

    u_char *end; // 该内存池内,内存池结束地址

    ngx_pool_t *next; // 指向下一个内存池的指针

    ngx_uint_t failed; // 失败次数,具体含义还未知

} ngx_pool_data_t;

1.3 内存池大块内存描述数据结构

// 大内存块描述

struct ngx_pool_large_s

{

    ngx_pool_large_t *next; // 指向下一个large节点

    void *alloc; // 指向大内存块地址指针

};

1.4 可自定义清除函数内存描述

// typedef定义函数指针

// 给变量类型定义一个别名,

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;

struct ngx_pool_cleanup_s

{

    ngx_pool_cleanup_pt handler; // 清除函数指针

    void *data; // 内存指针,作为handler的入参

    ngx_pool_cleanup_t *next; // 指向下一个cleanup节点

};

1.5 内存池原理

内存池是一个链表,链表的每个节点上都有分配空间,前面部分空间用于存储『ngx_pool_s』结构自身数据,剩下空间、链表通过此结构进行管理。

『ngx_pool_data_t』结构记录了当前节点可用内存地址的起(last)、止(end)地址以及下一个内存池节点,从而形成链表。

当申请的空间大于max时,会按大块内存进行分配,用使用『ngx_pool_large_s』进行管理,也是形成链表。

如图:

nginx worker占用内存设置_nginx

 

2、关键函数

2.1 申请/销毁内存基础函数(内存在堆上)

文件路径:src/os/unix/ngx_alloc.c

void *ngx_alloc(size_t size, ngx_log_t *log)

{

    void *p;

    p = malloc(size);

    if (p == NULL)

    {

    ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,

    "malloc(%uz) failed", size);

    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;

}


void *ngx_calloc(size_t size, ngx_log_t *log)

{

    void *p;

    p = ngx_alloc(size, log);

    if (p)

    {

        ngx_memzero(p, size);

    }

    return p;

}

2.2 创建内存池节点

文件路径:src/core/ngx_palloc.c

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t *p;
    // malloc内存,大小为size
    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);
    // 设置本内存池最大可用空间
    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;
}

2.3 销毁内存池

共销毁三个区域:

  • cleanup存储区,调用自定义的清理函数完成释放,全链表完成释放。
  • large区域,直接free掉,全链表完成释放
  • 内存池节点内存销毁,全链表完成释放。
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);
        }
    }

#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool->large; l; l = l->next)
    {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
    }

    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 (l = pool->large; l; l = l->next)
    {
        if (l->alloc)
        {
            ngx_free(l->alloc);
        }
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next)
    {
        ngx_free(p);

        if (n == NULL)
        {
            break;
        }
    }
}

2.3 重置内存池

  • large内存区域,全链表释放。
  • 全链表更新内存池节点last指针到初始位置。
void ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t *p;
    ngx_pool_large_t *l;

    for (l = pool->large; l; l = l->next)
    {
        if (l->alloc)
        {
            ngx_free(l->alloc);
        }
    }

    for (p = pool; p; p = p->d.next)
    {
        p->d.last = (u_char *)p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

2.4 使用内存池分配空间

如果申请的内存空间小于max值,则走小内存空间申请,反之走大块内存申请。

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

    return ngx_palloc_large(pool, size);
}

2.4.1 小块内存申请

如果当前内存池中有size长度的空间,则直接分配,否则进行扩容操作,新申请一个节点

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;

    p = pool->current;
    // 如果当前内存池中有size长度的空间可用,直接返回,否则创建新的内存池节点,进行扩容
    do
    {
        m = p->d.last;

        if (align)
        {
            m = ngx_align_ptr(m, 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);
}

2.4.2 新建内存池节点

  • 新申请一个节点,为本次申请提供空间。
  • 新申请节点接入链表中。
  • 需要注意的是,如果从当前pool申请内存失败超过4次,则current跳转到当前链表的最后一个节点,同时将新申请的节点挂载到链表尾。
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char *m;
    size_t psize;
    ngx_pool_t *p, *new;

    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);
    // 将last指针向下移动,跳过本次申请占用的部分
    new->d.last = m + size;

    for (p = pool->current; p->d.next; p = p->d.next)
    {
        // 这里体现的应该是当前pool节点以及其后面节点内存不足了
        // 而且如果失败大于4次,则移动current到当前链表最后一个节点
        if (p->d.failed++ > 4)
        {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

2.4.3 申请大内存快

  • 申请内存空间。
  • 判断large链表上前3个节点是否有释放空间节点(large->next为null),如果没有则将新申请的节点插入到表头。
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;
    // 查找large链表上是否有释放节点,最多查找3个
    for (large = pool->large; large; large = large->next)
    {
        if (large->alloc == NULL)
        {
            large->alloc = p;
            return p;
        }

        if (n++ > 3)
        {
            break;
        }
    }
    // 分配large结构
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL)
    {
        ngx_free(p);
        return NULL;
    }
    // 将large结构插入到pool current节点的large节点之后
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

2.5 大内存块释放

释放指定的大内存块。

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

2.6 分配cleanup内存块

  • 申请内存块。
  • 申请ngx_pool_cleanup_t结构。
  • 将该结构插入到当前pool->cleanup后面
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t *c;
    // 开辟ngx_pool_cleanup_t结构存储空间
    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;
    // 将存储结构插入链表中
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

2.7 自定义函数清理文件

使用自定义的handler清理内存(主要是文件)

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

2.8 关闭文件回调函数

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

2.9 删除文件回调函数

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