nginx源码学习(二) 内存池结构 ngx_pool_t

1,nginx的内存池介绍

    为了方便系统模块对内存的使用,方便内存的管理,nginx自己实现了进程池的机制来进行内存的分配和释放, 首先nginx会在特定的生命周期帮你

   统一建立内存池,当需要进行内存分配的时候统一通过内存池中的内存进行分配,最后nginx会在适当的时候释放内存池的资源,开发者只要在需要

   的时候对内存进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。

  例如对于内存的管理,如果我们需要使用内存,那么总是从一个ngx_pool_t的对象中获取内存,在最终的某个时刻,我们销毁这个ngx_pool_t对象,

  所有这些内存都被释放了。这样我们就不必要对对这些内存进行malloc和free的操作,不用担心是否某块被malloc出来的内存没有被释放。因为当

   ngx_pool_t对象被销毁的时候,所有从这个对象中分配出来的内存都会被统一释放掉。

2,nginx内存管理相关结构

 下面介绍下 nginx 封装的一些有关内存分配、管理相关函数,内存相关的操作主要在文件 os/unix/ngx_alloc.{h,c} 和 core/ngx_palloc.{h,c} 中实现:

 A  ./src/os/unix/ngx_alloc.{h,c}中包括所有nginx内存申请的相关函数。

    ngx_alloc() 包装了malloc(),仅添加了内存分配失败时的,log输出和debug时的log输出。

    ngx_calloc() 调用上面的函数,成功分配后,将内存清零。

    ngx_memalign() 也是向操作系统申请内存,只不过采用内存对齐方式。是为了减少内存碎片。如果操作系统支持posix_memalign()就采用它,如果

    支持memalign()则用memalign()。在0.8.19版本中,作者不再使用ngx_alloc(),而全部改用ngx_memalign(),函数ngx_memalign()返回基于一个指

   定alignment的大小为size的内存 空间,且其地址为alignment的整数倍,alignment为2的幂。

 B,./src/core/ngx_palloc.{h,c}

  包含了内存管理的数据结构,封装创建/销毁内存池,从内存池分配空间等函数。

  内存管理中几个主要的数据结构:
  在文件ngx_palloc.h定义的内存管理相关数据结构。

//该结构用来维护内存池的数据块,供用户分配之用
typedef struct {
    u_char               *last;  //当前内存分配结束位置,即下一段可分配内存的起始位置
    u_char               *end;   //内存池结束位置
    ngx_pool_t           *next;  //内存池里面有很多块内存,这些内存块就是通过该指针连成链表的
    ngx_uint_t            failed;//统计该内存池不能满足分配请求的次数,即分配失败次数
} ngx_pool_data_t;//内存池的数据块位置信息 

ngx_pool_t结构

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_chain_t结构     
 ngx_pool_large_t     *large;   //分配大块内存用,即超过max的内存请求   
 ngx_pool_cleanup_t   *cleanup; //释放内存池的callback   
 ngx_log_t            *log;	   //日志信息
};

其中, sizeof(ngx_pool_data_t) =16B sizeof(ngx_pool_t) =40B

大块内存结构:

//大内存结构
struct ngx_pool_large_s {
    ngx_pool_large_t     *next; //下一个大块内存
    void                 *alloc;//指向分配的大块内存
};

当待分配空间已经超过了池子自身大小,nginx也没有别的好办法,只好按照你需要分配的大小,实际去调用malloc()函数去分配,例如池子的大小是1K,

待分配的大小是1M。实际上池子里只存储了ngx_pool_large_t结构,这个结构中的alloc指针,指向被分配的内存,并把这个指针返回给系统使用。

上述这些数据结构的逻辑结构图如下:

          

  :在nginx的main()函数中,通过将ngx_pagesize 设置为1024来指定内存 分配按1024bytes对齐。这意味着你虽指示分配10 bytes的内存,

  实际上nginx也向 操作系统申请至少1024bytes的内存。 nginx将几乎所有的结构体放在ngx_core.h文件中重新进行了申明,形式如:

  typedef struct ngx_**_s      ngx_**_t; 。

3,nginx 内存池操作相关。

 A,内存池的创建销毁
               

//创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;
   // ngx_memalign 返回值为void*,void*可以执指向任何类型的数据
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  // 内存分配,该函数的实现在src/os/unix/ngx_alloc.c文件中,uinx,windows分开走
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t); //last指向ngx_pool_t结构体之后数据的起始
    p->d.end = (u_char *) p + size; //end指向分配的整个size大小的内存的末尾 
    p->d.next = NULL;
    p->d.failed = 0;
   //max中存放的数指所申请内存块中空闲的大小,因此,在计算max之前先减去了管理结点本身的大小。	 
    size = size - sizeof(ngx_pool_t); 
	//最大不超过 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小,即4095B
    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的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_create_pool有如下三步:

第一步,调用ngx_memalign()申请内存;

第二步,设置ngx_pool_t中的成员d(即类型ngx_pool_data_t)中的各个变量;
  …
  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;
  d.last则指向未使用内存的开始处,而d.end指向内存块的结尾处。刚申请的内存中占用ngx_pool_t结构体作为管理单元。所以,此时d.last指向

  (u_char *) p + sizeof(ngx_pool_t)处。

第三步,设置ngx_pool_t 除d变量的其它成员;
    ...
    p->max =...
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
     ...
在计算max时,max中存放的数指所申请内存块中空闲内存的大小。因此,在计算max之前先减去了管理结点本身的大小。

 下面介绍 ngx_create_pool 里分配内存的 ngx_memalign()函数

 void *  
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)  
{  
    void  *p;  
    int    err;  
      
    err = posix_memalign(&p, alignment, size);  
    //该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。  
      
    if (err) {  
        ngx_log_error(NGX_LOG_EMERG, log, err,  
            "posix_memalign(%uz, %uz) failed", alignment, size);  
        p = NULL;  
    }  
      
    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,  
        "posix_memalign: %p:%uz @%uz", p, size, alignment);  
      
    return p;  
}  

  从这个函数的实现体,我们可以看到p =ngx_memalign(NGX_POOL_ALIGNMENT, size, log);

  函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h

  #defineNGX_POOL_ALIGNMENT  16  因此,nginx的内存池分配,是以16字节为边界对齐的

  首先我们要理解为什么要内存对齐?  为了移植性和 程序的性能,可以参考这篇文章


 看下内存池的销毁函数,pool指向需要销毁的内存池:

    void  
    ngx_destroy_pool(ngx_pool_t *pool)  
    {  
        ngx_pool_t          *p, *n;  
        ngx_pool_large_t    *l;  
        ngx_pool_cleanup_t  *c;  
        // 遍历 cleanup链表结构依次调用clenup的handler清理
     //cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等.
        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);  
            }  
        }  
        //清理大块内存,ngx_free实际上就是标准的free函数
        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 can not use this log while the 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;  
            }  
        }  
        //只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。  
    #endif  
        //释放小内存块
        for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {  
            ngx_free(p);  
              
            if (n == NULL) {  
                break;  
            }  
        }  
    }  

   该函数将遍历内存池链表,释放大块内存(通过ngx_free),而没有提供对小块内存的释放。如果注册了clenup(也是一个链表结构),

   亦将遍历该cleanup链表结构依次调用clenup的handler清理和该内存相关联的的其它资源,也就是当我们从内存池中申请资源时,

   可能会附带一些其它的资源(比如打开的文件),这些资源的使用和申请的内存是绑定在一起的,所以在进行资源释放时,希望这些资

  源的释放能够和内存池释放时一起进行(通过handler()回调函数),避免了资源泄露和单独执行释放相关资源的麻烦。

总结起来 ngx_destroy_pool() 释放内存池,一共分三步
第一步、在释放前先对业务逻辑进行释放前的处理
    for (c = pool->cleanup; c; c = c->next) {
          ...
    }
第二步、释放large占用的内存(大内存块链表)
    for (l = pool->large; l; l = l->next) {
          ....
    }
第三步、释放所有的池子(即小内存块链表)
for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
       ...
  }

 B,内存申请和释放

 下面简单介绍有关内存申请函数。 

,void *ngx_palloc(ngx_pool_t *pool, size_t size); 代码如下:

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

    if (size <= pool->max) {//判断待分配内存与max值 
        p = pool->current; //小于max值,则从current节点开始遍历pool链表 
        do {
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); // 对齐内存指针,加快存取速度
            if ((size_t) (p->d.end - m) >= size) { //找到合适内存块
                p->d.last = m + size;//在该节点指向的内存块中分配size大小的内存
                return m;
            }
			//如果不满足,则查找下一个节点
            p = p->d.next;
        } while (p);
		
		//链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存  
        return ngx_palloc_block(pool, size);
    }
	//大于max值,则在large链表里分配内存
    return ngx_palloc_large(pool, size);
}

  ngx_palloc(pool, size); 从这个pool中分配一块为size大小的内存。注意,此函数分配的内存的起始地址按照NGX_ALIGNMENT进行了对齐。

关于内存字节对齐,看下面的宏定义:

#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))

这个宏使用来计算存储d字节的数据,如果按照a字节对齐,需要多少字节来存储对齐的数据,其中a为2的指数幂。

比如 ngx_align(9,4)  则其结果为12,因为按4字节对齐4的整数倍最小值 则为 12, 宏 ngx_align_ptr 和ngx_align同样的道理和作用。

对齐操作会提高系统处理的速度,上面已经解释过内存字节对齐的诸多好处,下面给出 ngx_palloc的流程图。

                                

界定小内存块和大内存块的值是 min (size, NGX_MAX_ALLOC_FROM_POOL (4095))。

,void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

      从这个pool中分配一块为size大小的内存。ngx_pnalloc() 函数与ngx_palloc()函数唯一不同之处,就是在计算申请内存的指针的方式未按32位对齐方式计算。

,static void * ngx_palloc_block(ngx_pool_t *pool, size_t size);

       待申请新的内存池节点小于等于max是调用这个函数。

       ngx_palloc_block() 函数用来分配新的内存池块,形成一个链表。这个函数用static 修饰 可见其为内调函数,不会被外部调用。代码如下:

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new, *current;
    psize = (size_t) (pool->d.end - (u_char *) pool);//计算pool的大小
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配一块与pool大小相同的内存
    if (m == NULL) {
        return NULL;
    }
    new = (ngx_pool_t *) m;
    new->d.end = m + psize;//设置end指针
    new->d.next = NULL;
    new->d.failed = 0;
    m += sizeof(ngx_pool_data_t);//让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置
    m = ngx_align_ptr(m, NGX_ALIGNMENT);//按NGX_ALIGNMENT字节对齐
    new->d.last = m + size;//在数据区分配size大小的内存并设置last指针  
    current = pool->current;
	//更新current
    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {//failed的值只在此处被修改
            current = p->d.next;//失败6次以上移动current指针
        }
    }
	//将分配的block链入内存池 
    p->d.next = new;//尾插法
	//如果直到最后节点failed计数>=6次,则current指向新分配的内存池节点block
    pool->current = current ? current : new;

    return m;
}
这个函数中申请到新的内存池块后,在该块中分配完ngx_pool_data_t结点后,将这个结点挂在内存池队列的结尾处。
 有 两点 需要注意的:

1.新建的内存池节点的开头部分都只有结构体ngx_pool_data_t(注意不是40B的ngx_pool_t(创建内存池时才这样)而是16 B的ngx_pool_data_t,因为

内存池除头节点外的其它节点没有必要更多的ngx_pool_t描述结构,多么节省内存), 空闲内存的开始处new->d.last 不仅去除ngx_pool_data_t大小

的头结构体而且还需 要 ngx_align_ptr(m, NGX_ALIGNMENT)进行内存对齐。

2.ngx_pool_data_t结构中的failed及current的妙用。failed实际上是用来计数用的,current字段记录了后续从内存池分配内存的起始内存节点,即从

current指向 的内存池节点开始搜索可分配的内存,其中current的变动是根据统计来做的。如下
   for (p = current; p->d.next; p = p->d.next) {
    if (p->d.failed++ > 4) {
        current = p->d.next;
     }
 }
   当链表较长,由于内存池管理队列是单向链表, 所以每次从头到尾搜索是很费时的。每次搜寻失败的结点(非尾部结点)的failed加1。failed指出了

 该结点经历多少次查寻,如果从当前内存池节点分配内存总失败次数大于等于6次(由于p->d.failed初始值为0,至少失败6次才为真),就将current字段

 移动到下一个内存池节点,如下一个节点的failed次数也大于等于6次,再下一个。这样,下次再做类似查询时,可以跳过若干不必要的结点加快查询

速度。最后新申请的内存池节点采用尾插法插入内存池链表中。

,static void *ngx_palloc_large(ngx_pool_t *pool, size_t size);

      待申请新的内存池节点大于于max是调用这个函数。

  

//控制大块内存的申请
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) {
        if (large->alloc == NULL) {
            large->alloc = p;//把新分配的内存块设置在其空出的large的alloc指针字段下
            return p;
        }

        if (n++ > 3) {//尝试5次仍未找到已释放大内存块后空出的ngx_pool_large_t头结构体
            break;
        }
    }
	// 重新分配ngx_pool_large_t结构体
    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;
}

ngx_palloc_large() 函数专用来申请大块内存,其过程总结为如下两步:

第一步,调用ngx_alloc申请的大块内存。

第二步,在ngx_pool_t中大块内存节点large链表中寻找空闲的ngx_pool_larger结点。如果找到,将大块内存挂在该结点上。ngx_pool_larger队列中

查找空闲结点数不会超过五次。超过五个结点没找到空闲结点就放弃。如果超过5次仍没找到空闲的large节点,则创建一个新的ngx_pool_large_t结

构体,并将申请到大块内存挂在这个新结点上,最后将这个节点采用头插法插入链表头部。

综合 函数⑶、⑷ 可知 ngx_palloc_block,ngx_palloc_large 为nginx从系统申请新的内存池节点加入到ngx_pool_t这个内存池管理容器中。

,void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

       该函数也是从ngx_pool_t内存池中分配size大小的内存,并且对分配的内存块进行了清零。内部实际上是调用ngx_palloc申请内存,然后调用

       ngx_memzero清零。

,void *ngx_prealloc(ngx_pool_t *pool, void *p, size_t old_size, size_t new_size);

        对指针p指向的一块内存再分配。如果p是NULL,则直接分配一块新的new_size大小的内存。如果p不是NULL, 新分配一块内存,并把旧内存

       中的内容拷贝 至新内存块中,然后释放p的旧内存(具体能不能释放旧的,要视具体的情况而定,这里不再详述)。这个函数实际上也是使用

        ngx_palloc实现的。

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

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

(8),ngx_int_t  ngx_pfree(ngx_pool_t *pool, void *p);// 释放指定的内存

       对于被置于大块内存链,也就是被large字段管理的一列内存中的某块进行释放。该函数的实现是顺序遍历large管理的大块内存链表。所以效率

      比较低下。如果在这个链表中找到了这块内存,则释放,并返回NGX_OK。否则返回NGX_DECLINED。由于这个操作效率比较低下,除非必要,

     也就是说这块内存非 常大,确应及时释放,否则一般不需要调用。反正内存在这个pool被销毁的时候,总归会都释放掉的嘛!

     需要注意的是该函数只释放large链表中注册的内存,普通内存在ngx_destroy_pool中统一释放。

(9),ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);// 注册cleanup回叫函数(结构体)

        ngx_pool_t中的cleanup字段管理着一个特殊的链表,该链表的每一项都记录着一个特殊的需要释放的资源。对于这个链表中每个节点所包含的

      资源如何去释放, 是自说明的。这也就提供了非常大的灵活性。意味着,ngx_pool_t不仅仅可以管理内存,通过这个机制,也可以管理任何需要

      释放的资源,例如,关闭文件,或 者删除文件等等的。 (这个过程在nginx里面出现的比较多,也就是 xxxx_add 操作通常不是实际的添加操作,

     而是分配空间并返回一个指针,后续我们还要通过操作指针指向的空间来实现所谓的add)

下面介绍这个链表(在文件ngx_palloc.h定义):

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;// 是一个函数指针,指向一个可以释放data所对应资源的函数。该函数的只有一个参数,就是data
    void                 *data;//指向要清除的数据  
    ngx_pool_cleanup_t   *next;//下一个cleanup callback
};

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

data字段所指向 的资源的大小。比如我们需要最后删除一个文件。那我们在调用这个函数的时候,把size指定为存储文件名的字符串的大小,然后调用

这个函数给cleanup链表中增加一项。 该函数会返回新添加的这个节点。我们然后把这个节点中的data字段拷贝为文件名。把hander字段赋值为一个删

除文件的函数 (当然该函数的原型为:void(*ngx_pool_cleanup_pt)(void *data))。

Nginx中预定义了两个cleanup函数。

void ngx_pool_cleanup_file(void *data) 用来关闭打开的文件。

void ngx_pool_delete_file(void *data) 用来删除文件并且试图关闭文件。

概括起来如下图:

                   

由图可知,每个需要清理的资源都对应有一个头部结构,这个结构中有一个关键的字段handler,handler是一个函数指针,在挂载一个资源到内存池上的

时候,同时也会注册一个清理资源的函数到这个handler上。即是说,内存池在清理cleanup的时候,就是调用这个handler来清理对应的资源。  

4,下面是内存操作的一些例子

毫无疑问内存池的使用给nginx带来很大好处,比如内存使用便利、逻辑代码的简化、程序性能的提升等。 

为了 更好的理解 nginx 内存管理相关 的设计 和 使用 方法,下面我们写一个测试例子 进行 编译调试:

代码如下:

//ngx_pool_test.c 
#include <stdio.h>
#include <string.h>
#include "ngx_config.h"//包含相关 nginx 头文件
#include "nginx.h"
#include "ngx_conf_file.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"

volatile ngx_cycle_t *ngx_cycle; // 测试需要
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,
			ngx_err_t err, const char *fmt, ...)
{
}
// 自定义结构体类型
typedef struct demo_s 
{
	int key;
	char *name;
}demo_t;

//输出内存池相关信息:小块内存链表上个内存节点信息
void pool_blocks(ngx_pool_t *pool)
{
	int n = 0;
	ngx_pool_t *pool_head = pool;//记录内存池头(链表)部节点
        // ngxin 对于一个内存池(链表) 只有头节点 包含 max,current等信息 
        while(pool)
	{
		printf("Block %d:\n", n+1);
		printf("block addr = 0x%0x\n", pool);
		printf("  .current = 0x%0x\n", pool_head->current); 
		printf("unused memory size is %d\n", (ngx_uint_t)(pool->d.end - 
						pool->d.last));
		printf("Block %d failed %d\n", n+1, pool->d.failed);
		pool = pool->d.next;
		++n;
	}
	printf("-------------------------------\n");
}


int main()
{
	ngx_pool_t *pool;
	demo_t *demo;
	char name[] = "hello NGX!";
 	char *buf;
       // 创建一个新的内存池
        pool = ngx_create_pool(1024, NULL);
	printf("pool max is %d\n\n", pool->max);
	pool_blocks(pool);
        // 向NGX申请内存
	demo = ngx_palloc(pool, sizeof(demo_t));
	buf = ngx_palloc(pool, strlen(name)+1);
	demo->key = 1;
	demo->name = buf;
	strcpy(buf, name);
        // 输出数据
        printf("Data\n");
    	printf("demo->key=%d, demo->name=%s\n", demo->key, demo->name);
	pool_blocks(pool);
       
        // 申请内存
	ngx_palloc(pool, 970);
	pool_blocks(pool);

	ngx_palloc(pool, 970);
	pool_blocks(pool);

	ngx_destroy_pool(pool);
	return 0;
}

上面的代码注意添加  ngx_cycle_t *ngx_cycle , ngx_log_error_core的相关声明,不然会出现如下错误:

undefined reference to `ngx_cycle'
undefined reference to `ngx_log_error_core'
对于上面的代码, 编写 相应的Makefile(不熟悉make的可以 参考这里)文件如下:

CC=gcc
C_FLAGS = -g -Wall -Wextra  
DIR=/home/dane/nginx-1.2.0
TARGETS=ngx_pool_test
TARGETS_FILE=$(TARGETS).c

all:$(TARGETS)


clean:
	rm -f $(TARGETS) *.o

CORE_INCS=-I $(DIR)/src/core/ \
		  -I $(DIR)/objs/ \
		  -I $(DIR)/src/event \
		  -I $(DIR)/src/event/modules \
		  -I $(DIR)/src/os/unix \
		  -I $(DIR)/Nginx_Pre/pcre-8.32/

NGX_OBJ = $(DIR)/objs/src/core/ngx_palloc.o \
		  $(DIR)/objs/src/core/ngx_string.o \
		  $(DIR)/objs/src/os/unix/ngx_alloc.o

$(TARGETS):$(TARGETS_FILE)
	$(CC) $(C_FLAGS) $(TARGETS_FILE) $(CORE_INCS) $(NGX_OBJ) -o $@

makefile 文件 需要指定 相应的 依赖文件 包含文件 路径等。

上面的Makefile 编写好后, 直接 make 就可产生 出 可执行文件 ngx_pool_test

./ngx_pool_test 即可运行 可执行文件。

结果如下:

              

 由程序及上图运行结果可知,程序开始调用ngx_create_pool 创建大小为 1024大小的内存池头部节点,因为ngx_pool_t 的大小为40字节,

所以此时可用最大内存 pool->max 大小为 1024-40 为 984字节。随后调用函数ngx_palloc 在内存池上分配 sizeof(demo_t)大小的内存,并

申请其第二个成员对应的内存char* name,其总共申请内存大小为 8+11 为19字节,因此 第一个内存块所剩内存大小为 965字节。随后 我们

申请 970字节的内存 此时内存池中没有满足要求的内存块,因此内存池调用函数ngx_palloc_block向系统申请 size即1024字节大小的内存,并

挂接在内存池之上,此时新申请的内存池节点的头部只是占用ngx_pool_data_t (16字节)结构体大小的内存,所以可用内存大小为 1024-16 为

1008 字节,所以对于第二个block 去除 970字节所申请内存大小 还剩 38字节,随后的程序读者可以 根据程序 自行分析。


参考资料:

http://blog.csdn.net/livelylittlefish/article/details/6586946

http://code.google.com/p/nginxsrp/wiki/NginxCodeReview

http://www.alidata.org/archives/1390


  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值