Nginx源码研究四:NGINX的内存管理

关于nginx的内存使用,我们先看代码,下面是nginx_cycle.c中对全局数据结构cycle的初始化过程

    pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); //申请16K的内存池
    if (pool == NULL) {
        return NULL;
    }
    pool->log = log;

    cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
    if (cycle == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }


我们可以看到,nginx对内存分配做了封装,第一步:申请内存池;第二步:在内存池中分配内存。我们分别来看一下

1、内存池的申请

a、内存池的数据结构

我们先看一下内存池的数据结构

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;   //指向最新申请的large内存指针,最新申请的内存指针的*next指针指向前一申请的内存指针
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};

 内存池定义了7个成员变量,这里先看内存池定义的max的成员变量,我们从后面使用内存池分配内存可以看到,这个max成员变量,实际上确定了该段申请的内存使用的大小。max的大小不 大于内存的分页大小,目的是优化内存的存取。同时程序一旦请求内存大于max时候,就不会在内存池中分配内存,会另外分配一块大的内存供程序使用。

 

typedef struct {
    u_char               *last;   //所申请内存池大小的尾指针
    u_char               *end;    //内存使用的尾指针
    ngx_pool_t           *next;   
    ngx_uint_t            failed; //*last- *end 是该申请内存段中可使用内存大小(*last会做对齐优化),
                    //failed值用来统计当请求分配内存大于该值的次数,                     
//failed值大于4将不再考虑在该段内存分配。 } ngx_pool_data_t;

这里再看*current和 *next。*current指向的是可以使用的内存池,*next指针实际上将所有内存池构成一个内存池链。我们可以从下面的简图中看出

 

 

 

 b、内存池的创建

下面看一下ngx_create_pool(NGX_CYCLE_POOL_SIZE, log) 函数,这是创建内存池的函数,过程不再细述:

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); //实际分配size大小的内存
    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;
}

 

c、内存池的销毁

申请的一段内存构成内存池,分成两部分,一部分是ngx_pool_t结构头,一部分是数据区,next指针指向下一段申请的内存池。current指针指向可以尝试分配内存的内存池。

对于这样的一个内存池,建立之后,存在一个销毁过程。可以从下面的代码中看出内存池链的销毁过程

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

    //释放申请的大内存
    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);
        }
    }

    //内存池的释放,两个指针,一个指向需要释放的内存,一个指向链表的下一个
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

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

 

 

 2、内存池上分配内存

  先看在内存池上分配内存的代码

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;

    if (size <= pool->max) {

        p = pool->current; //找到可以分配内存的内存池

        do {
            m = ngx_align_ptr(p->d.last, 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);   
    }

    return ngx_palloc_large(pool, size); //需要分配内存大于内存池的最大容量,则独立分配一块大内存供程序使用
}

 

 这里面我们看一下 m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT),目的是保证分配内存地址的后面几位为0,至于几位,由unsigned long的大小来决定,unsigned long如果是4位,则保证分配内存地址后两位为00

#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */

#define
ngx_align_ptr(p, a) \ (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

 

前面情况是在内存池链中,内存池的容量足够,而在不够的情况下,会新申请内存池。我们看一下申请过程

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

    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;

    current = pool->current;  
    //由于前面的内存池链的容量不够,分配失败,会给failed标识值加1,failed次数大于4的时候,
    //下次就不再考虑在该内存池上分配内存

    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            current = p->d.next;
        }
    }

    p->d.next = new;

    //将新申请的内存池,挂在内存池链的末尾
    pool->current = current ? current : new;

    return m;
}

 

posted @ 2015-04-15 10:36  壹木人  阅读(392)  评论(0编辑  收藏  举报