zoukankan      html  css  js  c++  java
  • 菜鸟nginx源代码剖析数据结构篇(九) 内存池ngx_pool_t

     

    菜鸟nginx源代码剖析数据结构篇(九) 内存池ngx_pool_t

  • Author:Echo Chen(陈斌)

  • Email:chenb19870707@gmail.com

  • Blog:Blog.csdn.net/chen19870707

  • Date:Nov 11th, 2014

    今天是一年一度的光棍节。还没有女朋友的程序员童鞋最好还是new一个出来,内存管理一直是C/C++中最棘手的部分。远不止new/delete、malloc/free这么简单。

    随着代码量的递增,程序结构复杂度的提高。今天我们就一起研究一下以静止著称的nginx的内存池。

    1.源码位置

    头文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_palloc.h

    源文件:http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_palloc.c

    2.数据结构定义

    先来学习一下nginx内存池的几个主要数据结构:

        ngx_pool_data_t(内存池数据块结构)

       1: typedef struct {
       2:     u_char               *last;        
       3:     u_char               *end;
       4:     ngx_pool_t           *next;
       5:     ngx_uint_t            failed;
       6: } ngx_pool_data_t;
    • last:是一个unsigned char 类型的指针。保存的是/当前内存池分配到末位地址。即下一次分配从此处開始。
    • end:内存池结束位置;
    • next:内存池里面有非常多块内存,这些内存块就是通过该指针连成链表的,next指向下一块内存。
    • failed:内存池分配失败次数。

    ngx_pool_s(内存池头部结构)

       1: struct ngx_pool_s {
       2:     ngx_pool_data_t       d;
       3:     size_t                max;
       4:     ngx_pool_t           *current;
       5:     ngx_chain_t          *chain;
       6:     ngx_pool_large_t     *large;
       7:     ngx_pool_cleanup_t   *cleanup;
       8:     ngx_log_t            *log;
       9: };
    • d:内存池的数据块。
    • max:内存池数据块的最大值;
    • current:指向当前内存池;
    • chain:该指针挂接一个ngx_chain_t结构;
    • large:大块内存链表,即分配空间超过max的情况使用;
    • cleanup:释放内存池的callback
    • log:日志信息

    由ngx_pool_data_t和ngx_pool_t组成的nginx内存池结构例如以下图所看到的:

    201210171140066270

    3.相关函数介绍

    在分析内存池方法前。须要对几个基本的内存相关函数作一下介绍:

    ngx_alloc:(仅仅是对malloc进行了简单的封装)

       1: void *
       2: ngx_alloc(size_t size, ngx_log_t *log)
       3: {
       4:     void  *p;
       5:  
       6:     p = malloc(size);
       7:     if (p == NULL) {
       8:         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
       9:                       "malloc(%uz) failed", size);
      10:     }
      11:  
      12:     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
      13:  
      14:     return p;
      15: }

    ngx_calloc:(调用malloc并初始化为0)

       1: void *
       2: ngx_calloc(size_t size, ngx_log_t *log)
       3: {
       4:     void  *p;
       5:  
       6:     p = ngx_alloc(size, log);
       7:  
       8:     if (p) {
       9:         ngx_memzero(p, size);
      10:     }
      11:  
      12:     return p;
      13: }

    ngx_memzero:

       1: #define ngx_memzero(buf, n)       (void) memset(buf, 0, n)

    ngx_free

       1: #define ngx_free          free

    ngx_memalign

       1: void *
       2: ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
       3: {
       4:     void  *p;
       5:     int    err;
       6:  
       7:     err = posix_memalign(&p, alignment, size);
       8:  
       9:     if (err) {
      10:         ngx_log_error(NGX_LOG_EMERG, log, err,
      11:                       "posix_memalign(%uz, %uz) failed", alignment, size);
      12:         p = NULL;
      13:     }
      14:  
      15:     ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
      16:                    "posix_memalign: %p:%uz @%uz", p, size, alignment);
      17:  
      18:     return p;
      19: }
  • 这里alignment主要是针对部分unix平台须要动态的对齐,对POSIX 1003.1d提供的posix_memalign( )进行封装,在大多数情况下,编译器和C库透明地帮你处理对齐问题。nginx中通过宏NGX_HAVE_POSIX_MEMALIGN来控制;调用posix_memalign( )成功时会返回size字节的动态内存。而且这块内存的地址是alignment的倍数。

    參数alignment必须是2的幂。还是void指针的大小的倍数。返回的内存块的地址放在了memptr里面。函数返回值是0.

  •  

    4.内存池基本操作

      • 内存池对外的主要方法有:
  • 创建内存池 ngx_pool_t *  ngx_create_pool(size_t size, ngx_log_t *log);
    销毁内存池 void ngx_destroy_pool(ngx_pool_t *pool);
    重置内存池 void ngx_reset_pool(ngx_pool_t *pool);
    内存申请(对齐) void *  ngx_palloc(ngx_pool_t *pool, size_t size);
    内存申请(不正确齐) void *  ngx_pnalloc(ngx_pool_t *pool, size_t size);
    内存清除 ngx_int_t  ngx_pfree(ngx_pool_t *pool, void *p);

     

  •  

  • 4.1 创建内存池ngx_create_pool

    ngx_create_pool用于创建一个内存池,我们创建时。传入我们的须要的初始大小:

  •    1: ngx_pool_t *
       2: ngx_create_pool(size_t size, ngx_log_t *log)
       3: {
       4:     ngx_pool_t  *p;
       5:     
       6:     //以16(NGX_POOL_ALIGNMENT)字节对齐分配size内存
       7:     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
       8:     if (p == NULL) {
       9:         return NULL;
      10:     }
      11:  
      12:     //初始状态:last指向ngx_pool_t结构体之后数据取起始位置
      13:     p->d.last = (u_char *) p + sizeof(ngx_pool_t);
      14:     //end指向分配的整个size大小的内存的末尾
      15:     p->d.end = (u_char *) p + size;
      16:     
      17:     p->d.next = NULL;
      18:     p->d.failed = 0;
      19:  
      20:     size = size - sizeof(ngx_pool_t);
      21:     //#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1),内存池最大不超过4095,x86中页的大小为4K
      22:     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
      23:  
      24:     p->current = p;
      25:     p->chain = NULL;
      26:     p->large = NULL;
      27:     p->cleanup = NULL;
      28:     p->log = log;
      29:  
      30:     return p;
      31: }
  • nginx对内存的管理分为大内存与小内存,当某一个申请的内存大于某一个值时,就须要从大内存中分配空间,否则从小内存中分配空间。

  • nginx中的内存池是在创建的时候就设定好了大小。在以后分配小块内存的时候,假设内存不够。则是又一次创建一块内存串到内存池中,而不是将原有的内存池进行扩张。当要分配大块内存是,则是在内存池外面再分配空间进行管理的,称为大块内存池。

  •  

    4.2 内存申请 ngx_palloc

  •    1: void *
       2: ngx_palloc(ngx_pool_t *pool, size_t size)
       3: {
       4:     u_char      *m;
       5:     ngx_pool_t  *p;
       6:  
       7:     //假设申请的内存大小小于内存池的max值
       8:     if (size <= pool->max) {
       9:  
      10:         p = pool->current;
      11:  
      12:         do {
      13:             //对内存地址进行对齐处理
      14:             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
      15:  
      16:             //假设当前内存块够分配内存,则直接分配
      17:             if ((size_t) (p->d.end - m) >= size) 
      18:             {
      19:                 p->d.last = m + size;
      20:  
      21:                 return m;
      22:             }
      23:             
      24:             //假设当前内存块有效容量不够分配,则移动到下一个内存块进行分配
      25:             p = p->d.next;
      26:  
      27:         } while (p);
      28:  
      29:         //当前全部内存块都没有空暇了,开辟一块新的内存,例如以下2详解
      30:         return ngx_palloc_block(pool, size);
      31:     }
      32:  
      33:     //分配大块内存
      34:     return ngx_palloc_large(pool, size);
      35: }

    须要说明的几点:

    1、ngx_align_ptr。这是一个用来内存地址取整的宏。很静止,一句话就搞定了。作用不言而喻,取整能够减少CPU读取内存的次数,提高性能。由于这里并没有真正意义调用malloc等函数申请内存,而是移动指针标记而已,所以内存对齐的活,C编译器帮不了你了,得自己动手。

       1: #define ngx_align_ptr(p, a)                                                   
       2:      (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

    2、开辟一个新的内存块 ngx_palloc_block(ngx_pool_t *pool, size_t size)

    这个函数是用来分配新的内存块,为pool内存池开辟一个新的内存块。并申请使用size大小的内存。

       1: static void *
       2: 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;
       7:  
       8:     //计算内存池第一个内存块的大小
       9:     psize = (size_t) (pool->d.end - (u_char *) pool);
      10:  
      11:     //分配和第一个内存块相同大小的内存块
      12:     m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
      13:     if (m == NULL) {
      14:         return NULL;
      15:     }
      16:  
      17:     new = (ngx_pool_t *) m;
      18:  
      19:     //设置新内存块的end
      20:     new->d.end = m + psize;
      21:     new->d.next = NULL;
      22:     new->d.failed = 0;
      23:  
      24:     //将指针m移动到d后面的一个位置,作为起始位置
      25:     m += sizeof(ngx_pool_data_t);
      26:     //对m指针按4字节对齐处理
      27:     m = ngx_align_ptr(m, NGX_ALIGNMENT);
      28:     //设置新内存块的last。即申请使用size大小的内存
      29:     new->d.last = m + size;
      30:  
      31:     //这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度。假设分配失败次数达到5次。就忽略,不须要每次都从头找起
      32:     for (p = pool->current; p->d.next; p = p->d.next) {
      33:         if (p->d.failed++ > 4) {
      34:             pool->current = p->d.next;
      35:         }
      36:     }
      37:  
      38:     p->d.next = new;
      39:  
      40:     return m;
      41: }
  • 3、分配大块内存 ngx_palloc_large(ngx_pool_t *pool, size_t size)

    ngx_palloc中首先会推断申请的内存大小是否超过内存块的最大限值,假设超过,则直接调用ngx_palloc_large。进入大内存块的分配流程;

       1: static void *
       2: 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:  
       8:     // 直接在系统堆中分配一块大小为size的空间
       9:     p = ngx_alloc(size, pool->log);
      10:     if (p == NULL) {
      11:         return NULL;
      12:     }
      13:  
      14:     n = 0;
      15:  
      16:     // 查找到一个空的large区,假设有,则将刚才分配的空间交由它管理  
      17:     for (large = pool->large; large; large = large->next) {
      18:         if (large->alloc == NULL) {
      19:             large->alloc = p;
      20:             return p;
      21:         }
      22:         //为了提高效率, 假设在三次内没有找到空的large结构体,则创建一个
      23:         if (n++ > 3) {
      24:             break;
      25:         }
      26:     }
      27:  
      28:  
      29:     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
      30:     if (large == NULL) {
      31:         ngx_free(p);
      32:         return NULL;
      33:     }
      34:     
      35:     //将large链接到内存池
      36:     large->alloc = p;
      37:     large->next = pool->large;
      38:     pool->large = large;
      39:  
      40:     return p;
      41: }
    整个内存池分配例如以下图:
    20121017114009366
    • 4.3 内存池重置 ngx_reset_pool

       1: void
       2: ngx_reset_pool(ngx_pool_t *pool)
       3: {
       4:     ngx_pool_t        *p;
       5:     ngx_pool_large_t  *l;
       6:     
       7:     //释放大块内存
       8:     for (l = pool->large; l; l = l->next) {
       9:         if (l->alloc) {
      10:             ngx_free(l->alloc);
      11:         }
      12:     }
      13:     
      14:     // 重置全部小块内存区
      15:     for (p = pool; p; p = p->d.next) {
      16:         p->d.last = (u_char *) p + sizeof(ngx_pool_t);
      17:         p->d.failed = 0;
      18:     }
      19:  
      20:     pool->current = pool;
      21:     pool->chain = NULL;
      22:     pool->large = NULL;
      23: }

    4.4 内存池释放 ngx_pfree

  •    1: ngx_int_t
       2: ngx_pfree(ngx_pool_t *pool, void *p)
       3: {
       4:     ngx_pool_large_t  *l;
       5:  
       6:     //仅仅检查是否是大内存块。假设是大内存块则释放
       7:     for (l = pool->large; l; l = l->next) {
       8:         if (p == l->alloc) {
       9:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
      10:                            "free: %p", l->alloc);
      11:             ngx_free(l->alloc);
      12:             l->alloc = NULL;
      13:  
      14:             return NGX_OK;
      15:         }
      16:     }
      17:  
      18:     return NGX_DECLINED;
      19: }

    所以说Nginx内存池中大内存块和小内存块的分配与释放是不一样的。我们在使用内存池时。能够使用ngx_palloc进行分配。使用ngx_pfree释放。而对于大内存,这样做是没有问题的,而对于小内存就不一样了,分配的小内存。不会进行释放。由于大内存块的分配仅仅对前3个内存块进行检查,否则就直接分配内存,所以大内存块的释放必须及时。

    4.5 外部资源的清理

    Nginx内存池支持通过回调函数,对外部资源的清理。ngx_pool_cleanup_t是回调函数结构体,它在内存池中以链表形式保存,在内存池进行销毁时,循环调用这些回调函数对数据进行清理。

       1: typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
       2:  
       3: struct ngx_pool_cleanup_s {
       4:     ngx_pool_cleanup_pt   handler;
       5:     void                 *data;
       6:     ngx_pool_cleanup_t   *next;
       7: };

    当中

        • handler:是回调函数指针;
        • data:回调时,将此数据传入回调函数;

    next://指向下一个回调函数结构体;

    假设我们须要加入自己的回调函数,则须要调用ngx_pool_cleanup_add来得到一个ngx_pool_cleanup_t,然后设置handler为我们的清理函数,并设置data为我们要清理的数据。

    这样在ngx_destroy_pool中会循环调用handler清理数据;

       1: ngx_pool_cleanup_t *
       2: ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
       3: {
       4:     ngx_pool_cleanup_t  *c;
       5:     
       6:     //分配ngx_pool_cleanup_t
       7:     c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
       8:     if (c == NULL) {
       9:         return NULL;
      10:     }
      11:  
      12:     //给data分配内存
      13:     if (size) {
      14:         c->data = ngx_palloc(p, size);
      15:         if (c->data == NULL) {
      16:             return NULL;
      17:         }
      18:  
      19:     } else {
      20:         c->data = NULL;
      21:     }
      22:  
      23:     //将回掉函数链入内存池
      24:     c->handler = NULL;
      25:     c->next = p->cleanup;
      26:  
      27:     p->cleanup = c;
      28:  
      29:     ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
      30:  
      31:     return c;
      32: }

    比方:我们能够将一个开打的文件描写叙述符作为资源挂载到内存池上,同一时候提供一个关闭文件描写叙述的函数注冊到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来处理文件描写叙述符资源了。

    201210171140326004
  • 4.6 内存池销毁 ngx_destroy_pool

       1: void
       2: ngx_destroy_pool(ngx_pool_t *pool)
       3: {
       4:     ngx_pool_t          *p, *n;
       5:     ngx_pool_large_t    *l;
       6:     ngx_pool_cleanup_t  *c;
       7:  
       8:     //依次调用外部析构回调函数
       9:     for (c = pool->cleanup; c; c = c->next) {
      10:         if (c->handler) {
      11:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
      12:                            "run cleanup: %p", c);
      13:             c->handler(c->data);
      14:         }
      15:     }
      16:     
      17:     //释放大块内存
      18:     for (l = pool->large; l; l = l->next) {
      19:  
      20:         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
      21:  
      22:         if (l->alloc) {
      23:             ngx_free(l->alloc);
      24:         }
      25:     }
      26:     //释放小块内存
      27:     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
      28:         ngx_free(p);
      29:  
      30:         if (n == NULL) {
      31:             break;
      32:         }
      33:     }
      34: }

    5.參考资料

    1.http://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html

    2.《深入理解Nginx》

  • -Echo Chen

  • Blog.csdn.net/chen19870707

  • -

查看全文
  • 相关阅读:
    poj 2763 Housewife Wind
    hdu 3966 Aragorn's Story
    poj 1655 Balancing Act 求树的重心
    有上下界的网络流问题
    URAL 1277 Cops and Thieves 最小割 无向图点带权点连通度
    ZOJ 2532 Internship 网络流求关键边
    ZOJ 2760 How Many Shortest Path 最大流+floyd求最短路
    SGU 438 The Glorious Karlutka River =) 拆点+动态流+最大流
    怎么样仿写已知网址的网页?
    5-10 公路村村通 (30分)
  • 原文地址:https://www.cnblogs.com/wzjhoutai/p/7083270.html
  • Copyright © 2011-2022 走看看