设为首页 收藏本站
查看: 1047|回复: 0

[经验分享] Nginx 源码分析-- 内存池(pool)的分析 三

[复制链接]

尚未签到

发表于 2015-7-26 12:36:44 | 显示全部楼层 |阅读模式
  上一篇已经通过对 ngx_palloc 这个内存池(pool)管理的核心函数--内存分配函数进行解析,我们窥探到了Nginx内存管理的主体方法还有对于大内存需求的解决之道,同时也对管理内存池的数据结构有了更深一步的认识,通过这些认识我们可以得到以下这样一张数据结构的示意图:
DSC0000.jpg
  图3  Nginx内存管理数据结构示意图
  做说明下,这里示意的是有需求大内存分配时的结构示意图,为了图示的方便,我们将 large_t 特殊话到了和 large所在的同一个pool单元里面,其实实际运行中并非一定在同一个pool单元中。如果没有大内存需求时 large_t 也并不存在。
  分析完了,内存分配函数 ngx_palloc,可以说nginx内存分配机制的解析我们已经完成了一半,对于 ngx_palloc 的变种函数如:ngx_pcalloc、ngx_pnalloc等就不一一详细说明了,和 ngx_palloc 结构类似 或者 只是添加了少许功能(比如,初始化内存数据为0等)。我们再来分析内存池的其他函数
  1、创建函数 ngx_create_pool


DSC0001.gif DSC0002.gif View Code


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);
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;
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_pool_t 按照 size 的大小进行些必要的初始化,并返回一个指针。
  2、内存池pool的注销函数 ngx_destroy_pool
  分析这个函数,首先得分析Nginx内存池的注销机制。我知道在需求内存时很可能用到一些数据结构,注销时需要特别的对它进行释放,否则可能发生内存泄漏。对这样的情况,Nginx内存管理中,采用了一个有特点的结构体来应对--ngx_pool_cleanup_t,对应的就是pool单元中的 cleanup。



struct ngx_pool_cleanup_s {
ngx_pool_cleanup_pt   handler;
void                 *data;
ngx_pool_cleanup_t   *next;
};
  一个看显然就明白是个链表,不过,ngx_pool_cleanup_pt 是什么呢?请看下面这个定义



typedef void (*ngx_pool_cleanup_pt)(void *data);
  很明白了,原来是一个函数指针。这一下问题就显然解决了,在内存管理中遇到这样的特殊的数据结构,只要编写它相应的释放函数,用函数指针加载到pool单元上,在注销pool内存池时调用即可。特别说明下是,ngx_pool_cleanup_t 数据结构中的 data 存储的是 handler 指向的函数可能需要用到的参数。看起来很有C++中类的析构函数的味道,可以有多个这样的函数用 ngx_pool_cleanup_t 链表的形式保存。回到,Nginx内存管理的源代码里,我们可以找到 ngx_pool_cleanup_add 这样的一个函数,和预想的一样,它就是分配释放函数的内存,并返回地址。附上源码:


View Code


ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t  *c;
/*
分配ngx_pool_cleanup_t 空间
*/
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
/*
分配参数空间
值得注意以上两次分配空间都是分配在POOL池中!
*/
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = NULL;
c->next = p->cleanup;
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
  好了,理解了释放特殊结构体后,我们再来看 ngx_destroy_pool 就比较简单了。回顾内存池的建设过程,先建pool单元,如果有大内存需求再建large。那么注销正好相反就是。首先处理 ngx_pool_cleanup_t 的特殊需求,如果有的话然后释放分配large,最后释放pool。其中如果配置了要写的DEBUG日志的,就还对DEBUG日志进行记录。附上源码:



void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t          *p, *n;
ngx_pool_large_t    *l;
ngx_pool_cleanup_t  *c;
    /*
    处理 ngx_pool_cleanup_t 的特殊需求
    */
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);
}
}
#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
   /*
    释放pool
   */
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
  大家或许有点疑惑,内存真的没有泄漏了么?ngx_pool_cleanup_t 所占的内存,释放了么?答案当然是肯定的!如果有这样的疑惑,请仔细看 ngx_pool_cleanup_add 函数他给 ngx_pool_cleanup_t 分配的内存空间也是pool单元中的内存( 同样的 ngx_pool_large_t ),释放pool的时候一并释放出来。
  分析到这里,Nginx内存管理机制源码的分析就基本上结束了。感慨到Nginx内存池的设计确实精美,pool单元数据结构的设计(实用)、内存分配机制(大内存特殊处理,大内存信息的结构体也在内存池中)、内存释放机制(经典的函数指针用法,很巧妙的防止内存泄漏),很多地方都可以借鉴!
  在最后,我们再用一个小小的验证程序来检验我们分析出来的成果。我们在  ngx_palloc 添加上一条  printf("call ngx_palloc\n") 其他函数也添加上类似的语句,编写如下main代码:



void fun(void *p)
{
printf("call fun\n");
}
int ngx_cdecl
main(int argc, char *const *argv)
{
ngx_pool_t    *t;
ngx_pool_cleanup_t *clt;
void *p1,*p2,*p3;
t=ngx_create_pool(512,NULL); /*创建pool*/
printf("\n");
p1=ngx_palloc(t,416); /*分配内存,在可分配内*/
printf("\n");
p2=ngx_palloc(t,216);/*分配内存,在可分配内,但pool中没有足够的内存空间*/
printf("\n");
p3=ngx_palloc(t,624);/*分配大内存*/
printf("\n");
clt=ngx_pool_cleanup_add(t, sizeof(void *));/*添加释放特殊结构体的函数*/
clt->handler=&fun;
ngx_destroy_pool(t);/*pool池注销*/
printf("\n");
return 0;
}
  实验结果,截图如下:
DSC0003.png
  图4  Nginx内存管理机制实验截图
  实验结果简单说明下:
  call ngx_create_pool //创建了pool池
  call ngx_palloc  //分配内存,在可分配内
  call ngx_palloc //分配内存,在可分配内,但pool中没有足够的内存空间
  call ngx_palloc_block // 分配新的pool单元
  call ngx_palloc  //分配大内存
call ngx_palloc_large  //调用大内存分配函数
call ngx_palloc  //分配 ngx_pool_large_t 结构体空间
  call ngx_pool_cleanup_add //添加释放特殊结构体的函数
  call ngx_palloc  //分配ngx_pool_cleanup_t 的空间
call ngx_palloc  //分配函数参数的空间
call ngx_destroy_pool  //内存池pool池注销
  call fun  //调用释放特殊结构体的函数释放
  以上实验结果,完全符合我们分析Nginx内存分配机制,Nginx内存池(pool)的分析到此处就结束了。经典的内存池管理机制!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-90795-1-1.html 上篇帖子: nginx中SSI问题的研究 下篇帖子: Nginx支持TCP负载均衡
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表