nginx_pool机制个人总结

本文深入探讨Nginx中的ngx_pool_t数据结构及其管理内存的机制,介绍ngx_pool_t如何简化资源管理并提高程序效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

*************************************************************************

nginx_pool机制

 

Nginx基本数据结构之ngx_pool_t

 

ngx_pool_t是一个非常重要的数据结构,在很多重要的场合都有使用,很多重要的数据结构也都在使用它。那么它究竟是一个什么东西呢?简单的说,它提供了一种机制,帮助管理一系列的资源(如内存,文件等),使得对这些资源的使用和释放统一进行,免除了使用过程中考虑到对各种各样资源的什么时候释放,是否遗漏了释放的担心。

 

例如对于内存的管理,如果我们需要使用内存,那么总是从一个ngx_pool_t的对象中获取内存,在最终的某个时刻,我们销毁这个ngx_pool_t对象,所有这些内存都被释放了。这样我们就不必要对对这些内存进行malloc和free的操作,不用担心是否某块被malloc出来的内存没有被释放。因为当ngx_pool_t对象被销毁的时候,所有从这个对象中分配出来的内存都会被统一释放掉。

 

在比如我们要使用一系列的文件,但是我们打开以后,最终需要都关闭,那么我们就把这些文件统一登记到一个ngx_pool_t对象中,当这个ngx_pool_t对象被销毁的时候,所有这些文件都将会被关闭。

 

从上面举的两个例子中我们可以看出,使用ngx_pool_t这个数据结构的时候,所有的资源的释放都在这个对象被销毁的时刻,统一进行了释放,那么就会带来一个问题,就是这些资源的生存周期(或者说被占用的时间)是跟ngx_pool_t的生存周期基本一致(ngx_pool_t也提供了少量操作可以提前释放资源)。从最高效的角度来说,这并不是最好的。比如,我们需要依次使用A,B,C三个资源,且使用完B的时候,A就不会再被使用了,使用C的时候A和B都不会被使用到。如果不使用ngx_pool_t来管理这三个资源,那我们可能从系统里面申请A,使用A,然后在释放A。接着申请B,使用B,再释放B。最后申请C,使用C,然后释放C。但是当我们使用一个ngx_pool_t对象来管理这三个资源的时候,A,B和C的释放是在最后一起发生的,也就是在使用完C以后。诚然,这在客观上增加了程序在一段时间的资源使用量。但是这也减轻了程序员分别管理三个资源的生命周期的工作。这也就是有所得,必有所失的道理。实际上是一个取舍的问题,在具体的情况下,你更在乎的是哪个。

 

可以看一下在nginx里面一个典型的使用ngx_pool_t的场景,对于nginx处理的每个http request, nginx会生成一个ngx_pool_t对象与这个http request关联,所有处理过程中需要申请的资源都从这个ngx_pool_t对象中获取,当这个http request处理完成以后,所有在处理过程中申请的资源,都将随着这个关联的ngx_pool_t对象的销毁而释放。

 

ngx_pool_t相关结构及操作被定义在文件src/core/ngx_palloc.h|c中。

 

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;

};

从ngx_pool_t的一般使用者的角度来说,可不用关注ngx_pool_t结构中各字段作用。所以这里也不会进行详细的解释,当然在说明某些操作函数的使用的时候,如有必要,会进行说明。

 

下面我们来分别解释下ngx_pool_t的相关操作。

 

ngx_pool_t *ngx_create_pool(size_t size,ngx_log_t *log);

创建一个初始节点大小为size的pool,log为后续在该pool上进行操作时输出日志的对象。需要说明的是size的选择,size的大小必须小于等于NGX_MAX_ALLOC_FROM_POOL,且必须大于sizeof(ngx_pool_t)。

 

选择大于NGX_MAX_ALLOC_FROM_POOL的值会造成浪费,因为大于该限制的空间不会被用到(只是说在第一个由ngx_pool_t对象管理的内存块上的内存,后续的分配如果第一个内存块上的空闲部分已用完,会再分配的)。

 

选择小于sizeof(ngx_pool_t)的值会造成程序崩溃。由于初始大小的内存块中要用一部分来存储ngx_pool_t这个信息本身。

 

当一个ngx_pool_t对象被创建以后,该对象的max字段被赋值为size-sizeof(ngx_pool_t)和NGX_MAX_ALLOC_FROM_POOL这两者中比较小的。后续的从这个pool中分配的内存块,在第一块内存使用完成以后,如果要继续分配的话,就需要继续从操作系统申请内存。当内存的大小小于等于max字段的时候,则分配新的内存块,链接在d这个字段(实际上是d.next字段)管理的一条链表上。当要分配的内存块是比max大的,那么从系统中申请的内存是被挂接在large字段管理的一条链表上。我们暂且把这个称之为大块内存链和小块内存链。

源码:

 

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

   if (p == NULL) {

       return NULL;

    }

 

   p->d.last = (u_char *) p + sizeof(ngx_pool_t);//从last起存放自己感兴趣的数据

   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;

}

 

*************************************************************************

void *ngx_palloc(ngx_pool_t *pool, size_tsize);

从这个pool中分配一块为size大小的内存。注意,此函数分配的内存的起始地址按照NGX_ALIGNMENT进行了对齐。对齐操作会提高系统处理的速度,但会造成少量内存的浪费。

源码:

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); //链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存  

    }

 

   return ngx_palloc_large(pool, size);

}

 

static void *

ngx_palloc_block(ngx_pool_t *pool, size_tsize)

{

   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;

 

   for (p = current; p->d.next; p = p->d.next) {

       if (p->d.failed++ > 4) {//为什么要4次才换??????还没弄明白!!!

           current = p->d.next;

       }

    }

 

   p->d.next = new;

 

   pool->current = current ? current : new;

 

   return m;

}

 

#define ngx_align_ptr(p, a)                                                  \

   (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t)a - 1))

利用& ~((uintptr_t) a - 1)把p位置的不整除于a的尾数去掉,如p的位置为130(十进制地址),那么就让其整除(假设位对齐a=8,那么把130对其二进制形式后三位设置为000)

如int b=130;

         cout<<(b&(~(8-1)));-------128整除于8二进制位***000只要保证后三位为0那么它就整除于8

由于调整后的位置要大于以前的,所以要加上((uintptr_t) a - 1);

 

 

void *ngx_pnalloc(ngx_pool_t *pool, size_tsize);

从这个pool中分配一块为size大小的内存。但是此函数分配的内存并没有像上面的函数那样进行过对齐。

 

void *ngx_pcalloc(ngx_pool_t *pool, size_tsize);

该函数也是分配size大小的内存,并且对分配的内存块进行了清零。内部实际上是转调用ngx_palloc实现的。

 

void *ngx_pmemalign(ngx_pool_t *pool,size_t size, size_t alignment);

按照指定对齐大小alignment来申请一块大小为size的内存。此处获取的内存不管大小都将被置于大内存块链中管理。

ngx_int_t ngx_pfree(ngx_pool_t *pool, void*p);

 

 

于被置于大块内存链,也就是被large字段管理的一列内存中的某块进行释放。该函数的实现是顺序遍历large管理的大块内存链表。所以效率比较低下。如果在这个链表中找到了这块内存,则释放,并返回NGX_OK。否则返回NGX_DECLINED。

if (l->alloc) {

           ngx_free(l->alloc);//用来释放大块内存

       }

由于这个操作效率比较低下,除非必要,也就是说这块内存非常大,确应及时释放,否则一般不需要调用。反正内存在这个pool被销毁的时候,总归会都释放掉的嘛!

源码:

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;

}

*******************************************************************

ngx_pool_cleanup_t*ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);

ngx_pool_t中的cleanup字段管理着一个特殊的链表,该链表的每一项都记录着一个特殊的需要释放的资源。对于这个链表中每个节点所包含的资源如何去释放,是自说明的。这也就提供了非常大的灵活性。意味着,ngx_pool_t不仅仅可以管理内存,通过这个机制,也可以管理任何需要释放的资源,例如,关闭文件,或者删除文件等等的。下面我们看一下这个链表每个节点的类型:

 

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

typedef void (*ngx_pool_cleanup_pt)(void*data);

 

struct ngx_pool_cleanup_s {

   ngx_pool_cleanup_pt   handler;

   void                 *data;

   ngx_pool_cleanup_t   *next;

};

data:         指明了该节点所对应的资源。

handler:   是一个函数指针,指向一个可以释放data所对应资源的函数。该函数的只有一个参数,就是data。

next:         指向该链表中下一个元素。

看到这里,ngx_pool_cleanup_add这个函数的用法,我相信大家都应该有一些明白了。但是这个参数size是起什么作用的呢?这个 size就是要存储这个data字段所指向的资源的大小。

 

比如我们需要最后删除一个文件。那我们在调用这个函数的时候,把size指定为存储文件名的字符串的大小,然后调用这个函数给cleanup链表中增加一项。该函数会返回新添加的这个节点。我们然后把这个节点中的data字段拷贝为文件名。把hander字段赋值为一个删除文件的函数(当然该函数的原型要按照void (*ngx_pool_cleanup_pt)(void *data))。

 

void ngx_destroy_pool(ngx_pool_t *pool);

该函数就是释放pool中持有的所有内存,以及依次调用cleanup字段所管理的链表中每个元素的handler字段所指向的函数,来释放掉所有该pool管理的资源。并且把pool指向的ngx_pool_t也释放掉了,完全不可用了。

 

源码中是:先释放cleanup字段所管链表  然后是大数据块的链表,最后是小块列表

         有优先级之别

void

ngx_destroy_pool(ngx_pool_t *pool)

{

   ngx_pool_t          *p, *n;

   ngx_pool_large_t    *l;

   ngx_pool_cleanup_t  *c;

//先释放clean链表里面的空间先

   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;

       }

    }

}

 

void ngx_reset_pool(ngx_pool_t *pool);

该函数释放pool中所有大块内存链表上的内存,小块内存链上的内存块都修改为可用。但是不会去处理cleanup链表上的项目。

 

 

ngx_http_upstream_send_response会调用ngx_pool_run_cleanup_file来关闭file

ngx_pool_run_cleanup_file(ngx_pool_t *p,ngx_fd_t fd)实现机制:

1.先利用p中cleanup链来拿到(c->handler== ngx_pool_cleanup_file) 判断得到file机制关闭的cleanup块,然后调用c->handler中的方法进行file的关闭;



图片引用自:http://blog.youkuaiyun.com/livelylittlefish/article/details/6586946#comments

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值