现在的位置: 首页 > 综合 > 正文

October 14th Wednesday

2013年10月18日 ⁄ 综合 ⁄ 共 5029字 ⁄ 字号 评论关闭

  When I am studying the initialize process of the nginx, I found that the array need reading.  There are seldom comments on the sources of nginx.  Many a function need reading and comprehending if you want to know what they can do.  During reading the sources I took down some notes.  The following is the part of my notes.  It is wrriten in Chinese.

 

 

Nginx源码研究

 

  Nginx的源码是0.8.16版本。不是最新版本,但是与网上其他人研究nginx的源码有所修改。阅读时注意参照对比。

 

一、内存池

1.  内存分配相关函数

ngx_alloc.c中包括所有nginx内存申请的相关函数。

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

 

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

 

ngx_memalign() 也是向操作系统申请内存,只不过采用内存对齐方式。估计是为了减少内存碎片。如果操作系统支持posix_memalign()就采用它,如果支持memalign()则用memalign()。在0.8.19版本中,作者不再使用ngx_alloc(),而全部改用ngx_memalign()注:在nginxmain()函数中,通过将ngx_pagesize 设置为1024来指定内存分配按1024bytes对齐。这是不是意味着你虽指示分配10 bytes的内存,实际上nginx也向操作系统申请至少1024bytes的内存。

2.  内存池结构

Nginx的内存池类型是ngx_pool_t。这个类型定义在ngx_core.h中。

typedef struct ngx_pool_s        ngx_pool_t;

 

由定义可知ngx_pool_t背后实际上是struct ngx_pool_s。这个结构体在ngx_palloc.h中有定义。

 

 

 

 

据说以前版本nginx中内存池的结构如下:

 

struct ngx_pool_s {
    u_char                     *last;
    u_char                     *end;
    ngx_pool_t               *current;
    ngx_chain_t              *chain;
    ngx_pool_t               *next;
    ngx_pool_large_t       *large;
    ngx_pool_cleanup_t  *cleanup;
    ngx_log_t                 *log;
};

 

目前版本中结构则是这样:

 

内存池管理结点:

typedef struct {

    u_char               *last;  /* 指向所使用内存的最后的地址 */

    u_char               *end;  /* 指向所申请到内存块的最后的地址 */

    ngx_pool_t        *next;  /* 指向下一个ngx_pool_t */

    ngx_uint_t         failed;  /* 这个 */

} ngx_pool_data_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;    /* 指向log的指针 */

};

 

清除函数的指针如下:

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

   

    清除函数的队列结构:

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

   

struct ngx_pool_cleanup_s {

     ngx_pool_cleanup_pt   handler;   /* 清除函数 */

     void                 *data;     /* 清除函数所用的参数 */

     ngx_pool_cleanup_t    *next;     /* 下一个结点的指针 */

};

 

指向大块内存的结构:

typedef struct ngx_pool_large_s  ngx_pool_large_t;

   

struct ngx_pool_large_s {

     ngx_pool_large_t  *next;  /* 指向下一个大块内存。*/

/* 大块内存也是队列管理。*/

     void             *alloc;  /* 指向申请的大块数据 */

};

 

当待分配空间已经超过了池子自身大小,nginx也没有别的好办法,只好按照你需要分配的大小,实际去调用malloc()函数去分配,例如池子的大小是1K,待分配的大小是1M。实际上池子里只存储了ngx_pool_large_t结构,这个结构中的alloc指针,指向被分配的内存,并把这个指针返回给系统使用。

 

 

ngx_create_pool() 函数用来创建内存池。

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

第二步,设置ngx_pool_t中的成员d中的各个变量;

 

  p->d.last = (u_char *) p + sizeof(ngx_pool_t);

  p->d.end = (u_char *) p + size;

 

从代码看出,d.end指向内存块的结尾处,而d.last则指向所占用的内存的结尾处。刚申请的内存中占用ngx_pool_t结构作为管理单元。所以,此时d.last指向(u_char *) p + sizeof(ngx_pool_t)处。

第三步,设置其他成员。注意:在计算max时,max中存放的数指所申请内存块中空闲的大小。因此,在计算max之前先减去了管理结点本身的大小。

 

ngx_destroy_pool() 用来释放内存池,一共分三步:

第一步、在释放前先对业务逻辑进行释放前的处理

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

 


第二步、释放large占用的内存
    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);
        }
    }

第三步、释放所有的池子
for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
  }

 

ngx_palloc_large() 函数专用来申请大块内存。

第一步,申请的大块内存,在ngx_pool_t中大块他队列中寻找空闲的ngx_pool_larger结点。如果找到,将大块内存挂在该结点上。

ngx_pool_larger队列中查找空闲结点数不会超过三次。超过三个结点没找到空闲结点就放弃。

创建一个新的结点,将申请到地大块内存挂在这个新结点上。将这个结点插入队列头部。

 

ngx_palloc() 函数用来申请内存块。首先要说明的是内存池中可能是由多块内存块组成的队列。其中每块内存都有一个ngx_pool_t管理结点用来连成管理队列。

1.       如果申请的内存大小超过了当前的内存池结点中空闲空间,nginx直接采用ngx_palloc_large()函数进行大块内存申请;

2.       在内存池管理队列中寻找能够满足申请大小的管理结点。如果找到了,先按32位对齐方式计算申请内存的指针,再将last指向申请内存块的尾部。

3.       找不到合适的内存池,用ngx_palloc_block()函数。

 

ngx_pnalloc() 函数与ngx_palloc()函数唯一不同之处,就是在计算申请内存的指针的方式未按32位对齐方式计算。

 

ngx_palloc_block() 函数用来分配新的内存池块,形成一个队列。

这个函数中申请到新的内存池块后,在该块中分配完ngx_pool_data_t结点后,将这个结点挂在内存池队列的结尾处。

这个函数中有两个要注意的地方:

1.       在内存池块中保留ngx_pool_data_t时,不仅是按ngx_pool_data_t大小计算而且是按32位对齐。

2.       ngx_pool_data_t结构中的failed的妙用。单从字面上不是太好理角这个成员变量的作用。实际上是用来计数用的。

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

    if (p->d.failed++ > 4) {

        current = p->d.next;

    }

}

       从上面这段代码是寻找内存池队列的尾部。当队列较长,由于内存池管理队列是单向队列所以每次从头到尾搜索是很费时的。每次搜寻失败的结点(非尾部结点)的failed1failed指出了该结点经历多少次查寻,目前版本中超过4次时,将内存池的current指针指向其后续的结点。这样,下次再做类似查询时,可以跳过若干不必要的结点加快查询速度。

   

ngx_pmemalign() 函数采用内存对齐的方式申请大内存块。

   

ngx_pfree() 函数用来释放大内存块。成功返回NGX_OK,失败返回NGX_DECLINED

   

ngx_pcalloc() 函数使用ngx_palloc()函数申请内存后,将申请的内存清零。

   

ngx_pool_cleanup_add() 函数只是用来添加内存池的cleanup队。

ngx_pool_cleanup_t类型的结点组成了内存池的清除函数处理队列。后加入队列的函数先调用。Nginx中预定义了两个cleanup函数。

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

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

   

3.  小结

下面是nginx内存池的概貌图。

 

 

二、array

1.       结构

struct ngx_array_s {

void        *elts;           /* 指向数组元素的起始地址 */

ngx_uint_t   nelts;          /* 现使用的元素的数目 */

size_t       size;            /* 元素的大小 */

ngx_uint_t   nalloc;        /* 分配数组中元素的数目 */

ngx_pool_t  *pool;           /* 指向所在的内存池的指针 */

};

注:nelts应该小于等于nalloc。例:分配了5个元素,使用了3

抱歉!评论已关闭.