Nginx内存池源码解析

本文深入解析Nginx内存池的工作原理,包括其结构、内存分配与回收机制,以及如何处理大、小块内存的不同策略。通过分析ngx_create_pool、ngx_palloc等核心函数,揭示Nginx高效内存管理的秘密。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  当从内存池中存取数据的时候,nginx是分为两种类型来处理的,一种是小块数据,他是直接从内存块中取得数据,另一种是当为大数据块的时候,直接malloc一个内存块(内存池外部分配数据),然后保存这个指针到内存池。

Nginx内存池的主体结构

1

内存池的头部结构

struct ngx_pool_s {
        ngx_pool_data_t       d;   //数据区的指针
        size_t                max;  //内存池所能容纳的最大值
        ngx_pool_t           *current; //指向当前内存块的头
        ngx_chain_t          *chain;  //将内存池连接起来
        ngx_pool_large_t     *large;  // 这个链表表示大内存块
        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;

ngx_pool_data_t d 数据区指针包含我们所需要操作的这个内存池的数据的指针

last表示已经使用数据的结尾,end标识当前内存池的结尾,也就是说end-last就是当前内存池未使用的大小

通常我们知道,在普通内存池下,我们去请求内存,如果这时候内存满了,就会扩大内存池。nginx则不会这么做,他会直接分配一个内存池,然后链接到data的next指针。在nginx里,每个内存池都拥有自己的子内存池,我们请求的时候就会遍历这些内存池。

failed 是为了标记我们请求内存(小内存块)由于内存池空间不足,我们需要重新分配一个子内存池的次数。

内存池的基本操作

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

创建内存池

///内存池的数据区的最大容量。
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t *p;
///可以看到直接分配size大小,也就是说我们只能使用size-sizeof(ngx_poll_t)大小
    p = ngx_alloc(size, log);
    if (p == NULL)
    {
        return NULL;
    }
///开始初始化数据区。
///由于一开始数据区为空,因此last指向数据区的开始。
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
///end也就是数据区的结束位置
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;
///这里才是我们真正能使用的大小。
    size = size - sizeof(ngx_pool_t);
///然后设置max。内存池的最大值也就是size和最大容量之间的最小值。
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
///current表示当前的内存池。
    p->current = p;
///其他的域置NULL。
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;
///返回指针。
    return p;
}

内存申请

在nginx中有3个函数可用,分别是ngx_palloc,ngx_calloc,ngx_pnalloc。这三个函数的区别就是第一个函数分配会对齐,第二个函数用来分配一块清0的内存。第三个函数分配的内存不会对齐。

我们只分析第一个函数。

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char *m;
    ngx_pool_t *p;
///首先判断当前申请的大小是否超过max,如果超过则说明是大块,此时进入large
    if (size <= pool->max) 
    {
        ///得到当前的内存池指针。
        p = pool->current;
        ///开始遍历内存池,
        do {
        ///首先对齐last指针。
                m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
///然后得到当前内存池中的可用大小。如果大于请求大小,则直接返回当前的last,也就是数据的指针。
                if ((size_t) (p->d.end - m) >= size) 
                     {
                        ///更新last,然后返回前面保存的last。
                        p->d.last = m + size;
                        return m;
                     }
        ///否则继续遍历
                 p = p->d.next;
            } while (p);
///到达这里说明内存池已经满掉,因此我们需要重新分配一个内存池然后链接到当前的data的next上。(紧接着我们会分析
            return ngx_palloc_block(pool, size);
        }
        ///申请大块。
            return ngx_palloc_large(pool, size);
}

我们再来看看 ngx_palloc_block函数的实现,这个函数就是重新分配一块内存池,然后链接到当前内存池的数据域指针。

 重新new的这个内存池和父内存池大小是一样的。

并且新得到的这个内存池只保存了ngx_pool_data_t这个结构,也就是说数据区直接跟在ngx_pool_data_t下

 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);//计算内存池第一个内存块的大小
  
         m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配和第一个内存块同样大小的内存块
          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移动到d后面的一个位置,作为起始位置
             m = ngx_align_ptr(m, NGX_ALIGNMENT);//对m指针按4字节对齐处理
             new->d.last = m + size;//设置新内存块的last,即申请使用size大小的内存
    
            current = pool->current;
       //这里的循环用来找最后一个链表节点,这里failed用来控制循环的长度,如果分配失败次数达到5次,
        //就忽略,不需要每次都从头找起
           for (p = current; p->d.next; p = p->d.next) {
               if (p->d.failed++ > 4) {
               current = p->d.next;
               }
           }
    
       p->d.next = new;
    
       pool->current = current ? current : new;
    
       return m;
   }

 

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:     p = ngx_alloc(size, pool->log);
   9:     if (p == NULL) {
  10:         return NULL;
  11:     }
  12:  
  13:     n = 0;
  14:     // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理  
  15:     for (large = pool->large; large; large = large->next) {
  16:         if (large->alloc == NULL) {
  17:             large->alloc = p;
  18:             return p;
  19:         }
  20:  
  21:         if (n++ > 3) {
  22:             break;
  23:         }
  24:     }
  25:     //为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
  26:     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
  27:     if (large == NULL) {
  28:         ngx_free(p);
  29:         return NULL;
  30:     }
  31:  
  32:     large->alloc = p;
  33:     large->next = pool->large;
  34:     pool->large = large;
  35:  
  36:     return p;
  37: }

分配完了,我们来看释放,在nginx中,只有大内存提供释放的接口,可以供我们手工释放,但是小内存没有释放的接口,只有当整个内存池被销毁,才会被释放。

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

ngx_destroy_pool

ngx_destroy_pool这个函数用于销毁一个内存池:

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:     //最后释放所有内存池中的内存块
  28:     for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
  29:         ngx_free(p);
  30:  
  31:         if (n == NULL) {
  32:             break;
  33:         }
  34:     }
  35: }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值