Nginx基础知识. 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;         //大块内存链表. 该链表中的元素申请内存大于max
    ngx_pool_cleanup_t   *cleanup;       //用于释放内存的结构. 这个结构接触到就知道干嘛了.
    ngx_log_t            *log;           //日志信息对象
};
typedef struct {
    u_char               *last;          //如果需要分配内存出去, 那么就是从last开始分配
    u_char               *end;           //整个内存块的结束位置
    ngx_pool_t           *next;          //链接下一个内存块
    ngx_uint_t            failed;        //内存分配失败的次数, 一定次数后就不再考虑向这块内存申请内存
} ngx_pool_data_t;
这两个结构体涵盖了Nginx内存池的大部分内容.

在我64位机器上 sizeof(ngx_pool_data_t) = 32;   sizeof(struct ngx_pool_s) = 80;

我这里画的简陋, 又不好盗图, 主要参见此博客:http://blog.csdn.net/livelylittlefish/article/details/6586946

                                                  



2. 内存池的一些操作
1、首先
创建内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

     //利用memalign创建一段size大小的内存段.函数memalign将分配一个由size指定大小,地址是boundary的倍数的内存块。
     //参数boundary必须是2的幂!函数memalign可以分配较大的内存块,并且可以为返回的地址指定粒度。
    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大小需要减去为ngx_pool_t分配的部分
    size = size - sizeof(ngx_pool_t);
     //每块内存都有一个最大值
    //#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) . 一般为4095
    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函数最后的变量初始化中, current表示这个内存池当前指向的内存块. 因为现在只有一个块, 且这个块就在内存池头部里, 所以直接指向自身.
large在上面注释中是指大块内存链表, 大块是指申请的size大于内存池头部里的max变量从而导致申请的内存块, 通过这种方式申请的内存快都会被连在这里

cleanup会在之后的函数中提到.

                                                             

2、接着

在已有内存池的前提下进行的内存申请动作
按照上面讲的关于large的变量来理解
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;


     //如果申请的size符合条件(小于max)
    if (size <= pool->max) {


        p = pool->current;


        do {
               //下面的ngx_align_ptr是一个宏, 用于将地址对齐, 这样对这段内存进行操作的时候, 可以减少CPU访问内存次数
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);     


               //如果有足够的空间, 就返回这段空间的首地址, 记得调整last指针
            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大于我们指定的max, 此时就需要申请大内存了
    return ngx_palloc_large(pool, size);
}

假如当初我们创建的第一块内存的大小为1024, 因为内存池头部占去80字节, 现在又因需求而申请200字节大小的内存. 内存池的现状如下所示.

                                                                           


//根据上面的函数, 下面给出申请新的内存块的函数
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;


     //得到第一块内存的大小
    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;


     //这里需要注意一下, 新申请的内存块, 虽然是通过ngx_pool_t指向的, 但是我们可以在这段添加内存快的函数中发现,
     //并没有使用到ngx_pool_t中除了ngx_pool_data_t之外的内容, 且下面可以看到, m是从ngx_pool_data_t之后的地址开始的
     //所以新增的内存块一般都比第一块内存块大一些
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;


     //下面的循环一个目的是找到内存块链表的最后一块
     //另一个目的是查找是否有失败次数超过一定次数的内存块.一般分配新内存块的原因都是因为之前的内存块分配失败
     //    所以下面会让链表中的每个内存快失败次数加一.
     //最后一个目的是, 让pool->current修改为失败次数达到要求的下一个内存块, 这样一来, 不用每次都从头开始找
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }


    p->d.next = new;


    return m;
}

申请过了一块200大小的内存后, 又有需求800大小的内存, 满足小于max的限制, 但是显然第一块内存已经无法分配出这么大一块的内存了, 所以此时需要重新申请一块同样大小的内存块用来分配




//之后, 就是关于大内存块的部分
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;


     //下面的ngx_alloc函数, 是对malloc的封装, 直接分配size大小的内存
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }


    n = 0;
     //从pool中large指向的内存开始找, 看是否有挂着的结构体, 但是其结构体中的数据是空的的结构体. (因为释放产生的)
     //有就直接把这段内存挂上去
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }
          //找3次后就不再继续找
        if (n++ > 3) {
            break;
        }
    }
     //而是直接分配一个结构体
    large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }
     //且把该结构体挂在large头部
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;


    return p;
}

大块内存的数据结构如下所示:

typedef struct ngx_pool_large_s ngx_pool_large_t;

struct ngx_pool_large_s{
   ngx_pool_large_t  *next;    //指向下一块大块内存
   void    *alloc;             //指向分配的大块内存
};




3、最后
内存池的后续操作, 比如内存池重置
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);
        }
    }

     //然后将last指针重置, 所以要注意的是, 所有小内存是不释放的, 只是简单的指针重置
    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_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个内存块进行检查,否则就直接分配内存,所以大内存块的释放必须及时。


4. Nginx对除了内存之外的资源的清理动作
首先, 看看这个数据的结构:
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;     //对应这个资源的回收方式
    void                 *data;        //这个资源指向的指针
    ngx_pool_cleanup_t   *next;        //下一个要回收的资源
};

对应的处理:
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

     //处理每一个
    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;

            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值