为了方便系统模块对内存的使用,方便内存的管理,nginx自己实现了进程池的机制来进行内存的分配和释放, 首先nginx会在特定的生命周期帮你
统一建立内存池,当需要进行内存分配的时候统一通过内存池中的内存进行分配,最后nginx会在适当的时候释放内存池的资源,开发者只要在需要
的时候对内存进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。
例如对于内存的管理,如果我们需要使用内存,那么总是从一个ngx_pool_t的对象中获取内存,在最终的某个时刻,我们销毁这个ngx_pool_t对象,
所有这些内存都被释放了。这样我们就不必要对对这些内存进行malloc和free的操作,不用担心是否某块被malloc出来的内存没有被释放。因为当
ngx_pool_t对象被销毁的时候,所有从这个对象中分配出来的内存都会被统一释放掉。
下面介绍下 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; //日志信息 };
大块内存结构:
//大内存结构 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; 。
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; } } }
亦将遍历该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) {
...
}
下面简单介绍有关内存申请函数。
⑴,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_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来清理对应的资源。
毫无疑问内存池的使用给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 编写好后, 直接 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