NGINX 1.13.6内存管理源码分析

该文是针对NGINX 1.13.6这个版本进行分析。内存管理的源文件集中在以下几个文件中:

  1. src/core/ngx_palloc.h

  2. src/core/ngx_palloc.c

NGINX内存管理相对于REDIS或者MC简单一些,分为大内存和小内存管理。小内存管理,采用分块的模式,块内采用的是连续存储分配方式,块间采用的离散存储分配方式。大内存管理则采用离散存储分配方式。核心数据结构如下:

 

  • 小内存数据区数据结构

typedef struct {

   u_char               *last; // 最近一次分配完成后的地址

   u_char               *end; // 该内存块的末尾结束地址

   ngx_pool_t           *next; // 下一个小内存块的起始地址

   ngx_uint_t            failed; // 内存碎片不足以一次请求分配的计数器

} ngx_pool_data_t;

 

  • 内存池数据结构

struct ngx_pool_s {

   ngx_pool_data_t     d; // 小内存数据区

   size_t              max; // 小内存区可分配的最大内存大小

   ngx_pool_t         *current; // 存放当前可用的小内存数据区,取决于failed计数器

   ngx_chain_t        *chain; // 预留

   ngx_pool_large_t    *large; // 大内存的单向链表的头节点

   ngx_pool_cleanup_t  *cleanup; //单向链表,可以向内存池注册一系列回调函数,当内存池销毁的时候,会遍历该回调链表进行回调。

   ngx_log_t          *log;  // 日志句柄

};

 

  • 大内存数据区数据结构


struct ngx_pool_large_s {

   ngx_pool_large_t     *next; // 指向下一个大内存节点的地址,单向链表

   void                 *alloc; // 大内存区

};

 

  • 这些主要的数据结构的关系如下

 

 

内存池ngx_pool_t管理着一个一个小内存区的单向链表,同时也管理着一个大内存区的单向链表。值得注意的是,内存池也包含一个小内存区域,同内存池同属于一个连续内存区域。如果该内存区域内存不够使用,则在堆上再申请一块连续区域,对于这些新申请的小内存区域的首地址是ngx_pool_t指针以及ngx_pool_data_t指针复用。为什么要复用呢?因为能确保内存池的current指针能寻址到新申请的小内存区域的首地址。

 

  • 创建内存池源码分析

ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t*log)

{

   ngx_pool_t  *p;

        

  // 申请一块连续的内存块,并且16个字节对齐,从寻址效率考虑。有两种选择,POSIX

  // 提供的函数,另外一个选择是采用C标准库提供的函数

  p =ngx_memalign(NGX_POOL_ALIGNMENT, size, log);

  // C语言编程的一个好习惯,内存分配不一定每次都是成功的,需要做判断

   if (p == NULL) {

       return NULL;

    }



   //设置last指针和end指针的位置

   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;

}

 

  • 内存池的释放源码分析

void ngx_destroy_pool(ngx_pool_t *pool)

{

   ngx_pool_t          *p, *n;

    ngx_pool_large_t    *l;

   ngx_pool_cleanup_t  *c;



   //遍历清理回调链表,回调清理函数

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

       }

    }



   // 遍历大内存区链表,释放内存

   for (l = pool->large; l; l = l->next) {

       if (l->alloc) {

           ngx_free(l->alloc);

       }

    }



   // 遍历小内存区链表,释放小内存区

   for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next){

       ngx_free(p);



       if (n == NULL) {

           break;

       }

    }

}

 

  • 内存分配源码分析

// 如果申请的大小超过了一个小内存区域的最大值,就从大内存区域申请内存

void * ngx_palloc(ngx_pool_t *pool, size_t size)

{

#if !(NGX_DEBUG_PALLOC)

   if (size <= pool->max) {

       return ngx_palloc_small(pool, size, 1);

    }

#endif



   return ngx_palloc_large(pool, size);

}



// 从小内存区申请内存

static ngx_inline void * ngx_palloc_small(ngx_pool_t *pool, size_tsize, ngx_uint_t align)

{

   u_char      *m;

   ngx_pool_t  *p;



    //从当前可用的小内存区块开始遍历

    p= pool->current;



do {

       // 将要分配内存的首地址指向最近一次分配的位置

       m = p->d.last;



       //对即将要分配的内存进行内存对齐,C语言编程习惯

       if (align) {

           m = ngx_align_ptr(m, NGX_ALIGNMENT);

       }



        //如果剩余的内存能满足分配,修改last指针,返回分配内存的首地址

        if ((size_t) (p->d.end - m) >= size){

           p->d.last = m + size;



           return m;

       }

                  

       //如果当前小内存区块不能满足分配,遍历下一个小内存区块进行分配

       p = p->d.next;



    }while (p);



   //如果当前的小内存区块都不能满足分配,则重新申请一个新的小内存区

   return ngx_palloc_block(pool, size);

}



// 从堆中申请一块小内存区块

static void * ngx_palloc_block(ngx_pool_t *pool, size_tsize)

{

   u_char      *m;

   size_t       psize;

   ngx_pool_t  *p, *new;



   //计算该区块的大小,注意pool的地址其实是ngx_pool_t和ngx_pool_data_t复用

   psize = (size_t) (pool->d.end - (u_char *) pool);



     //16个字节内存对齐,在堆中申请内存

    m= ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);

   if (m == NULL) {

       return NULL;

    }



    //初始化新的小内存区块的信息

   new = (ngx_pool_t *) m;



   new->d.end = m + psize;

   new->d.next = NULL;

    new->d.failed= 0;



    m+= sizeof(ngx_pool_data_t);

    m= ngx_align_ptr(m, NGX_ALIGNMENT);

   new->d.last = m + size;



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

      // 这个很重要,如果连续四次该区块分配内存失败,

      // 需要将当前可用的小内存区//块指向链表的下一个节点

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

           pool->current = p->d.next;

       }

    }



   p->d.next = new;



   return m;

}



// 大内存区块的分配

static void * ngx_palloc_large(ngx_pool_t *pool, size_tsize)

{

   void              *p;

   ngx_uint_t         n;

   ngx_pool_large_t  *large;

   //在堆中申请内存

    p= ngx_alloc(size, pool->log);

   if (p == NULL) {

       return NULL;

    }



   n= 0;

   //如果当前大内存区链表有可用的节点存放刚分配好的内存,那么分配流程就结束了。

   //如果连续三个节点都是unavailable的,那么就试图从小内存区域中为大内存区域的

   //链表节点分配空间,并将该节点链入大内存区块的链表的链表头。

   for (large = pool->large; large; large = large->next) {

       if (large->alloc == NULL) {

           large->alloc = p;

           return p;

       }



       if (n++ > 3) {

           break;

       }

    }



   large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);

   if (large == NULL) {

       ngx_free(p);

       return NULL;

    }



   large->alloc = p;

   large->next = pool->large;

   pool->large = large;



   return p;

}

 

  • 内存释放源码分析

ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)

{

    ngx_pool_large_t  *l;



   //释放大内存区域,小内存区域不会被释放,除非释放整个内存池。

   for (l = pool->large; l; l = l->next) {

       if (p == l->alloc) {

           ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,

                           "free:%p", l->alloc);

           ngx_free(l->alloc);

           l->alloc = NULL;



           return NGX_OK;

       }

    }



   return NGX_DECLINED;

}

 

  • 内存池重置源码分析

void ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

    // 释放大内存区,将内存归还给系统,但是大内存区的容器不释放
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    // 将小内存区的内存使用指针重置,不释放内存
    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

    // 重置当前可用内存池指针
    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值