初识nginx——内存池篇

初识nginx——内存池篇

 

     为了自身使用的方便,Nginx封装了不少有用的数据结构,好比ngx_str_t ,ngx_array_t, ngx_pool_t 等等,对于内存池,nginx设计的十分精炼,值得咱们学习,本文介绍内存池基本知识,nginx内存池的结构和关键代码,并用一个实际的代码例子做了进一步的讲解nginx

 

 

1、内存池概述

    内存池是在真正使用内存以前,预先申请分配必定数量的、大小相等(通常状况下)的内存块留做备用。当有新的内存需求时,就从内存池中分出一部份内存块,若内存块不够用时,再继续申请新的内存。程序员

   内存池的好处有减小向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减小程序员在编写代码中对内存的关注等数组

   目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等数据结构

 

2、nginx内存池综述

     nginx为每个层级都会建立一个内存池,进行内存的管理,好比一个模板,tcp链接,http请求等,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操做系统。tcp

     在分配的内存上,nginx有小块内存和大块内存的概念,小块内存 nginx在分配的时候会尝试在当前的内存池节点中分配,而大块内存会调用系统函数malloc向操做系统申请函数

     在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_pool 和 ngx_reset_pool的时候一并释放性能

     区分小块内存和大块内存的缘由有2个,学习

     一、针对大块内存  若是它的生命周期远远短于所属的内存池,那么提供一个单独的释放函数是十分有意义的,但不区分大块内存和小块内存,针对大的内存块 便会没法提早释放了ui

     二、大块内存与小块内存的界限是一页内存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值经过调用getpagesize()得到),大于一页的内存在物理上不必定是连续的,因此若是分配的内存大于一页的话,从内存池中使用,和向操做系统从新申请效率差很少是等价的this

 

      nginx内存池提供的函数主要有如下几个

     NewImage

 

 

3、nginx内存池详解

    nginx使用了ngx_pool_s用于表示整个内存池对象,ngx_pool_data_t表示单个内存池节点的分配信息,ngx_pool_large_s表示大块内存

它们的结构和含义以下

struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};

next:   指向下一个大块内存

alloc:指向分配的大块内存

 

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

d:内存池的节点的数据分配状况

max:      单个内存池节点容量的最大值

current: 指向当前的内存池节点

chain: 指向一个ngx_chain_t结构

large:  指向大块内存链表

cleanup:释放内存池的callback

log:     用于输出日志

 

typedef struct {
   u_char *last;
   u_char *end;
   ngx_pool_t *next;
   ngx_uint_t failed;
} ngx_pool_data_t;

last:    内存池节点已分配的末位地址,下一次分配会尝试今后开始

end: 内存池节点的结束位置

next:next指向下一个内存池节点

failed: 当前内存池节点分配失败次数

 

NewImage

 

       nginx 内存池示意图1

 

    在分配内存的时候,nginx会判断当前要分配的内存是小块内存仍是大块内存,大块内存调用ngx_palloc_large进行分配,小块内存nginx先会尝试从内存池的当前节点(p->current)中分配,若是内存池当前节点的剩余空间不足,nginx会调用ngx_palloc_block新建立一个内存池节点并从中分配,

若是内存池当前节点的分配失败次数已经大于等于6次(p->d.failed++ > 4),则将当前内存池节点前移一个

(这里有个须要注意的地方,当当前内存节点的剩余空间不够分配时,nginx会从新建立一个ngx_pool_t对象,而且将pool.d->next指向新的ngx_pool_t,新分配的ngx_pool_t对象只用到了ngx_pool_data_t区域,并无头部信息,头部信息部分已经被当作内存分配区域了)

 

 

 

NewImage

 

                 nginx 内存池示意图2(新建了一个内存池节点和分配了2个大块内存,其中一个已经释放) 

 

 

关键代码

建立内存池代码

 

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 ); //间接调用了posix_memalign分配内存
     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; //因为当前内存池只有一个节点因此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;
}

 

ngx_palloc分配函数代码

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); //当前已有节点都分配失败,建立一个新的内存池节点
     }
     return  ngx_palloc_large(pool, size); //分配大块内存
}

 

消耗内存池

 

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); //间接调用free释放内存
         if  (n == NULL) {
             break ;
         }
     }
}

 

4、示例代码

 这里是直接替换了原有nginx代码的main函数 (src/core/nginx.c)

void  print_pool(ngx_pool_t *pool)
{
     if  (pool->large != NULL)
     {
         printf ( "has large memory\n" );
         for (ngx_pool_large_t* i = pool->large; i!=NULL; i = i->next)
         {
             printf ( "\t\tlarge next=0x%x\n" , i->next);
             printf ( "\t\tlarge alloc=0x%x\n" , i->alloc);
         }
         }
         int  i=1;
         while (pool)
         {
             printf ( "pool=0x%x,index:%d\n" , pool, i++);
             printf ( "\t\tlast=0x%x\n" , (pool->d).last);
             printf ( "\t\tend=0x%x\n" ,(pool->d).end);
             printf ( "\t\tnext=0x%x\n" ,(pool->d).next);
             printf ( "\t\tfailed=%d\n" ,pool->d.failed);
             printf ( "\t\tmax=%d\n" ,pool->max);
             printf ( "\t\tcurrent=0x%x\n" ,pool->current);
             printf ( "\t\tchain=0x%x\n" ,pool->chain);
             printf ( "\t\tlarge=0x%x\n" ,pool->large);
             printf ( "\t\tcleanup=0x%x\n" ,pool->cleanup);
             printf ( "\t\tlog=0x%x\n" ,pool-> log );
             printf ( "\t\tavailable pool memory=%d\n" , pool->d.end-pool->d.last);
             printf ( "\n" );
             pool=pool->d.next;
         }
     }
void  print_array( int  *a, int  size)
{
     for ( int  i=0; i<size; i++)
     {
         printf ( "%d," ,a[i]);
     }
     printf ( "\n" );
}
int  main()
{
     ngx_pool_t *pool;
     int  array_size = 128;
     int  array_size_large = 1024;
     int  page_size = getpagesize(); //得到一页的大小
     printf ( "page_size:%d\n" , page_size);
     printf ( "----------------------------\n" );
     printf ( "create a new pool" );
     pool = ngx_create_pool(1024, NULL); //建立一个大小为1024的内存池
     print_pool(pool);
     printf ( "----------------------------\n" );
     printf ( "alloc block 1 from the pool:\n" );
     int  *a1 = ngx_palloc(pool,  sizeof ( int ) * array_size); //分配第一块内存 用于建立数组
     for  ( int  i=0; i< array_size; i++)
     {
         a1[i] = i+1;
     }
     print_pool(pool);
     printf ( "----------------------------\n" );
     printf ( "alloc block 2 from the pool:\n" );
     int  *a2 = ngx_palloc(pool,  sizeof ( int ) * array_size); //分配第二块内存 用于建立数组,这个时候会建立第二个内存池节点
     for  ( int  i=0; i< array_size; i++)
     {
         a2[i] = 12345678;
     }
     print_pool(pool);
     printf ( "----------------------------\n" );
     printf ( "alloc large memory:\n" );
     printf ( "\t\tlarge next before=0x%x\n" , pool->current->d.last);
     int  * a3 = ngx_palloc(pool,  sizeof ( int ) * array_size_large); //因为大小超过了max的值 ngx_palloc中会调用ngx_palloc_large分配大块内存
     printf ( "\t\tlarge next after=0x%x\n" , pool->large);
     for  ( int  i=0; i< array_size_large; i++)
     {
         a3[i] = i+1;
     }
     print_pool(pool);
     print_array(a1,array_size);
     print_array(a2,array_size);
     print_array(a3,array_size_large);
     ngx_destroy_pool(pool);
     return  0;
}

 

 

 

 

  运行结果:

 NewImage

NewImage

 

 

 

NewImage 

 

 经过红框能够看到ngx_pool_t中只有第一个内存池节点的头部信息是有意义的,后续调用ngx_palloc_block建立的节点的头部信息都已经被数据覆盖。

 

5、总结

nginx的代码设计的十分灵活,既方便咱们开发,也方便咱们复用其中的结构,其中内存池的使用 对咱们学习nginx,了解nginx如何管理内存有着十分重要的意义。

相关文章
相关标签/搜索