nginx内存池基本原理及问题

来源:互联网 发布:python 替换字符串 编辑:程序博客网 时间:2024/05/17 22:10

nginx的内存池相关文章已经很多了,这里写一下简单原理和最近碰到的问题。

用到的几个结构,相应说明请看注释:

//每次能从pool分配的最大内存块大小,ngx_pagesize在X86下一般是4096,即4k,也就是说每次能从pool分配的最大内存块大小为4095字节,将近4k#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)//默认pool大小:16k#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)struct ngx_pool_large_s {  /*大块内存数据结构*/    ngx_pool_large_t     *next;  /*其实是一个头插法的单链表,每次分配一个大块内存都将列表节点插入到这个链表的表头*/    void                 *alloc; /*大块内存是直接用malloc来分配的,alloc就是用来保存分配到的内存地址*/};typedef struct {  /*一个内存池是由多个pool节点组成的链,这个结构用来链接各个pool节点和保存pool节点可用的内存区域起止地址*/    u_char               *last;  /*当前内存分配结束位置,即下一段可分配内存的起始位置*/    u_char               *end;   /*该pool节点内存结束地址*/    ngx_pool_t           *next;  /*下一个pool节点地址*/    ngx_uint_t            failed;/*该pool节点分配内存失败的次数*/} ngx_pool_data_t;struct ngx_pool_s {    /*内存池控制结构*/    ngx_pool_data_t       d;       /*当前pool节点信息*/    size_t                max;     /*一次能分配的最大内存大小*/    ngx_pool_t           *current; /*用来保存当前从哪个pool上分配内存的pool指针,每次分配内存都会从current指向的pool上分配*/    ngx_chain_t          *chain;    ngx_pool_large_t     *large;   /*大块内存列表,*/    ngx_pool_cleanup_t   *cleanup;    ngx_log_t            *log;};


先看一下创建内存池的实现代码,不到20行的代码,很简单的:

ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){    ngx_pool_t  *p;    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);    if (p == NULL) {        return NULL;    }    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;}

从上面的代码可以知道max最大为4095,也就是说每次申请的内存最大大小为4095字节,超出则使用大块内存(参考ngx_palloc和ngx_palloc_large的实现,这里不讲了)。

在X64下(下同),调用pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, log)后,得到的pool内存大小及布局如下:


last和end指针指向的内存区域就是可用空间,pool头已经占80个字节了,所以可用空间比创建时指定的pool大小少80个字节,这里是我觉得设计得不合理的地方,这个pool的大小应该等于用户在创建pool时指定的大小加上pool头大小,这样用户创建了多少就能用多少。

得到pool之后就可以从里面分配内存了,先来看一下分配内存的函数实现,也是几行代码:

void *ngx_palloc(ngx_pool_t *pool, size_t size){    u_char      *m;    ngx_pool_t  *p;    if (size <= pool->max) {        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);    }    return ngx_palloc_large(pool, size);}

比如调用p = ngx_palloc(pool, 32);  last会向后移动32个字节,内存布局如下:


假设这个pool可用空间不够了,那么会调用ngx_palloc_block分配一个与当前pool大小一样的pool,并将该pool挂在内存池链上和将last指针调整好之后直接返回给用户可用的内存地址,如下图是调用q = ngx_palloc(pool, 128);后的内存布局,左边是可用空间不够的pool,右边是ngx_palloc_block分配的pool:


以上就是nginx内存池的基本原理,首先分配一个大块内存,然后每次分配小块内存时直接修改last指针后直接返回内存地址,非常之高效。

基本原理讲完了,再来看看ngx_palloc的bug,这个bug隐藏得比较深,耗费了好几天才搞定。这个bug是我同事KawaruNagisa发现的,他也给官网提bug并accept了,相信下个版本会得到修复的。我接触nginx时间不是很长,这个bug正好有机会来研究研究nginx的内存池实现。先来看看ngx_palloc的几行代码:

void *ngx_palloc(ngx_pool_t *pool, size_t size){……            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);           //内存对齐,在64位系统下是8字节对齐            if ((size_t) (p->d.end - m) >= size) {                p->d.last = m + size;                return m;            }……}

上面的代码中本意是先得到对齐之后的m,并和end指针对比,如果end和m之间的可用空间大于等于要分配的size,那么就分配成功,并将last向后移动。但是还有另一种情况没有考虑到:假设 last=28, end=30, size = 32, end-last=2,即end和last之间只有2个字节的可用空间,那么将last 8字节对齐之后为32,即m=32,那么end-m=-2,-2再转换成size_t则变成了18446744073709551614,再跟size相比,肯定为true,接着调整last指针并返回m;而我们要分配32个字节,应该再分配一个pool并在这个pool上分配内存,显然是bug啊。用gdb模拟一下:


所以上面代码中if条件里应该加上p->d.end > m ,修复后的ngx_palloc应该如下:

void *ngx_palloc(ngx_pool_t *pool, size_t size){    u_char      *m;    ngx_pool_t  *p;    if (size <= pool->max) {        p = pool->current;        do {            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);            if (p->d.end > m && (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);    }    return ngx_palloc_large(pool, size);}

另一种避免这个Bug的方法是创建pool时(ngx_create_pool)指定的size要按NGX_ALIGNMENT字节对齐,否则比较容易出Bug。


更详细的nginx内存池讲解请参考:

http://blog.csdn.net/v_july_v/article/details/7040425

0 0
原创粉丝点击