nginx内存池学习

最近学习nginx,今天看了下有关内存池方面的东东,记录下来作为总结学习,不正确或者片面之处后续再修改。

内存池作用:

统一建立内存池,当需要进行内存分配的时候统一通过内存池中的内存进行分配,最后nginx会在适当的时候释放内存池的资源,开发者只要在需要 的时候对内存进行申请即可,不用过多考虑内存的释放等问题,大大提高了开发的效率。

 

内存池数据结构:

//管理和维护整个内存池头部信息,一个内存池只有一个。
struct ngx_pool_t {  		   
    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的内存请求),nginx中,大块内存分配直接采用标准系统接口malloc  
    ngx_pool_cleanup_t   *cleanup;   //析构函数(回调函数),挂载内存释放时需要清理资源的一些必要操作  
    ngx_log_t            *log;       //内存分配相关的日志记录  
}; 
//该结构用来维护内存池的数据块,供用户分配之用
typedef struct {
    u_char               *last;  //当前内存分配结束位置,即下一段可分配内存的起始位置
    u_char               *end;   //内存池结束位置
    ngx_pool_t           *next;  //内存池里面有很多块内存,这些内存块就是通过该指针连成链表的
    ngx_uint_t            failed;//统计该内存池不能满足分配请求的次数,即分配失败次数
} ngx_pool_data_t;//内存池的数据块位置信息 
其中,<span lang="zh-CN" style="font-family:'Lucida Console';">sizeof(ngx_pool_data_t)</span><span lang="en-US" style="font-family:'Lucida Console';">=16B</span><span lang="zh-CN" style="font-family:宋体;">,</span><span lang="zh-CN" style="font-family:'Lucida Console';">sizeof(ngx_pool_t)</span><span lang="en-US" style="font-family:'Lucida Console';">=40B</span><span lang="zh-CN" style="font-family:宋体;"></span>。
//大内存结构
struct ngx_pool_large_s {
    ngx_pool_large_t     *next; //下一个大块内存
    void                 *alloc;//指向分配的大块内存
};
超过max的内存请求

 

以上结构逻辑关系如下:

 

内存池相关操作:

1)创建

//创建内存池
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;
}

总结起来,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;

物理机构图如下:


其中,ngx_memalign(NGX_POOL_ALIGNMENT, size, log)函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h #defineNGX_POOL_ALIGNMENT  16  因此,nginx的内存池分配,是以16字节为边界对齐的。

2)销毁

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

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) {
       ...
  }

3)内存申请

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

创建的内存池中分配200B的内存,调用ngx_palloc(pool, 200)后,该内存池物理结构如下图:

 

 


//链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存 

//大块内存的申请
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==》malloc;
创建内存池或者小数据分配新的内存池块使用ngx_memalign==》posix_memalign或者memalign;

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值