nginx内存池

nginx内存池

拿来主义:https://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html

参考:http://blog.csdn.net/houjixin/article/details/56294550

1、基本结构

先来学习一下nginx内存池的几个主要数据结构:[见:./src/core/ngx_palloc.h/.c]

ngx_pool_data_t(内存池数据块结构)

typedef struct {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *next;
    ngx_uint_t           failed;
} ngx_pool_data_t;

ngx_pool_s(内存池头部结构)

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

可以说,ngx_pool_data_t和ngx_pool_s基本构成了nginx内存池的主体结构,下面详细介绍一下nginx内存池的主体结构:


nginx内存池模型

如上图,nginx的内存池实际是一个由ngx_pool_data_t和ngx_pool_s构成的链表,其中:

ngx_pool_data_t中:

last:是一个unsigned char 类型的指针,保存的是/当前内存池分配到末位地址,即下一次分配从此处开始。
end:内存池结束位置;
next:内存池里面有很多块内存,这些内存块就是通过该指针连成链表的,next指向下一块内存。
failed:内存池分配失败次数。

ngx_pool_s

d:内存池的数据块;
max:内存池数据块的最大值;
current:指向当前内存池;
chain:该指针挂接一个ngx_chain_t结构;
large:大块内存链表,即分配空间超过max的情况使用;
cleanup:释放内存池的callback
log:日志信息

以上是内存池涉及的主要数据结构,为了尽量简化,其他涉及的数据结构将在下面实际用到时候再做介绍。

2、内存池基本操作

内存池对外的主要方法有:

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

2.1、内存池创建(ngx_create_pool)

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

//通过ngx_create_pool可以创建一个内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p; 

    // #define NGX_POOL_ALIGNMENT       16 
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  // 分配内存函数,uinx,windows分开走
    if (p == NULL) {
        return NULL;
    }   
    
    p->d.last = (u_char *) p + sizeof(ngx_pool_t); //初始指向 ngx_pool_t 结构体后面
    p->d.end = (u_char *) p + size; //整个结构的结尾后面
    p->d.next = NULL;
    p->d.failed = 0;
        
    //sizeof(ngx_pool_t)用来存储自身
    size = size - sizeof(ngx_pool_t);
    
    // #define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
    // 最大不超过 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小
    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;
}

nginx对内存的管理分为大内存与小内存,当某一个申请的内存大于某一个值时,就需要从大内存中分配空间,否则从小内存中分配空间。

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

2.2、内存申请

ngx_palloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;

    // pool->max 数据块大小,即小块内存的最大值
    if (size <= pool->max) {

        // pool->current 保存当前内存值
        p = pool->current;

        do {
            m = ngx_align_ptr(p->d.last, 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);
    }
    //size过大,需要在大数据块内存链表上申请内存空间
    return ngx_palloc_large(pool, size);
}

这里需要说明的几点:

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

#define ngx_align_ptr(p, a)                                           \
(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大小的内存;

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

    current = pool->current;

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

3、ngx_palloc_large(ngx_pool_t *pool, size_t size)

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

//控制大块内存的申请
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); // 在大数据块链表中申请调用了ngx_alloc
    if (p == NULL) {
       return NULL;
    }

    n = 0;

    // 查找到一个空的large区,如果有,则将刚才分配的空间交由它管理
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    // 为了提高效率, 如果在三次内没有找到空的large结构体,则创建一个
    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;
}
nginx内存池模型

2.3、内存池重置

ngx_reset_pool

//重置,将内存池恢复到初始分配的状态
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); //释放大块内存
     }
    }

    pool->large = NULL;

    for (p = pool; p; p = p->d.next) {
       p->d.last = (u_char *) p + sizeof(ngx_pool_t); //小块内存结尾指针指向刚分配的位置,其中的数据将被覆盖
    }
}

2.4、内存池清理

ngx_pfree

//控制大块内存的释放。注意,这个函数只会释放大内存,不会释放其对应的头部结构,遗留下来的头部结构会做下一次申请大内存之用
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;
}

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

ngx_pool_cleanup_s

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

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s  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清理数据;

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

image

2.5、内存池销毁

ngx_destroy_pool

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

//销毁内存池
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) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
        if (l->alloc) {
            ngx_free(l->alloc); // 直接ngx_free,宏,本质是free
    }
}

#if (NGX_DEBUG)

/*
 * we could allocate the pool->log from this pool
 * so we cannot use this log while 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;
        }
    }

#endif

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

        if (n == NULL) {
          break;
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352