Nginx-ngx_palloc

    关于Nginx中内存管理的这一块,个人觉的这个是非常值得看的。里面有很多思路都很精妙,需要细细的揣摩。总的来说内存池的管理分为两大块,以页面的大小为分界线,不超过一个页面大小的内存块在内存池中进行管理,当超过一个页面大小后,就按需来分配,需要多少直接从内存中malloc了。 

这里先看下涉及到的几个结构体。

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

//这个机构体主要是用来释放pool中特殊资源,比如说关闭文件
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler; //释放资源的操作符
    void                 *data;
    ngx_pool_cleanup_t   *next;
};


typedef struct ngx_pool_large_s  ngx_pool_large_t;

//当申请的空间超过一个内存页面大小的时候,直接由有这个结构体来管理
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc; //按需申请空间
};


typedef struct {
    u_char               *last;    //当前节点空间已经分配到位置, end - last就是剩余空间。
    u_char               *end;    //当前pool节点的结束边界
    ngx_pool_t           *next;   //下一个pool节点
    ngx_uint_t            failed; //从当前pool节点申请空间时失败的次数
} ngx_pool_data_t;


struct ngx_pool_s {
    ngx_pool_data_t       d;            //描述当前内存池的使用情况
    /*以下字段只在第一个节点上有效,在链表的后面节点都会当做数据区来处理*/
    size_t                max;          //pool节点最大的可用空间
    ngx_pool_t           *current;      //这个节点的含义比较复杂,可以参考后面ngx_plalloc_block函数的解析来理解
    ngx_chain_t          *chain;        //这里实际上是缓存了整个系统中用到的chain节点,需要用的时候直接从这个链表中去获取
    ngx_pool_large_t     *large;        //当pool的数据区完全分配都不能满足空间需求的时候,需要从这里申请空间
    ngx_pool_cleanup_t   *cleanup;      //特殊资源释放的结构体
    ngx_log_t            *log;          //
};
struct ngx_pool_s 等价于ngx_pool_t这个结构体在源码中随处可见,因为nginx中有关于内存申请和使用的都得从这来,所以这个结构体是相当的重要,各个字段的含义见结构体中的注释。  特别说明的是ngx_pool_t的数据区的最大空间大小为NGX_MAX_ALLOC_FROM_POOL=ngx_pagesize-1,也就是一个内存页的大小,linux上一般是4k。
    在Nginx中的内存池管理,实际上每次空间的申请是从一个由ngx_pool_t的链表中从头到尾遍历所有节点去查看是否满足需求,一旦满足就分配,这个里面有一个优化:就是在每个节点上有有一个字段failed,这个字段是用来记录从当前节点查找空间时查找失败的次数,如果次数较多——当前设置的值是4次,那么下次再在分配空间的时候就不会从头开始了,而是从失败次数不多余4次的节点开始,这个地方就涉及current值的修改,所以说current这个指针不一定是指向自己节点的起始位置,而是整个内存池中第一个failed字段值不大于4的那个节点。

ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p; 
    /*  
    这个函数已经在上一篇文章中介绍过了,这个函数的功能是申请size大小的空间,
    按照NGX_POOL_ALIGNMENT对齐,这里对齐的位数为16位。 
    */
    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); //数据区的实际大小。 
    //数据区的实际大小还需要不超过系统定义的大小,这里定义的大小为一个内存页大小。                           
    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;
}

 这个函数很有意思,至少在我看代码之前没有这么用过,它先申请一个size大小的空间,很可能这个空间大小是远远大于sizeof(ngx_pool_t)的, 因为在对结构体进行内存分配的时候,是按照顺序来分配的,所以后面就空出了一大段空间,自然而然的后面那段空间就成了数据区了。然后用成员区的一个结构体 ngx_pool_data_t来管理,这样就不需要单独为数据区去再调用一次malloc了。大概示意图如下: 

pool的创建过程看完了,下面看下pool的销毁过程:

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

销毁过程还是遵循一贯的原则: “自里向外”——先把结构体里面的结构体申请的空间释放掉,然后再释放外一层空间,值得注意的是: 在释放内存的nxg_pool_cleanup_t这个结构体内的空间的时候,这个结构体本身是挂载了自己的毁掉函数来销毁自己的空间,而不是用简单的free来处理,至于为什么这么做还需要往后看才能看明白原因。最后在释放空间的时候调用的ngx_free这个函数实际上是对free的一个宏定义,这里只是为了保持函数命名一致。

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

    pool->large = NULL;

    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    }   
}
这是关于pool重置的函数,主要是把large区的空间都释放掉了, 然后把每个ngx_pool_t的节点指针初始化到其实位置。

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

    if (size <= pool->max) {
        /*如果所申请的空间大小没有超过一个ngx_pool_t的数据区大小,那么必须从这个节点中申请,如果
          在当前内存池中没有找到合适的,那么可以新开一个节点,然后分配空间满足这次申请。
        */
        p = pool->current;

        do {
            /*按照NGX_ALIGNMENT对齐,实际上是系统的字长。
            #define ngx_align_ptr(p, a)                                \
                (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
            */
            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); //当前的内存池中没有足够的空间了,那么需要申请新的pool节点,然后再进行分配。
    }
    //当当前申请的空间超过了单个内存池的上限后,需要从pool的large字段获取空间。
    return ngx_palloc_large(pool, size);
}

这个函数从名字来看大概能知道是分配空间用的,因为有alloc这个片段,当时前面加了一个p,那么猜测一下就知道是从pool中分配空间了。这个函数里应用了ngx_palloc_block和ngx_palloc_large这两个函数,那么先看看这两个函数是怎么实现的。

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new, *current;
    //pool的大小,直接由当前的pool计算出来
    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);
    //这里可以看出,新申请的节点从ngx_pool_data_t后面的空间都被当做数据区了,
    //内存池管理的节点实际上就是第一个节点。
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    current = pool->current;

    for (p = current; p->d.next; p = p->d.next) {        
        if (p->d.failed++ > 4) {
           //如果当前pool节点失败次数>4次了,那么下次再申请空间的时候就
           //从当前节点的下一个节点开始。
            current = p->d.next;
        }
    }

    p->d.next = new;
    //更新遍历的起始节点
    pool->current = current ? current : new;

    return m;
}

创建一个新的节点,不过这个心的节点和之前 ngx_create_pool函数创建的节点有所不同,这里新串讲的节点结构体中除了ngx_pool_data_t之外其他的字段都被抹掉了,直接当做数据区来对待,并且会随着一个节点failed字段的变化会调整首节点中current指针指向的地址。

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);
    if (p == NULL) {
        return NULL;
    }   

    n = 0;

    for (large = pool->large; large; large = large->next) {
        /*  
          在large链表中找到一个没有被使用的节点,如果在寻找
          large链表的过程中,寻找到了一个空的large节点并且搜寻的节点数
          不超过三个,那么直接把这个新节点加入到链表后面
        */
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }   
  if (n++ > 3) {
            break;
        }
    }
    /*
    1,如果large链表中的节点数超过3个
    2,如果是第一个节点
    满足这两个条件中的一个都会走到这个分支来。
    */
    //分配large节点
    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
    //将新的节点加入链表头部
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

按需分配新的节点,并且将新的节点加入的large链表中。

另外看看和ngx_palloc函数类似的函数

void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
这两个函数实际上和ngx_palloc的功能几乎一样,略微有差别的地方是

ngx_pnalloc在申请空间的时候没有对齐操作

ngx_pcalloc实际上是调用了ngx_pcalloc,然后对返回来的内存做了一个赋值操作,初始化为0.


void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)                                               
{
    void              *p; 
    ngx_pool_large_t  *large;
    //字节对齐分配内存
    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }   
    //分配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;
}
ngx_pmemalign这个函数实际上是分配大空间用的,但是在分配large的内存空间的时候做了字节对齐操作。

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) { //找到对应的指针。然后进行free操作
            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;
}
ngx_pfree这个函数其功能是用来释放large链表中某个指定节点的。

接下来看看pool中特殊资源的释放相关的API

/*添加特殊资源释放的操作*/
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) {
        //为具体资源分配空间,最后handler操作的也是这里的资源
        //这个data里会描述具体的资源信息
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }   

    } else {
        c->data = NULL;
    }   
    //handler初始化为空,资源释放的回调函数
    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;
}
下面来看一个具体的特殊资源释放操作——关闭文件。

void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {
	    //如果对应的handler是用来释放文件的操作
            cf = c->data;

            if (cf->fd == fd) {
                c->handler(cf); //调用具体的handler来进行操作,这个handler需要在外面定义好。
                c->handler = NULL;
                return;
            }
        }
    }
}
上面一直出现了一个handler来处理特殊资源,但是一直没有看到对应的实现,下面看看具体函数
void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}
可以看到,实际上关闭文件的handler很简单,就是调用了ngx_close_file的一个函数,将文件关闭。

下面再看一个比关闭文件稍微复杂的函数——删除文件

void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}
实际上这个函数只是对关闭文件增加了一个文件删除操作,目前来看ngx_pool中的特殊资源也就涉及到文件的关闭和删除。





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值