nginx源码学习——内存池

主要数据结构

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;
};
typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t            failed;
} ngx_pool_data_t;

创建内存池

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;
    //以16字节对齐的方式开辟内存池的第一个块,大小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;
    //用户可用的实际内存大小如果大于系统页大小(32位linux一般4kB),都按照最大值NGX_MAX_ALLOC_FROM_POOL处理。大于max的内存分配将特殊处理
    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;
}

每个分配的内存块,end指针总是指向末尾,last指针指向剩余内存的开始部分。
nginx源码学习——内存池_第1张图片

分配内存

void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;
    //申请分配的内存小于可分配最大值
    if (size <= pool->max) {
        //从current块开始往后查找可用块
        p = pool->current;
        //依次向后查找,直到有足够空间,返回该块
        do {
            m = p->d.last;

            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);
    }
    //所申请的内存大于max,需要申请大块内存
    return ngx_palloc_large(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, *current;
    //计算一个ngx_pool_t的大小
    psize = (size_t) (pool->d.end - (u_char *) pool);
    //采用内存对齐的方式分配一块buff,win32系统默认malloc采用8字节对齐,这里采用16字节对齐。
    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;
    //除了第一个内存块会有current、max等成员,之后的所有内存块只有ngx_pool_data_t和用户内存两部分。
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    current = pool->current;
    //从current块开始依次向后校验,因为走到这一步表示前面的块剩余空间都不够用,需要将failed字段加1,同时将current指针移动到failed小于4的块
    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            current = p->d.next;
        }
    }
    //将新块添加到pool的末尾
    p->d.next = new;
    //如果current未NULL,则指向new
    pool->current = current ? current : new;

    return m;
}

nginx源码学习——内存池_第2张图片

内存池中的内存块默认大小为pagesize-1,即4095Bytes。使用内存池,主要用于存储空间较小、数量较多的结构体,可以避免产生内存碎片,提升程序执行性能。对于大块的内存申请,则完全不需要使用内存池进行分配。nginx中,对于超过4095Bytes的内存,直接malloc。同时为了便于管理,会在内存池中开辟一个ngx_pool_large_t结构体,管理对应的内存块。

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大小的内存,size>pagesize
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;
    //查找前3个large对象有无可用的挂载点,有则直接将内存挂载,否则重新分配一个large对象
    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(pool, sizeof(ngx_pool_large_t));
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
    //头插法,large链表的头指向的是最新元素
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

nginx源码学习——内存池_第3张图片

内存池销毁

内存池在销毁前,会进行一些清理工作。比如清理ssl,image,mail等等操作。这些都是在销毁内存池的时候进行的。
主要结构体如下:

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;    //清理函数需要使用到的数据信息
    ngx_pool_cleanup_t   *next;    //指向下一个结构体
};

调用ngx_pool_cleanup_add添加清理任务。

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;
    //从内存池分配空间
    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;
}

nginx源码学习——内存池_第4张图片

业务模块在调用ngx_pool_cleanup_add后,可以设定清理函数句柄和参数内容。在连接结束后,调用清理函数,释放内存池资源。

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);
        }
    }
    //释放large内存
    for (l = pool->large; l; l = l->next) {

        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);

        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

#if (NGX_DEBUG)

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

    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 (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

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

内存池的好处

nginx作为一个web服务器,一切都是为性能服务的,内存池也不例外。内存池的好处主要有:

  • 开辟内存池的方式,用来分配比较小的内存,比如各个结构体。这样可以避免出现内存碎片,而影响运行效率。
  • 尽量的从内存池里分配空间,可以减少malloc的次数。因为malloc本身由glibc分配内存,glibc为了防止竞争会加锁,过于频繁调用malloc也会影响性能。

你可能感兴趣的:(nginx,源码学习)