Nginx内存池源码分析

Nginx内存池

Nginx简介

Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。
其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

内存池概述

1 内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存。
2 内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片。

Nginx内存池概述

nginx为每一个层级都会创建一个内存池,进行内存的管理,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操作系统。
在分配的内存上,nginx有小块内存和大块内存的概念,在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_poolngx_reset_pool的时候一并释放。
大块内存与小块内存的界限

在创建内存池的函数中有这样一行代码

p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

x86体系结构下,ngx_pagesize一般为4096B,即4K,那么NGX_MAX_ALLOC_FROM_POOL为4095B

nginx中的开辟内存函数

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

ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = malloc(size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "malloc(%uz) failed", size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;
}

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

ngx_calloc(size_t size, ngx_log_t *log)
{
    void  *p;

    p = ngx_alloc(size, log);

    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

其中的ngx_memzero用的是memset

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

ngx_free

#define ngx_free          free

nginx内存池中用到的数据结构

**struct ngx_pool_s** {               /* 内存池的管理模块,即内存池头部结构 */  
    ngx_pool_data_t       d;      /* 内存池的数据块 */  
    size_t                max;    /* 内存池数据块的最大值 */  
    ngx_pool_t           *1current;/* 指向当前内存池 */  
    ngx_chain_t          *chain;  /* 指向一个 ngx_chain_t 结构 */  
    ngx_pool_large_t     *large;  /* 大块内存链表,即分配空间超过 max 的内存 */  
    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;            /* 内存池的数据块,即小块内存 */ 
struct ngx_pool_large_s {  //大块内存头部结构
    ngx_pool_large_t     *next;  //指向下一个大块内存
    void                 *alloc; //指向当前大块内存
};

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

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data; 
    ngx_pool_cleanup_t   *next;
};

其中
handler:是回调函数指针;
data:回调时,将此数据传入回调函数;
next://指向下一个回调函数结构体;
如果我们需要添加自己的回调函数,则需要调用ngx_pool_cleanup_add来得到一个ngx_pool_cleanup_t,然后设置handler为我们的清理函数,并设置data为我们要清理的数据。这样在ngx_destroy_pool中会循环调用handler清理数据;

内存池的主要操作

对外的方法主要有以下:

创建内存池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_pnalloc(ngx_pool_t *pool, size_t size);
内存申请(对齐)void * ngx_palloc(ngx_pool_t *pool, size_t size);
内存清除void * ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);;

图(网上找的)
在这里插入图片描述

源码分析

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

 ngx_pool_t *
   2: ngx_create_pool(size_t size, ngx_log_t *log)
   3: {
   4:     ngx_pool_t  *p;
   5:  
   6:     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
   7:     if (p == NULL) {
   8:         return NULL;
   9:     }
  10:  
  11:     p->d.last = (u_char *) p + sizeof(ngx_pool_t);//初始状态:last指向ngx_pool_t结构体之后数据取起始位置
  12:     p->d.end = (u_char *) p + size;//end指向分配的整个size大小的内存的末尾
  13:     p->d.next = NULL;
  14:     p->d.failed = 0;
  15:     //#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
  16:     //内存池最大不超过4095,4K
  17:     size = size - sizeof(ngx_pool_t);
  18:     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
  19:  
  20:     p->current = p;
  21:     p->chain = NULL;
  22:     p->large = NULL;
  23:     p->cleanup = NULL;
  24:     p->log = log;
  25:  
  26:     return p;
  27: }

内存分配函数
1 ngx_palloc

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

2 ngx_pnalloc

ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);
    }
#endif

    return ngx_palloc_large(pool, size);
}

两者的区别:两者都调用了ngx_palloc_small但传入参数不同为1 和0。
一个进行内存对齐一个不进行
我们看ngx_palloc_small函数
用来分配小块内存

static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    p = pool->current;

    do {
        m = p->d.last;

        if (align) {
            m = ngx_align_ptr(m, NGX_ALIGNMENT);//用来内存对齐
        }

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

其中的内存对齐 ngx_align_ptr

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

在这里插入图片描述

分配大块内存 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: }

分配新的内存块ngx_palloc_block

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

重置内存池ngx_reset_pool

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

摧毁内存池ngx_destroy_pool

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

#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool->large; l; l = l->next) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
    }

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

#endif

    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;
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值