nginx内存管理

首先,我们来看看它几个主要的数据结构:

//ngx内存池的头部信息
struct ngx_pool_s {
    ngx_pool_data_t       d;      //数据区域
    size_t                max;    //最大每次可分配内存
    ngx_pool_t           *current;//指向当前的内存池指针地址
    ngx_pool_large_t     *large;  //存大块数据的链表
};
//ngx小块内存的信息
typedef struct {
    u_char          *last; //内存池中未使用内存的开始结点地址
    u_char          *end;  //内存池的结束地址
    ngx_pool_t      *next; //下一个内存池
    ngx_uint_t      failed;//失败次数
} ngx_pool_data_t;

很明显,ngx_pool_data_t和ngx_pool_s构成了内存池的主题。在ngx_pool_data_t中:last表示内存池中未使用内存的开始结点地址,end表示当前内存池的结尾。相当于last到end就是当前内存池还未使用的内存大小。而它链表的形式就决定了nginx中如果当前内存池已满,便不会像平常一样扩大该内存池,而是重新分配一块内存,链到next指针上。但当内存池空间不够却又未满时,申请内存就回会失败,failed主要就是记录失败的次数,进而从新分配一个内存池。
接着就是表示大块内存的 ngx_pool_large_s

//大块内存的信息
struct ngx_pool_large_s {
    ngx_pool_large_t     *next; //指向下一个存储地址
    void                 *alloc;//数据块指针地址
};

就是一个简单的链表,next指向下一块内存,alloc指向数据。

接下来,通过代码来分析具体的方法,更好的理解内存池的实现。(仅win32)

首先来看ngx_create_pool

///内存池的数据区最大容量
#define MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)

//创建ngx的内存池
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);
    //这里的ngx_memalign()函数相当于(ngx_pool_t*)ngx_alloc(size),执行内存分配。
    //#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)
    if (p == NULL) {
        return NULL;
    }

//开始初始化数据区
//由于刚刚的开辟内存,因此last指针指向数据区的开始。
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
//end指针指向数据区的末尾。
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

//当前内存池的大小减去内存池头部信息的大小,得到真正能够使用的内存大小
    size = size - sizeof(ngx_pool_t);

//设置max,因为内存池的大小不超过一页(4k),所以内存池的最大值也就是size和NGX_MAX_ALLOC_FROM_POOL之中较小的。
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

//current表示当前内存池
    p->current = p;
//其他域都置成NULL
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;
//返回指针
    return p;
}

接下来看看如何从内存池中分配一块内存使用。

//考虑内存对齐的内存池内存申请
void* ngx_palloc(size_t size)
    {
//判断当前申请内存的大小是否超过max,没有则说明申请的是小块内存  
        if (size <= _pool->max)
            ngx_alloc_small(size, 1);
//申请大块内存            
        return ngx_alloc_large(size);
    }
//考虑内存不对齐的内存池内存申请
void* ngx_pnalloc(size_t size)
{
    if (size <= _pool->max)
        ngx_alloc_small(size, 0);
    return ngx_alloc_large(size);
}
//小块内存申请
void* ngx_alloc_small(size_t size, int i)
    {
        u_char *str;
        ngx_pool_t *p;
        p = _pool->current;//得到当前内存池指针

      //遍历内存池
        do{     
        str=p->d.last;   //保存last指针
            if (i){          //内存对齐操作
                str = ngx_align_ptr(p->d.last, NGX_ALTGNMENT);//就是将内存的地址对齐
                //#define NGX_ALIGNMENT   sizeof(unsigned long)
                //#define ngx_align_ptr(p,a)         \
    (u_char*)(((ngx_uint_t)(p)+((ngx_uint_t)a - 1))& ~((ngx_uint_t)a-1))
            }
             //判断当前内存池可用大小与请求的内存大小
            if ((size_t)(p->d.end - str) >= size){
            //更新last指针,并将前面保存的last返回 使用该内存
                p->d.last = str + size;
                return str;
            }
            //否则就继续遍历
            p = p->d.next;
        } while (p);
    //表示内存池已经满掉,因此我们需要重新分配一个内存池然后链接到当前的data的next上。
        return ngx_palloc_block(size);
    }

内存对齐操作,是一个内存地址取整宏,毕竟这里只是对指针进行偏移,last指针的位置需要自己动手将其调整。因为内存不对齐的话,会导致CPU I/O的次数增加,效率降低。

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

ngx_palloc_block的实现主要就是重新分配一块内存池,然后链接到当前内存池的数据去指针。注意这里的新内存池大小是与其父内存池一样大。

    void* ngx_palloc_block(size_t size)
    {
        u_char *str;
        ngx_pool_t *p, *new_p,*current;
        size_t psize;
        //计算当前内存池的的大小
        psize = (size_t)(_pool->d.end - (u_char*)_pool);
        //再开辟一块同样大小的内存池
        str = (u_char*)ngx_alloc(psize);
        if (NULL == str)
            return NULL;

        new_p = (ngx_pool_t*)str;
        //更新指针,初始化新开辟出来的内存池
        new_p->d.end = str + psize;
        new_p->d.next = NULL;
        new_p->d.failed = 0;

//由下面可以看出来,新开辟出来的内存池只存放了ngx_pool_data_t的信息
        str += sizeof(ngx_pool_data_t);//头部信息大小
        str = ngx_align_ptr(str, NGX_ALTGNMENT);//内存对齐操作
        new_p->d.last = str + size;//更新last指针的位置,即申请的size大小

        current=_pool->current;//设置current

        /*当failed大于4说明我们请求了4次,都不能满足,此时就假设老
        的内存池没有空间了,从比较新的内存块开始  提高效率*/
        for (p = current; p->d.next; p = p->d.next){
            if (p->d.failed++ > 4){
                current = p->d.next;
            }
        }
        //连接到最后一个内存池上
        p->d.next = new_p;

        //如果current为NULL,则current就为new_p
        pool->current=current?current:new_p;
        return str;
    }

这样设置current的原因是因为在ngx_palloc中分配内存是从current开始的,将current设置为比较新分配的内存会提高效率,而循环条件“failed大于4”则在上面说了,同样为提高效率。
至于大块内存的申请,就是简单的malloc一块大块内存链接到主内存池上。

void* ngx_alloc_large(size_t size)
{
    void *p;
    ngx_uint_t n;
    ngx_pool_large_t *large;

    p = ngx_alloc(size);
    if (NULL == p)
        return NULL;

    n = 0;
    //开始遍历large链表,查找一个alloc(内存区指针)为空,则赋值返回
    for (large = _pool->large; large; large = large->next)
    {
        if (large->alloc == NULL){
            large->alloc = p;
            return p;
        }
        if (n++ > 3)//
            break;
    }
    //为提高效率,如果3次都没找到空的,则重新创建一个
    large = (ngx_pool_large_t*)ngx_alloc_small(sizeof(ngx_pool_large_t), 1);
    if (large == NULL)
    {
        ngx_pfree(p);
        return NULL;
    }

//链接数据区指针p到large。这里可以看到直接插入到large链表的头的。
    large->alloc = p;
    large->next = NULL;
    _pool->large = large;
    return p;
}

好了,这些便是内存池的内存分配, 下面我们来看看内存的释放。这里我们要知道在nginx中,只有大块内存提供了free接口,可以供我们释放,而小块内存,是没有提供这样的接口的,只有在整个内存desrtoy掉时小块内存才会被释放。

    //重置内存池
    void ngx_reset_pool()
    {
        ngx_pool_t *p;
        ngx_pool_large_t *large;

        //释放大块内存
        for (large = _pool->large; large; large = large->next)
        {
            if (large->alloc){
                ngx_free(large->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->large = NULL;
    }
    //把p指向的内存归还给内存池
    int ngx_pfree(void *p)
    {
        ngx_pool_large_t *large;

        for (large = _pool->large; large; large = large->next)
        {
            if (large->alloc){
                ngx_free(large->alloc);
                large->alloc = NULL;
                return NGX_OK;
            }
        }
        return NGX_DECLINED;
    }

内存池的销毁

    //销毁内存池
    void ngx_destroy_pool()
    {
        ngx_pool_t *p, *n;
        ngx_pool_large_t *large;

        //先从大块内存开始进行释放
        for (large = _pool->large; large; large = large->next)
        {
            if (large->alloc){
                ngx_free(large->alloc);
            }
        }

        //最后释放内存池的内存池
        for (p = _pool, n = _pool->d.next;; p = n, n = n->d.next)
        {
            ngx_free(p);
            if (n == NULL)
                break;
        }
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值