nginx-----内存池

nginx对内存的管理由其内部的内存池实现,nginx在/src/os/unix/ngx_alloc.h/.c中定义了基本的内存分配操作,如malloc等。内存池部分的操作在/src/core/ngx_palloc.(h/c)中实现。

一、内存池相关数据结构

一个基本的nginx内存池结构如下所示
这里写图片描述

由上图可知,nginx通过将多个内存块串联成链表以形成一个内存池的,其中每个内存块都包含了一个固定头部(如每个内存块头部的蓝色部分d),固定头部记录了每个内存块的内存使用相关信息,同时每个内存池还包含了一个头部(如链表首个元素的红色部分),其中保存了内存池的相关信息。

1、内存池中每个内存块的头部

typedef struct {    //内存池中每个节点的固有部分
    u_char               *last; //指向每个节点的实际可用空间的起始地址
    u_char               *end;  //指向每个节点的实际可用空间的结束地址
    ngx_pool_t           *next; //指向内存池中的下一个节点
    ngx_uint_t            failed;   //当前节点分配失败的次数
} ngx_pool_data_t;

sizeof(ngx_pool_data_t)=16字节,即内存池中除了头节点外的其他每个节点的起始16字节都是用来保存该内存块的使用信息的。

2、内存池头部结构

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

sizeof(struct ngx_pool_s )=40,即内存池头节点的前40个字节是用于保存整个内存池和当前节点的相关信息的。

二、内存池的相关操作

内存池主要提供了以下操作:

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

下面将依次进行介绍。

1、创建内存池

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);    //分配以16字节对齐的size大小的内存块,前面包含内存池的固有部分40字节
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  //将last指针指向当前内存块中可用部分的起始地址
    p->d.end = (u_char *) p + size; //将end指针指向当前内存块可用空间的结束地址
    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;
}

eg:当执行完ngx_create_pool(1024,log)后,得到的结果如下:
这里写图片描述

则当前内存池只有一个节点,在该节点的前40字节中记录了当前内存池的相关信息,而从p->d.last到p->d.end之间的984字节可用于内存分配,因此p->max=984

2、内存申请

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

    if (size <= pool->max) {    //1、小块分配,所需的空间可以在当前内存池中一次分配完
        p = pool->current;  //找到内存池当前可用的节点
        do {
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);    //m指向当前内存块可用空间的起始地址
            if ((size_t) (p->d.end - m) >= size) {  //1、1.当前内存块的剩余可用空间大于所需空间
                p->d.last = m + size;   //更新当前内存块可用空间的起始地址
                return m;   //并返回分配结果m
            }
            p = p->d.next;  //1、 2当前内存块中剩余空间不足以满足要求,则依次变了内存池的下一个节点,以找到有足够剩余空间的内存块,并分配内存
        } while (p);
        return ngx_palloc_block(pool, size);    //1、3内存池中所有节点都不满足要求,则重新分配一个内存块
    }
    return ngx_palloc_large(pool, size);   //2、大块分配,分配一个大块内存,并加入到large链表中
}

由上可知,在nginx内存池中申请内存主要分为两种情况:

  1. 待分配的内存size< p->max,即可以在内存池中一次分配成功,为小块分配
  2. 所需内存size>p->max,即超过了内存池每次可分配的最大空间,为大块分配

    对于小块分配,可能有以下几种情况:
    1、在内存池当前可用节点中可以分配成功
    2、在内存池的其他可用节点中可以分配成功
    3、当前内存池中的所有节点都不满足要求,此时则需要重新创建一个内存块,由于size<= p->max,因此此时可以分配成功
    对于以上三种情况,分别如下图所示。
    这里写图片描述

1、 当所需内存size=300字节时,在内存池链表的当前节点中就可以分配成功,直接在当前节点中分配
2、当所需内存size=600字节时,内存池的当前节点不足以分配所需大小的内存,因此遍历链表,找到下一个满足要求的节点,在该节点中分配内存,如图所示,此时在第二个节点中可以满足要求
3、当所需内存size=800字节时,内存池所有节点都不足以分配该大小的内存,因此需要重新创建一个节点,对于一个新节点,其可用空间为1024-16=1008字节>(p->max),因此一定可以满足要求

对于大块分配,如size=1000>(p->max)时,则需要调用 ngx_palloc_large(pool, size); 分配一个大块内存,并将其插入到内存池大块内存链表large链表的头部。

另一种内存分配ngx_pnalloc()与此操作基本相同,只是不再要求返回的内存地址是字节对齐的。

3、重置内存池

重置内存池的操作主要是释放large链表中的所有大块内存,重置所有小块内存的起始地址和失败次数,并将内存池的current指向内存池的头节点

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

    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;       //将内存池的current指针指向内存池的头节点
    pool->chain = NULL;
    pool->large = NULL;
}

4、释放内存

在nginx中,用户只能主动释放大块内存,小块内存需要等待内存池销毁时才能一起释放

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)    //只能释放large链表中的内存块,内存池中的其他节点只能等到内存池销毁时统一释放
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) { //释放large链表中的某个大块内存p
        if (p == l->alloc) {
            ngx_free(l->alloc);
            l->alloc = NULL;
            return NGX_OK;
        }
    }
    return NGX_DECLINED;
}

5、销毁内存池

nginx销毁内存池所执行的主要操作包括:依次执行每个清理函数,释放所有的大块内存,释放所有的小块内存

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) {
            c->handler(c->data);
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值