Nginx源码分析之--内存池

Nginx作为高性能的web服务器,它是如何管理内存的呢?

内存池的优点

  1. pool化处理,避免多次向系统申请内存,减少用户态和内核态切换,提高内存分配效率;
  2. 内存统一分配和回收,避免内存泄漏;
  3. 整块分配,避免内存过于碎片化;
  4. 内存对齐处理,减少CPU读取内存次数,提高寻址效率;

内存池相关的数据结构

结构体示意图

数据结构定义

typedef struct ngx_pool_s            ngx_pool_t;
typedef struct ngx_pool_large_s  ngx_pool_large_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;    //内存块大于max内存块的链表
    ngx_pool_cleanup_t   *cleanup;  //销毁内存池回调函数
    ngx_log_t            *log;      //错误日志指针
};

typedef struct {
    u_char               *last;     //当前内存块已使用到此处,下一次使用从此处开始
    u_char               *end;      //内存块结束位置
    ngx_pool_t           *next;     //内存池有很多内存块,通过next指针组成链表
    ngx_uint_t            failed;   //内存块分配失败次数
} ngx_pool_data_t; //小内存块(链表)

//大内存块(链表)
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;  //下一数据块地址
    void                 *alloc; //当前数据块地址
};

内存池主要函数

新建内存池

/*
 * 新建一个内存池
 */
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    //调用系统内存分配函数,分配一块堆内存,且做对齐处理
    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最大为pagesize-1(比如:x86时为4095), 即:无论小块内存空间是否足够,超过pagesize-1大小的内存均走large块分配
    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;
}

从内存池申请内存

  • 先对齐再分配内存
/*
 * 从内存池中申请一块内存
 * (1)如果申请的内存小于定义的max,则从内存池中分配内存,需先对齐再分配
 * (2)否则分配大块内存挂载到large指针上
 */
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);
}
  • 先对齐再分配内存, 并填充0
/*
 * 从内存池中申请内存,并填充0
 */
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;
}
  • 不用对齐分配内存
/*
 * 从内存池中申请一块内存 (小块内存无需先对齐)
 */
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);
}

从内存池申请内存小块内存

/*
 * 分配小块内存
 * 1. 循环遍历内存块ngx_pool_data_t链表
 * 2. 如果当前内存块剩余大小满足申请的内存大小,则移动相应指针,返回分配的内存指针
 * 3. 否则,移动到下一个内存块
 * 4. 如果所有内存块都没能满足申请的内存大小,则重新申请一块内存块挂载到链表上,并返回分配的内存地址
 */
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;

    do {
        m = p->d.last; //最后使用位置

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT); //先内存对齐, 减少CPU读取内存次数
        }

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

新建小内存块再分配

/*
 * 再次申请小块内存,并挂载到小块内存链表上,并返回当次申请的内存指针
*/
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);
    new->d.last = m + size;

    for (p = pool->current; p->d.next; p = p->d.next) {
        //调整各个内存块分配失败次数,如果失败次数大于4,则此块内存节点不再使用
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;

    return m;
}

分配大内存

/*
 * 分配大内存块
 */
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); //向系统申请size大小堆内存
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    //查找一个空的large区,如果有,则将分配的空间p交由它管理(最多3次)
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) { //ngx_pfree函数会产生空的large区
            large->alloc = p;
            return p;
        }

        if (n++ > 3) { //超过一定次数,放弃查找空的large区
            break;
        }
    }

    //创建一个新的large节点
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    //新分配的空间p交由新建的large节点管理,并且插入到large链表头部
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

销毁内存池

/*
 * 销毁内存池
 */
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;
        }
    }
}

重置内存池

/*
 * 重置内存池
 * 1. 释放大块内存
 * 2. 重置可开始分配位置指针、分配失败次数为0、可用小内存块current指针指向首个内存块
 */
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; //分配失败次数重置为0
    }

    pool->current = pool; //current指向首个内存块
    pool->chain = NULL;
    pool->large = NULL;
}

回收大块内存链表上的某个节点内存

/*
 * 回收大块内存链表上的某个节点内存
 */
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;
}