Nginx——2

来源:互联网 发布:如何在php中定义常量 编辑:程序博客网 时间:2024/06/10 04:59
ngx_pool_t是一个非常重要的数据结构,在很多重要的场合都有使用,很多重要的数据结构也都在使用它。那么它究竟是一个什么东西呢?简单的说,它提供了一种机制,帮助管理一系列的资源(如内存,文件等),使得对这些资源的使用和释放统一进行,免除了使用过程中考虑到对各种各样资源的什么时候释放,是否遗漏了释放的担心。

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

注:笔者的nginx版本为1.10.1

一、数据结构定义

ngx_pool_t定义在[nginx源码目录]/src/core/ngx_palloc.h以及[nginx源码目录]/src/core/ngx_palloc.c,与之相关的头文件和源文件好包括[nginx源码目录]/src/os/unix/ngx_alloc.h以及ngx_alloc.c中。

[cpp] view plain copy print?
  1. struct ngx_pool_t {  //内存池的管理分配模块  
  2.     ngx_pool_data_t       d;         //内存池的数据块  
  3.     size_t                max;       //数据块大小,可分配小块内存的最大值  
  4.     ngx_pool_t           *current;   //指向当前或本内存池,以后的内存分配从该指针指向的内存池中分配  
  5.     ngx_chain_t          *chain;     //该指针挂接一个ngx_chain_t结构  
  6.     ngx_pool_large_t     *large;     //指向大块内存分配,nginx中,大块内存分配直接采用标准系统接口malloc  
  7.     ngx_pool_cleanup_t   *cleanup;   //析构函数,挂载内存释放时需要清理资源的一些必要操作  
  8.     ngx_log_t            *log;       //内存分配相关的日志记录  
  9. };  

其中内存池的数据块ngx_pool_data_t的数据结构定义如下:

[cpp] view plain copy print?
  1. typedef struct {    //内存池的数据结构模块  
  2.     u_char               *last;    //当前内存分配结束位置,即下一段可分配内存的起始位置  
  3.     u_char               *end;     //内存池的结束位置  
  4.     ngx_pool_t           *next;    //链接到下一个内存池,内存池的很多块内存就是通过该指针连成链表的  
  5.     ngx_uint_t            failed;  //记录内存分配不能满足需求的失败次数  
  6. } ngx_pool_data_t;   //结构用来维护内存池的数据块  


还有一个很重要的大块内存分配数据结构模块定义:

[cpp] view plain copy print?
  1. struct ngx_pool_large_t {  
  2.     ngx_pool_large_t     *next;  //指向下一个大内存块  
  3.     void                 *alloc; //实际利用malloc分配得到的内存的首地址  
  4. };  


大致数据结构图示如下:



二、内存管理解析

  • 内存的创建、销毁和重置
  • 2.1 创建内存块

[cpp] view plain copy print?
  1. ngx_pool_t *  
  2. ngx_create_pool(size_t size, ngx_log_t *log)  
  3. {  
  4.     ngx_pool_t  *p;  
  5.   
  6.     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  //ngx_memlign定义在ngx_alloc.h中,主要用于分配16字节边界对其的的内存块,返回指向该内存块的指针  
  7.     if (p == NULL) {                                    //NGC_POOL_ALIGNMENT定义为16  
  8.         return NULL;  
  9.     }  
  10.   
  11.     p->d.last = (u_char *) p + sizeof(ngx_pool_t);  //d.last指向当前已经分配的内存的末端地址,即下一个可以分配内存的首地址  
  12.     p->d.end = (u_char *) p + size;                 //指向当前内存池地址的末尾  
  13.     p->d.next = NULL;  
  14.     p->d.failed = 0;  
  15.   
  16.     size = size - sizeof(ngx_pool_t);  
  17.     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;  //NGC_MAX_ALLOC_FROM_POOL最大不超过4095B  
  18.   
  19.     p->current = p;      //初始化指向当前从内存中取得的内存块的首地址  
  20.     p->chain = NULL;  
  21.     p->large = NULL;    //创建内存池时,并没有需要很大内存块,所以为空  
  22.     p->cleanup = NULL;  
  23.     p->log = log;  
  24.   
  25.     return p;  
  26. }  


ngx_memalign函数定义如下:

[cpp] view plain copy print?
  1. void *  
  2. ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)  
  3. {  
  4.     void  *p;  
  5.   
  6.     p = memalign(alignment, size);  
  7.     if (p == NULL) {  
  8.         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,  
  9.                       "memalign(%uz, %uz) failed", alignment, size);  
  10.     }  
  11.   
  12.     ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,  
  13.                    "memalign: %p:%uz @%uz", p, size, alignment);  
  14.   
  15.     return p;  
  16. }  


例如在Linux(Ubantu14.04 64位)系统上申请大小为1024Bytes的内存池:最后结果如下:



  • 2.2 内存池销毁

[cpp] view plain copy print?
  1. void  
  2. ngx_destroy_pool(ngx_pool_t *pool)  
  3. {  
  4.     ngx_pool_t          *p, *n;  
  5.     ngx_pool_large_t    *l;  
  6.     ngx_pool_cleanup_t  *c;  
  7.       
  8.     for (c = pool->cleanup; c; c = c->next) {  
  9.         if (c->handler) {  
  10.             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,  
  11.                 "run cleanup: %p", c);  
  12.             c->handler(c->data);  
  13.         }  
  14.     }  
  15.     //cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,  
  16.     //清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。  
  17.       
  18.     for (l = pool->large; l; l = l->next) {  
  19.         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);  
  20.           
  21.         if (l->alloc) {  
  22.             ngx_free(l->alloc);  
  23.         }  
  24.     }  
  25.     //这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,  
  26.     //即大内存块就是通过malloc和free操作进行管理的。  
  27.       
  28. #if (NGX_DEBUG)  
  29.       
  30.     /** 
  31.     * we could allocate the pool->log from this pool 
  32.     * so we can not use this log while the free()ing the pool 
  33.     */  
  34.       
  35.     for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {  
  36.         ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,  
  37.             "free: %p, unused: %uz", p, p->d.end - p->d.last);  
  38.           
  39.         if (n == NULL) {  
  40.             break;  
  41.         }  
  42.     }  
  43.     //只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。  
  44. #endif  
  45.       
  46.     for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {  
  47.         ngx_free(p);  
  48.           
  49.         if (n == NULL) {  
  50.             break;  
  51.         }  
  52.     }  
  53. }  
  54. //该片段彻底销毁内存池本身。内存池是又多个链表相连接,d.next指向下一个内存池  

  • 2.3 重置内存块

[cpp] view plain copy print?
  1. void  
  2. ngx_reset_pool(ngx_pool_t *pool)  //重置poo内存池,使得pool内存池回归初始状态  
  3. {  
  4.     ngx_pool_t        *p;  
  5.     ngx_pool_large_t  *l;  
  6.   
  7.     for (l = pool->large; l; l = l->next) {  
  8.         if (l->alloc) {  
  9.             ngx_free(l->alloc);           //释放所有大内存块  
  10.         }  
  11.     }  
  12.   
  13.     for (p = pool; p; p = p->d.next) {  
  14.         p->d.last = (u_char *) p + sizeof(ngx_pool_t);  //使得last指向ngx_pool_t后的内存地址  
  15.         p->d.failed = 0;  
  16.     }  
  17.   
  18.     pool->current = pool;  
  19.     pool->chain = NULL;  
  20.     pool->large = NULL;  
  21. }  


  • 从内存池中分配内存

从内存池中取得size大小的内存主要通过ngx_palloc以及ngx_pnalloc,两者的区别是,前者从内存池中取得NGX_ALIGNMENT字节对其的内陈块,而后者不考虑字节对其,NGX_ALIGNMENT定义在ngx_config.h中:

[cpp] view plain copy print?
  1. #ifndef NGX_ALIGNMENT  
  2. #define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */  
  3. #endif  

以ngx_palloc分析:

[cpp] view plain copy print?
  1. void *  
  2. ngx_palloc(ngx_pool_t *pool, size_t size)  
  3. {  
  4. #if !(NGX_DEBUG_PALLOC)  
  5.     if (size <= pool->max) {    //若需要分配的内存大小大于内存池中的可分配内存,直接调用ngx_palloc_large分配,否则调用ngx_palloc_small分配,1代表直接对齐  
  6.         return ngx_palloc_small(pool, size, 1);  
  7.     }  
  8. #endif  
  9.   
  10.     return ngx_palloc_large(pool, size);  
  11. }  


调用的ngx_palloc_small实现如下:
[cpp] view plain copy print?
  1. static ngx_inline void *  
  2. ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)  
  3. {  
  4.     u_char      *m;  
  5.     ngx_pool_t  *p;  
  6.   
  7.     p = pool->current;  
  8.     
  9.     do {  
  10.         m = p->d.last;  
  11.         //执行对齐操作,    
  12.         //即以last开始,计算以NGX_ALIGNMENT对齐的偏移位置指针,    
  13.         if (align) {  
  14.             m = ngx_align_ptr(m, NGX_ALIGNMENT);  
  15.         }  
  16.            
  17.         //然后计算end值减去这个偏移指针位置的大小是否满足索要分配的size大小,    
  18.          //如果满足,则移动last指针位置,并返回所分配到的内存地址的起始地址;    
  19.         if ((size_t) (p->d.end - m) >= size) {  
  20.             p->d.last = m + size;  
  21.   
  22.             return m;  
  23.         }  
  24.          //如果不满足,则查找下一个内存池。    
  25.         p = p->d.next;  
  26.   
  27.     } while (p);  
  28.     //如果遍历完整个内存池链表均未找到合适大小的内存块供分配,则执行ngx_palloc_block()来分配。    
  29.                 
  30.     //ngx_palloc_block()函数为该内存池再分配一个block,该block的大小为链表中前面每一个block大小的值。    
  31.     //一个内存池是由多个block链接起来的。分配成功后,将该block链入该poll链的最后,    
  32.     //同时,为所要分配的size大小的内存进行分配,并返回分配内存的起始地址。    
  33.     return ngx_palloc_block(pool, size);  
  34. }  

当需要进行ngx_palloc_block进行小内存分块时,ngx_palloc_block定义如下:
  • ngx_palloc_block分配的可分配内存大小和pool指向的可分配内存大小一样
  • 该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置,而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。
[cpp] view plain copy print?
  1. static void *  
  2. ngx_palloc_block(ngx_pool_t *pool, size_t size)  
  3. {  
  4.     u_char      *m;  
  5.     size_t       psize;  
  6.     ngx_pool_t  *p, *new, *current;  
  7.       
  8.     psize = (size_t) (pool->d.end - (u_char *) pool);  
  9.     //计算pool的大小,即需要分配的block的大小  
  10.       
  11.     m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);  
  12.     if (m == NULL) {  
  13.         return NULL;  
  14.     }  
  15.     //执行按NGX_POOL_ALIGNMENT对齐方式的内存分配,假设能够分配成功,则继续执行后续代码片段。  
  16.       
  17.     //这里计算需要分配的block的大小  
  18.     new = (ngx_pool_t *) m;  
  19.       
  20.     new->d.end = m + psize;  
  21.     new->d.next = NULL;  
  22.     new->d.failed = 0;  
  23.     //执行该block相关的初始化。  
  24.       
  25.     m += sizeof(ngx_pool_data_t);  
  26.     //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置  
  27.     m = ngx_align_ptr(m, NGX_ALIGNMENT);  
  28.     new->d.last = m + size;  
  29.     //在数据区分配size大小的内存并设置last指针  
  30.       
  31.     current = pool->current;  
  32.       
  33.     for (p = current; p->d.next; p = p->d.next) {  
  34.         if (p->d.failed++ > 4) {  
  35.             current = p->d.next;  
  36.             //失败4次以上移动current指针  
  37.         }  
  38.     }  
  39.       
  40.     p->d.next = new;  
  41.     //将分配的block链入内存池  
  42.       
  43.     pool->current = current ? current : new;  
  44.     //如果是第一次为内存池分配block,这current将指向新分配的block。  
  45.       
  46.     return m;  
  47. }  


当请求的内存分配大小size大于当前内存池的可分配内存时(由于内存池是一个链表,随着不断地申请内存,current的指针会指向不同的内存池首地址),此时会调用ngx_palloc_large,ngx_palloc_large实现如下:
[cpp] view plain copy print?
  1. //这个函数在头文件里面并没有明确声明出来,而是在源文件中定义  
  2. //即nginx在进行内存分配需求时,不会自行去判断是否是大块内存还是小块内存,  
  3. //而是交由内存分配函数去判断,对于用户需求来说是完全透明的。  
  4. static void *  
  5. ngx_palloc_large(ngx_pool_t *pool, size_t size)  
  6. {  
  7.     void              *p;  
  8.     ngx_uint_t         n;  
  9.     ngx_pool_large_t  *large;  
  10.       
  11.     p = ngx_alloc(size, pool->log);  //下文紧接着将分析此ngx_alloc函数  
  12.     if (p == NULL) {  
  13.         return NULL;  
  14.     }  
  15.       
  16.     n = 0;  
  17.       
  18.     //以下几行,将分配的内存链入pool的large链中,  
  19.     //这里指原始pool在之前已经分配过large内存的情况。  
  20.     for (large = pool->large; large; large = large->next) {  
  21.         if (large->alloc == NULL) {  
  22.             large->alloc = p;  
  23.             return p;  
  24.         }  
  25.           
  26.         if (n++ > 3) {  
  27.             break;  
  28.         }  
  29.     }  
  30.       
  31.     //如果该pool之前并未分配large内存,则就没有ngx_pool_large_t来管理大块内存  
  32.     //执行ngx_pool_large_t结构体的分配,用于来管理large内存块。ngx_pool_large_t所需内存依然遵循从内存池中取所需内存的原则  
  33.     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));  
  34.     if (large == NULL) {  
  35.         ngx_free(p);  
  36.         return NULL;  
  37.     }  
  38.       
  39.     large->alloc = p;  
  40.     large->next = pool->large;  
  41.     pool->large = large;  
  42.       
  43.     return p;  
  44. }  

其中,ngx_alloc定义在文件src/os/unix/ngx_alloc.c中:
[cpp] view plain copy print?
  1. void *  
  2. ngx_alloc(size_t size, ngx_log_t *log)  
  3. {  
  4.     void  *p;  
  5.   
  6.     p = malloc(size);  
  7.     if (p == NULL) {  
  8.         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,  
  9.                       "malloc(%uz) failed", size);  
  10.     }  
  11.   
  12.     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);  
  13.   
  14.     return p;  
  15. }  


由此完成了内存的分配。

随后是内存的释放过程:通过调用ngx_destroy_pool对内存池的所有内存进行统一的释放,避免了内存泄露的可能。



三、案例分析

下面以一个案例简单的分析内存池分配内存的过程:

整个示例代码如下:

[cpp] view plain copy print?
  1.     /**  
  2.      * ngx_pool_t test, to test ngx_palloc, ngx_palloc_block, ngx_palloc_large  
  3.      */    
  4.         
  5. #include <stdio.h>    
  6. #include "ngx_config.h"    
  7. #include "ngx_conf_file.h"    
  8. #include "nginx.h"    
  9. #include "ngx_core.h"    
  10. #include "ngx_string.h"    
  11. #include "ngx_palloc.h"    
  12.   
  13. void dump_pool(ngx_pool_t* pool)    
  14. {    
  15.     while (pool)    
  16.     {    
  17.         printf("pool = 0x%x\n", pool);    
  18.         printf("\t.d = 0x%x\n",&pool->d);    
  19.         printf("\t.last = 0x%x\n", pool->d.last);    
  20.         printf("\t.end = 0x%x\n", pool->d.end);    
  21.         printf("\t.next = 0x%x\n", pool->d.next);    
  22.         printf("\t.failed = %d\n", pool->d.failed);    
  23.         printf("\t.max = %d\n", pool->max);    
  24.         printf("\t.current = 0x%x\n", pool->current);    
  25.         printf("\t.chain = 0x%x\n", pool->chain);    
  26.         printf("\t.large = 0x%x\n", pool->large);    
  27.         printf("\t.cleanup = 0x%x\n", pool->cleanup);    
  28.         printf("\t.log = 0x%x\n", pool->log);    
  29.         printf("available pool memory = %d\n\n", pool->d.end - pool->d.last);    
  30.         pool = pool->d.next;    
  31.     }    
  32. }    
  33.     
  34. int main()    
  35. {    
  36.     ngx_pool_t *pool;   
  37.     printf("--------------------------------\n");   
  38.     printf("the size of ngx_pool_t is:%d,and the size of intptr_t is:%d\n",sizeof(ngx_pool_t),sizeof(intptr_t));  
  39.     printf("--------------------------------\n");   
  40.     printf("the size of size_t is:%d\n",sizeof(size_t));  
  41.     printf("--------------------------------\n");    
  42.     printf("create a new pool:\n");    
  43.     printf("--------------------------------\n");    
  44.     pool = ngx_create_pool(1024, NULL);    
  45.     dump_pool(pool);    
  46.     
  47.     printf("--------------------------------\n");    
  48.     printf("alloc block 1 from the pool:\n");    
  49.     printf("--------------------------------\n");    
  50.     ngx_palloc(pool, 1024);    
  51.     dump_pool(pool);    
  52.     
  53.     printf("--------------------------------\n");    
  54.     printf("alloc block 2 from the pool:\n");    
  55.     printf("--------------------------------\n");    
  56.     ngx_palloc(pool, 1024);    
  57.     dump_pool(pool);    
  58.     
  59.     printf("--------------------------------\n");    
  60.     printf("alloc block 3 from the pool :\n");    
  61.     printf("--------------------------------\n");    
  62.     ngx_palloc(pool, 512);    
  63.     dump_pool(pool);    
  64.     
  65.     ngx_pool_large_t *p;  
  66.     p=pool->large;  
  67.     int i=1;  
  68.     while(p)  
  69.     {  
  70.         printf("%dth large block address is:0x%x\n",i,p);  
  71.         ++i;  
  72.         p=p->next;  
  73.     }  
  74.   
  75.     ngx_destroy_pool(pool);    
  76.     return 0;    
  77.     }    

1、首先创建内存池

[cpp] view plain copy print?
  1. pool = ngx_create_pool(1024, NULL);    


2、随着向内存池中申请1024Bytes数据,由于当前可分配内存为944Bytes,所以需要调用ngx_palloc_large取得所需内存:


图示如下


3、再往内存池中申请1024Bytes数据:



4、从内存池中申请512Bytes内存,由于512Bytes小于当前可分配内存912Bytes,因此调用ngx_palloc_small进行内存分配

转自:http://blog.csdn.net/mao19931004/article/details/54377101


原创粉丝点击