nginx内存池源码剖析

nginx的简单介绍

Nginx是一款轻量级Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东新浪网易腾讯淘宝等,下面,我们将对Nginx的内存池部分源码进行剖析。

nginx内存池简介?

nginx为每一个层级都会创建一个内存池,进行内存的管理,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操作系统。
在分配的内存上,nginx有小块内存和大块内存的概念(下面的源码剖析中会介绍到),在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_pool 和 ngx_reset_pool的时候一并释放。

nginx内存池分配内存的大小内存块儿区分

if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }

 

当所申请的内存大小小于max时,采用小块儿内存分配机制,max<=4095。否则采用大块儿内存分配机制 

ngx主要API

图:

由于上面的图是一个静态图,所以不能够完全体现出内存池分配时的动态,且听我下面细细道来。为了方便下面源代码的剖析,我们分别对不同的块儿进行了命名

nginx内存池之创建内存池

源代码:

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); //将last指向未使用内存的头部
    p->d.end = (u_char *) p + size; //将end指针指向未使用内存的尾部
    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;
}

创建内存池所用到的数据结构数据结构:

ngx_pool_t :

管理小块儿内存的分配
typedef struct {
    u_char               *last; //指向未使用内存的头部
    u_char               *end; //指向未使用内存的尾部
    ngx_pool_t           *next; //指向下一个内存快
    ngx_uint_t            failed; //此内存块儿分配小块内存时失败的次数
} ngx_pool_data_t;

为了方便使用,用typedef重命名
typedef struct ngx_pool_s            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; //nginx日志入口指针
};

内存池创建完成后,如上面图片中最右边的块儿1所示。

内存分配函数:

1、ngx_palloc(ngx_pool_t *pool, size_t size)

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、ngx_pnalloc(ngx_pool_t *pool, size_t size)

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

两个函数的区别:只是在分配小块儿内存上所传的参数有区别,palloc函数中nginx_palloc_small的第三个参数为1,而pnalloc的参数为0,意思是palloc函数分配内存得考虑内存对齐,而pnalloc函数分配内存时不用考虑内存对齐问题

我们就以palloc函数为例,来看看内存池是如何管理内存的:

小块儿内存的分配

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; //第一次分配内存时是从第内存块儿1进行分配的

    do {
        m = p->d.last;

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT); //内存对齐后m指向为分配内存的首部
        }

        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) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;
  
    return m;
}

举个例子来说明一下小块儿内存的分配流程:

1、调用ngx_create_pool(124, log),将内存块儿的大小设置为124byte,那么块儿1剩余的大小小于124

2、第一次申请内存时调用palloc函数:palloc(pool,70),会调用ngx_palloc_small(pool,70)函数,直接在内存块儿1的第一个内存块进行分配。

3、当我们第二次再调用:palloc(pool,70),时会发现调用ngx_palloc_small(pool,70),内存块儿1中的第一个内存块儿不够用了,就会调用ngx_palloc_block函数进行内存块儿的申请,也就是申请内存块儿2,然后将其与内存池串联起来

4、当我们多次申请大小为70byte的内存时会发现每一次第一块儿内存都会分配失败,falid++,当加到4的时候就会将current指针指向faild<4的块儿1上,这也就是ngx_palloc_block函数中最后for循环的作用

以上就是小块儿内存分配的步骤。

大块儿内存的分配

通过内存池的large进行分配,通过块儿3对大块儿内存进行管理

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);//调用malloc函数进行内存分配
    if (p == NULL) {
        return NULL;
    }

    n = 0;
    
    //第一次分配内存时不用进for循环,
    多次分配和释放大块内存后,如果前三个块儿3有未挂载内存块儿的,就可以直接使用
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }
`    
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1); //调用小块儿内存分配函数构造块儿3
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
    
    进行头插
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

所用到的数据结构

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next; 指向下一个内存管理块儿
    void                 *alloc; 指向大块儿内存
};

大块儿内存的释放

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

上面代码也就是将大块儿内存管理的某一块儿所管理的内存归还。

举例说明:(内存池还是上面申请的内存池,块儿的大小为70)

1、调用palloc(100,log),申请大小为100byte的内存块儿

2、内存块大小大于小块内存的max,通过内存池的large作为头指针的链表对大块儿内存进行管理,也就是通过图中的块儿3进行管理

3、大块内存的释放也就是将链表节点对应的内存块释放掉注意:这里管理大块儿内存的内存都是通过申请小块儿内存而来的,nginx内存池只有对大块儿内存的回收,没有对小块儿内存的回收,这是与其的使用场景有很大关系的,nginx的本质是是一个http服务器, 是一个短连接的服务器,发起一个request请求,到达nginx服务器以后,处理完成,nginx给客户端返回一个request响应,http就主动断开(http1.1 kepp_alive 60s),http返回响应以后,需要等待60s,60s之内客户端又发来请求,重置这个时间
,否则调用nginx_reset_pool重置内存池(下面会讲到),等待下一次客户端的请求。

内存池的重置函数

 

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

 

此函数比较简单,就不再说详细说明,首先,先将大块内存释放,在将小块儿内存收回

内存池清理函数

我们在申请内存的时候,对应的可能会占有外部资源,比如文件或者malloc出来的外部资源,在destroy的时候,得将外部资源释放,所以在申请内存之前,可以写好对应的资源释放函数,同或函数指针添加到链表中,通过回调的方式释放资源

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

destory函数

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);
        }
    }//便利clean为头节点的链表,释放对应的资源


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

以上就是nginx内存池源码的剖析,如有错误,可留言

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值