Nginx学习(20)—内存池创建

内存池创建

对于内存池的结构,我们可以暂且将其分为头部以及数据域两部分,代码如下:
[cpp]  view plain copy
  1. struct ngx_pool_s {  
  2.     ngx_pool_data_t       d;        // 数据域部分,小块内存在此分配  
  3.     size_t                max;      // 整个数据块的大小,亦即能够分配的小块内存最大值  
  4.     ngx_pool_t           *current;  // 指向当前内存池  
  5.     ngx_chain_t          *chain;    // 可以挂载一个chain结构  
  6.     ngx_pool_large_t     *large;    // 当分配的内存超过max值时,即分配大块内存时,使用该成员,属于数据域部分  
  7.     ngx_pool_cleanup_t   *cleanup;  // 当内存池释放的时候,同时需要释放的一些资源使用该成员  
  8.     ngx_log_t            *log;      // 日志  
  9. };  
  10.   
  11. typedef struct {  
  12.     u_char               *last;     // 当前内存分配结束位置,亦即下一段内存分配开始位置  
  13.     u_char               *end;      // 内存池结束位置  
  14.     ngx_pool_t           *next;     // 链接下一个内存池  
  15.     ngx_uint_t            failed;   // 该数据域部分不能满足分配内存的次数  
  16. } ngx_pool_data_t;  
内存池创建的代码如下:
[cpp]  view plain copy
  1. ngx_pool_t *  
  2. ngx_create_pool(size_t size, ngx_log_t *log)  
  3. {  
  4.     ngx_pool_t  *p;  
  5.   
  6.     /* 创建数据域大小为size的内存池,地址以NGX_POOL_ALIGNMENT对齐,这里是16位 */  
  7.     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
  8.     if (p == NULL) {  
  9.         return NULL;  
  10.     }  
  11.   
  12.     p->d.last = (u_char *) p + sizeof(ngx_pool_t);  
  13.     p->d.end = (u_char *) p + size;  
  14.     p->d.next = NULL;  
  15.     p->d.failed = 0;  
  16.   
  17.     size = size - sizeof(ngx_pool_t);  
  18.     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;  
  19.   
  20.     p->current = p;  
  21.     p->chain = NULL;  
  22.     p->large = NULL;  
  23.     p->cleanup = NULL;  
  24.     p->log = log;  
  25.   
  26.     return p;  
  27. }  
按照上面的描述,可以简单用图示意下:

内存池分配

nginx提供给用户使用的内存分配接口有:
[cpp]  view plain copy
  1. void *ngx_palloc(ngx_pool_t *pool, size_t size);  
  2. void *ngx_pnalloc(ngx_pool_t *pool, size_t size);  
  3. void *ngx_pcalloc(ngx_pool_t *pool, size_t size);  
  4. void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);  
ngx_palloc和ngx_pnalloc都是从内存池里分配size大小内存,至于分得的是小块内存还是大块内存,将取决于size的大小;他们的不同之处在于,palloc取得的内存是对齐的,pnalloc则否。ngx_pcalloc是直接调用palloc分配好内存,然后进行一次初始化操作。ngx_pmemalign将在分配size大小的内存并按alignment对齐,然后挂到large字段下,当做大块内存处理。

以ngx_palloc为例详细说明下:
[cpp]  view plain copy
  1. void *  
  2. ngx_palloc(ngx_pool_t *pool, size_t size)  
  3. {  
  4.     u_char      *m;  
  5.     ngx_pool_t  *p;  
  6.   
  7.     /* 申请分配小内存块 */  
  8.     if (size <= pool->max) {  
  9.   
  10.         p = pool->current;  
  11.   
  12.         do {  
  13.             /* 内存对齐宏,下面有注释 */  
  14.             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);  
  15.   
  16.             /* 如果小内存块的数据域大于申请内存的size大小,就直接分配; 
  17.              * 否则,通过next指针寻找下一个内存池,直到找到能够分配size大小内存的内存池为止 
  18.              */  
  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.   
  29.         /* 如果不存在能够分配size大小的内存池,则申请一个新的内存池,并分配。 
  30.          * failed字段在该函数里增加,一个内存池当failed字段大于4之后就将current字段指向 
  31.          * 下一个内存池,亦即当failed字段大于4之后意味着该内存池已经分配完毕。至于为什么大小 
  32.          * 是4,可能就像网站说的,是基于统计或者测试的经验吧。。 
  33.          */  
  34.         return ngx_palloc_block(pool, size);  
  35.     }  
  36.   
  37.     /* 申请分配大的内存块 */  
  38.     return ngx_palloc_large(pool, size);  
  39. }  
关于内存对齐使用的 宏注释
[cpp]  view plain copy
  1. #define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))  
  2. #define ngx_align_ptr(p, a)                                                   \  
  3.     (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))  
以ngx_align(d, a)为例,这里a是2的幂的数,如2,4,8...
以a=8为例,用二进制表示就是a = 0x1000;(a-1) = 0x00...00111,再取反后为 0x11...11000;
当d=0~8时,0 =< d+(a-1) <= 15,该数与[~(a-1)]按位与,取高位,结果为0x1000,十进制为8;
当d=9~16时,0 =< d+(a-1) <= 23,该数与[~(a-1)]按位与,取高位,结果为0x10000,十进制为16;
当d=17~24时,0 =< d+(a-1) <= 31,该数与[~(a-1)]按位与,取高位,结果为0x11000,十进制为24;
......

亦即,最后取值均为8的倍数,完成对齐的功能。
推而广之,一个数加上(a-1)后与a(2的幂)进行(a减一后取反再按位与)的操作,所得值即为这个数向上取a的整倍数。

具体的内存池分配示意图:


内存池重置

[cpp]  view plain copy
  1. void  
  2. ngx_reset_pool(ngx_pool_t *pool)  
  3. {  
  4.     ngx_pool_t        *p;  
  5.     ngx_pool_large_t  *l;  
  6.   
  7.     /* 重置即相当于返回初始创建内存池的情景,此时无大块内存分配,因此需要将其释放 */  
  8.     for (l = pool->large; l; l = l->next) {  
  9.         if (l->alloc) {  
  10.             ngx_free(l->alloc);  
  11.         }  
  12.     }  
  13.   
  14.     pool->large = NULL;  
  15.   
  16.     /* 只是将last指针指向可共分配的内存的初始位置。 
  17.      * 这样,就省去了内存池的释放和重新分配操作,而达到重置内存池的目的 
  18.      */  
  19.     for (p = pool; p; p = p->d.next) {  
  20.         p->d.last = (u_char *) p + sizeof(ngx_pool_t);  
  21.     }  
  22. }  

内存池释放

[cpp]  view plain copy
  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.     /* 先释放cleanup上挂载的数据资源,调用cleanup自身的handler处理方法 */  
  9.     for (c = pool->cleanup; c; c = c->next) {  
  10.         if (c->handler) {  
  11.             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,  
  12.                            "run cleanup: %p", c);  
  13.             c->handler(c->data);  
  14.         }  
  15.     }  
  16.   
  17.     /* 然后释放大内存块 */  
  18.     for (l = pool->large; l; l = l->next) {  
  19.   
  20.         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);  
  21.   
  22.         if (l->alloc) {  
  23.             ngx_free(l->alloc);  
  24.         }  
  25.     }  
  26.   
  27.     /* 最后完成内存池的释放 */  
  28.     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {  
  29.         ngx_free(p);  
  30.   
  31.         if (n == NULL) {  
  32.             break;  
  33.         }  
  34.     }  
  35. }  

总结

Nginx内存的设计初探很精妙,然后学习进去发现逻辑很清晰。这种设计思想要有印象,以后遇到类似内存的设计问题可以提供备选思路。

你可能感兴趣的:(Nginx学习(20)—内存池创建)