Chinaunix首页 | 论坛 | 博客
  • 博客访问: 265506
  • 博文数量: 41
  • 博客积分: 397
  • 博客等级: 二等列兵
  • 技术积分: 325
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-24 23:10
文章分类
文章存档

2014年(3)

2013年(20)

2012年(14)

2011年(4)

分类: LINUX

2014-09-11 19:51:55

       先从一个简单的内存池(Nginx内存池,nginx-1.6.1版本)实现开始,来获取其大致的概念。一个内存池可以认为是一个内存分配器,有些内存池被设计成只能分配固定长度的内存块,而有些则设计成可分配不定长度的内存块。另一个特点是内存块的释放,在内存池被销毁后,从该内存池的分配的所有内存块都将会被销毁,可以称之为“一键销毁”功能!

接口设计:

Nginx内存池的核心接口,可以分成两大类(声明于ngx_palloc.h头文件中):

1、内存池操作接口



2、基于内存池的内存操作接口

3、接口的使用范式如下:

从接口的设计来看,一切还算比较明朗,但以下几点还是需要注意:

1) 从ngx_palloc()等内存分配函数的size参数可以看出,基于内存池的内存分配操作不是固定长度的。

2) 内存分配函数考虑了一些两种情况,内存初始化,以及内存地址对齐,因此内存分配操作可以按以下方式分类: 


内存是否初始化
内存地址是否对齐
ngx_palloc

按内部默认方式对齐
ngx_pnalloc

未对齐
ngx_pcalloc
初始化为0
按内部默认方式对齐
ngx_pmemalign

按用户指定方式对齐

3) 基于第1点,我们有理由相信ngx_create_pool()的size参数应该是限制该内存池所能分配的内存块大小的上限。但其实不然,后面关于实现的分析可以看到这点。

4) 在内存池生命周期内,可以通过ngx_free()归还内存块,但其实并不是所有从内存池分配的内存都可以通过该接口来释放的。

5)支持ngx_reset_pool()重置内存池操作


设计与实现:

Nginx内存池的设计中,其所管理的内存有两种类型,blocklarge memory

block是一个内存池每次从系统分配的固定长度的内存块(一般通过malloc分配)block的长度在创建内存池就已经决定(即由ngx_create_poolsize参数所决定),且对于一个内存池而言所有block的长度都是一致的。前面所讨论的ngx_create_pool函数的size参数表示的是,内存池内部实现中一个block的大小。一个block内存有三个方面的用处:1).用于block自身的管理,2).用于large memory的管理(只有需要分配large内存时才存在)3).分配给用户。从接口设计的角度讲,这个参数设计得并不好,因为它需要使用者了解内存池的内部实现,才能明白其准确意义!

large memory则指的是长度超过block限制大小或者是按用户指定内存地址对齐方式分配(ngx_pmemalign函数)的内存块。

以上两种类型的内存在Nginx内存池中,各自通过链表管理起来。

 

Nginx内存池:


block: 


large memory: 


1.内存池管理函数的实现比较简单


点击(此处)折叠或打开

  1. /**创建内存池*/
  2. ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log)
  3. {
  4.     ngx_pool_t *p;

  5. /*分配一个block(大小为size)的内存*/
  6.     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
  7.     if (p == NULL) {
  8.         return NULL;
  9.     }

  10. /*初始化block管理数据
  11. *这是该内存池的第一个block,除了要一部分内存用于管理该block外,
  12. *还有一部分内存被用于管理内存池本身
  13. *由于ngx_pool_t 的定义是内嵌一个ngx_pool_data_t ,
  14. *因此总共用于管理block和内存池的数据长度就是sizeof(ngx_pool_t)*/
  15.     p->d.last = (u_char *) p + sizeof(ngx_pool_t);
  16.     p->d.end = (u_char *) p + size;
  17.     p->d.next = NULL;
  18.     p->d.failed = 0;

  19.     /*设置该内存池能从block分配的内存块的大小限制*/
  20.     size = size - sizeof(ngx_pool_t);
  21.     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

  22.     /*初始化内存池管理数据*/
  23.     p->current = p;//从第一个block开始查询链表,即查询链表头
  24.     p->chain = NULL;
  25.     p->large = NULL;
  26.     p->cleanup = NULL;
  27.     p->log = log;

  28.     return p;
  29. }


点击(此处)折叠或打开

  1. /**销毁内存池*/
  2. void ngx_destroy_pool(ngx_pool_t *pool)
  3. {
  4. ………
  5.     /*遍历large内存链表释放large内存*/
  6.     for (l = pool->large; l; l = l->next) {

  7.         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);

  8.         if (l->alloc) {
  9.             ngx_free(l->alloc);
  10.         }
  11.     }
  12. ………
  13.     /*遍历block链表, 释放block内存*/
  14.     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
  15.         ngx_free(p);

  16.         if (n == NULL) {
  17.             break;
  18.         }
  19.     }
  20.     /*注意以上两个for循环的次序不能颠倒,因为large内存的管理数据位于block中*/
  21. }


点击(此处)折叠或打开

  1. /**重置内存池*/
  2. void ngx_reset_pool(ngx_pool_t *pool)
  3. {
  4.     ngx_pool_t *p;
  5.     ngx_pool_large_t *l;

  6.     /*遍历large链表释放内存给系统*/
  7.     for (l = pool->large; l; l = l->next) {
  8.         if (l->alloc) {
  9.             ngx_free(l->alloc);
  10.         }
  11.     }

  12.     /*遍历block链表, 但不释放内存给系统, 仅将block管理数据重置*/
  13.     for (p = pool; p; p = p->d.next) {
  14.         p->d.last = (u_char *) p + sizeof(ngx_pool_t);
  15.         p->d.failed = 0;
  16.     }

  17.     /*重置查询表头*/
  18.     pool->current = pool;
  19.     pool->chain = NULL;
  20.     pool->large = NULL;
  21. }

2.内存分配,我们从ngx_palloc开始分析,先看流程图



点击(此处)折叠或打开

  1. /**分配内存,返回内存地址按照某种方式对齐,内存未作任何初始化*/
  2. void *ngx_palloc(ngx_pool_t *pool, size_t size)
  3. {
  4.     u_char *m;
  5.     ngx_pool_t *p;

  6.     /*判断分配大小是否超出block现在*/
  7.     if (size <= pool->max) {
  8.     /*大小没有超出block限制*/
  9.         p = pool->current;
  10.         /*dowhile 循环, 尝试从现存block中分配内存*/
  11.         do {
  12.             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//内存地址按NGX_ALIGNMENT对齐处理
  13.             if ((size_t) (p->d.end - m) >= size) {
  14.                 p->d.last = m + size;
  15.                 return m;
  16.             }
  17.             p = p->d.next;
  18.         } while (p);

  19.         /*现存block无法满足分配需求,创建一个新的block用于内存分配*/
  20.         return ngx_palloc_block(pool, size);    
  21.     }
  22.     
  23.     /*大小超出block限制,分配large内存块*/
  24.     return ngx_palloc_large(pool, size);
  25. }

点击(此处)折叠或打开

  1. /**创建一个新的block*/
  2. static void *ngx_palloc_block(ngx_pool_t *pool, size_t size)
  3. {
  4.     u_char *m;
  5.     size_t psize;
  6.     ngx_pool_t *p, *new, *current;

  7.     /*计算block大小, 注意对于一个内存池而言,所有block的大小都是一致的.
  8.      *为什么ngx_pool_t不维护这么一个域,而每次都去动态计算呢?*/
  9.     psize = (size_t) (pool->d.end - (u_char *) pool);

  10.     /*从系统中分配一个block, 并初始化block管理数据,注意管理数据所需的内存也来自block*/
  11.     m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
  12.     if (m == NULL) {
  13.         return NULL;
  14.     }
  15.     new = (ngx_pool_t *) m;
  16.     new->d.end = m + psize;
  17.     new->d.next = NULL;
  18.     new->d.failed = 0;
  19. /*注意第一个block已经在ngx_create_pool中创建了,所有本函数创建的block均由ngx_pool_data_t 管
  20. * 理,因此此处偏移sizeof(ngx_pool_data_t)就ok,没有必要偏移sizeof(ngx_pool_t)*/
  21.     m += sizeof(ngx_pool_data_t);
  22.     m = ngx_align_ptr(m, NGX_ALIGNMENT);//内存地址,按NGX_ALIGNMENT方式对齐
  23.     new->d.last = m + size; //设置有效内存起始位置

  24.     /*更新查询链表头,跳过所有内存分配次数大于4(为什么是4?)的block*/
  25.     current = pool->current;
  26.     for (p = current; p->d.next; p = p->d.next) {
  27.         if (p->d.failed++ > 4) {
  28.             current = p->d.next;
  29.         }
  30.     }

  31.     p->d.next = new;//将新建的block加入block链表

  32.     pool->current = current ? current : new;
  33.     return m;
  34. }



点击(此处)折叠或打开

  1. /**分配large内存*/
  2. static void *ngx_palloc_large(ngx_pool_t *pool, size_t size)
  3. {
  4.     void *p;
  5.     ngx_uint_t n;
  6.     ngx_pool_large_t *large;

  7.     /*从系统分配large内存块*/
  8.     p = ngx_alloc(size, pool->log);
  9.     if (p == NULL) {
  10.         return NULL;
  11.     }

  12.     /*尝试寻找large内存链表中无效的管理数据,更新该管理数据,
  13.     *将新分配的large内存块直接加入large内存链表*/
  14.     n = 0;
  15.     for (large = pool->large; large; large = large->next) {
  16.         if (large->alloc == NULL) {
  17.             large->alloc = p;
  18.             return p;
  19.         }
  20.         if (n++ > 3) {//为啥是3?
  21.             break;
  22.         }
  23.     }

  24.     /*需要从block中重新分配一个块内存用于管理新分配的large内存块
  25.     *注意这里调用了ngx_palloc, 这是一个隐藏比较深的递归调用*/
  26.     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
  27.     if (large == NULL) {
  28.         ngx_free(p);
  29.         return NULL;
  30.     }
  31.     large->alloc = p;
  32.     large->next = pool->large;
  33.     pool->large = large;

  34. /*对比ngx_palloc_large 与ngx_palloc_block ,发现,block的管理数据从block自身分出一般内存来使用的,*而large内存块的管理数据,则不在large内存块中,需另行分配(其实就从block中分配)*/
  35.     return p;
  36. }

ngx_pnalloc的实现除了不对内存地址做对齐处理外,其他与ngx_pnalloc一致;ngx_pcalloc的实现是基于ngx_pnalloc分配内存,然后,将内存块清0。接下来是ngx_pmemalign。


点击(此处)折叠或打开

  1. /**分配内存,返回内存地址按alignment对齐,内存未作任何初始化*/
  2. void * ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
  3. {
  4.     void *p;
  5.     ngx_pool_large_t *large;

  6.     /*用户指定alignment对齐要求,直接从系统中分配large内存块*/
  7.     p = ngx_memalign(alignment, size, pool->log);
  8.     if (p == NULL) {
  9.         return NULL;
  10.     }
  11.     
  12.     /*直接从block中分配管理large内存块的内存,
  13.     * 为什么不像ngx_palloc_large那样先查找有无可用的管理内存块?*/
  14.     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
  15.     if (large == NULL) {
  16.         ngx_free(p);
  17.         return NULL;
  18.     }

  19.     /*将large内存块加入链表中*/
  20.     large->alloc = p;
  21.     large->next = pool->large;
  22.     pool->large = large;

  23.     return p;
  24. }
 最后是内存释放

点击(此处)折叠或打开

  1. /**释放内存*/
  2. ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
  3. {
  4.     ngx_pool_large_t *l;

  5.     /*遍历large链表,释放large内存块,
  6. *注意此处并没有释放管理large内存块的内存(即large链表节点)
  7. *而仅是将其alloc指针设为NULL*/
  8.     for (l = pool->large; l; l = l->next) {
  9.         if (p == l->alloc) {
  10.             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
  11.                            "free: %p", l->alloc);
  12.             ngx_free(l->alloc);
  13.             l->alloc = NULL;
  14.             return NGX_OK;
  15.         }
  16.     }
  17.     
  18.     /*
  19. return NGX_DECLINED;
  20. }


最后以一个内存池快照作总结:




阅读(3540) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

CU博客助理2014-11-25 16:46:45

专家点评:作者从接口说明、接口设计到代码实现,条理清晰的讲解了nginx的内存池管理,是理解nginx内存池的上好博文。如果作者再比较一下libc、java、kernel skb等的内存池管理,那就更加完美了。