初识nginx——内存池篇

为了自身使用的方便,Nginx封装了很多有用的数据结构,比如ngx_str_t ,ngx_array_t, ngx_pool_t 等等,对于内存池,nginx设计的十分精炼,值得我们学习,本文介绍内存池基本知识,nginx内存池的结构和关键代码,并用一个实际的代码例子作了进一步的讲解

 

一、内存池概述

内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存。

内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减少程序员在编写代码中对内存的关注等

目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等

二、nginx内存池综述

nginx为每一个层级都会创建一个内存池,进行内存的管理,比如一个模板,tcp连接,http请求等,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操作系统。

在分配的内存上,nginx有小块内存和大块内存的概念,小块内存 nginx在分配的时候会尝试在当前的内存池节点中分配,而大块内存会调用系统函数malloc向操作系统申请

在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_pool 和 ngx_reset_pool的时候一并释放

区分小块内存和大块内存的原因有2个,

1、针对大块内存  如果它的生命周期远远短于所属的内存池,那么提供一个单独的释放函数是十分有意义的,但不区分大块内存和小块内存,针对大的内存块 便会无法提前释放了

2、大块内存与小块内存的界限是一页内存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值通过调用getpagesize()获得),大于一页的内存在物理上不一定是连续的,所以如果分配的内存大于一页的话,从内存池中使用,和向操作系统重新申请效率差不多是等价的

nginx内存池提供的函数主要有以下几个

三、nginx内存池详解

nginx使用了ngx_pool_s用于表示整个内存池对象,ngx_pool_data_t表示单个内存池节点的分配信息,ngx_pool_large_s表示大块内存

它们的结构和含义如下

struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};

next:   指向下一个大块内存

alloc:指向分配的大块内存

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

d:内存池的节点的数据分配情况

max:      单个内存池节点容量的最大值

current: 指向当前的内存池节点

chain: 指向一个ngx_chain_t结构

large:  指向大块内存链表

cleanup:释放内存池的callback

log:     用于输出日志

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

last:    内存池节点已分配的末位地址,下一次分配会尝试从此开始

end: 内存池节点的结束位置

next:next指向下一个内存池节点

failed: 当前内存池节点分配失败次数

nginx 内存池示意图1

在分配内存的时候,nginx会判断当前要分配的内存是小块内存还是大块内存,大块内存调用ngx_palloc_large进行分配,小块内存nginx先会尝试从内存池的当前节点(p->current)中分配,如果内存池当前节点的剩余空间不足,nginx会调用ngx_palloc_block新创建一个内存池节点并从中分配,

如果内存池当前节点的分配失败次数已经大于等于6次(p->d.failed++ > 4),则将当前内存池节点前移一个

(这里有个需要注意的地方,当当前内存节点的剩余空间不够分配时,nginx会重新创建一个ngx_pool_t对象,并且将pool.d->next指向新的ngx_pool_t,新分配的ngx_pool_t对象只用到了ngx_pool_data_t区域,并没有头部信息,头部信息部分已经被当做内存分配区域了)

nginx 内存池示意图2(新建了一个内存池节点和分配了2个大块内存,其中一个已经释放)

关键代码

创建内存池代码

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);//间接调用了posix_memalign分配内存
    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;//由于当前内存池只有一个节点所以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;
}

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 ((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);//分配大块内存
}

消耗内存池

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);
        }
    }
    #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);//间接调用free释放内存
        if (n == NULL) {
            break;
        }
    }
}

四、示例代码

这里是直接替换了原有nginx代码的main函数 (src/core/nginx.c)

void print_pool(ngx_pool_t *pool)
{
    if (pool->large != NULL)
    {
        printf("has large memory\n");
        for(ngx_pool_large_t* i = pool->large; i!=NULL; i = i->next)
        {
            printf("\t\tlarge next=0x%x\n", i->next);
            printf("\t\tlarge alloc=0x%x\n", i->alloc);
        }
        }
        int i=1;
        while(pool)
        {
            printf("pool=0x%x,index:%d\n", pool, i++);
            printf("\t\tlast=0x%x\n", (pool->d).last);
            printf("\t\tend=0x%x\n",(pool->d).end);
            printf("\t\tnext=0x%x\n",(pool->d).next);
            printf("\t\tfailed=%d\n",pool->d.failed);
            printf("\t\tmax=%d\n",pool->max);
            printf("\t\tcurrent=0x%x\n",pool->current);
            printf("\t\tchain=0x%x\n",pool->chain);
            printf("\t\tlarge=0x%x\n",pool->large);
            printf("\t\tcleanup=0x%x\n",pool->cleanup);
            printf("\t\tlog=0x%x\n",pool->log);
            printf("\t\tavailable pool memory=%d\n", pool->d.end-pool->d.last);
            printf("\n");
            pool=pool->d.next;
        }
    }
void print_array(int *a,int size)
{
    for(int i=0; i<size; i++)
    {
        printf("%d,",a[i]);
    }
    printf("\n");
}
int main()
{
    ngx_pool_t *pool;
    int array_size = 128;
    int array_size_large = 1024;
    int page_size = getpagesize();//获得一页的大小
    printf("page_size:%d\n", page_size);
    printf("----------------------------\n");
    printf("create a new pool");
    pool = ngx_create_pool(1024, NULL);//创建一个大小为1024的内存池
    print_pool(pool);
    printf("----------------------------\n");
    printf("alloc block 1 from the pool:\n");
    int *a1 = ngx_palloc(pool, sizeof(int) * array_size);//分配第一块内存 用于创建数组
    for (int i=0; i< array_size; i++)
    {
        a1[i] = i+1;
    }
    print_pool(pool);
    printf("----------------------------\n");
    printf("alloc block 2 from the pool:\n");
    int *a2 = ngx_palloc(pool, sizeof(int) * array_size);//分配第二块内存 用于创建数组,这个时候会创建第二个内存池节点
    for (int i=0; i< array_size; i++)
    {
        a2[i] = 12345678;
    }
    print_pool(pool);
    printf("----------------------------\n");
    printf("alloc large memory:\n");
    printf("\t\tlarge next before=0x%x\n", pool->current->d.last);
    int * a3 = ngx_palloc(pool, sizeof(int) * array_size_large);//由于大小超过了max的值 ngx_palloc中会调用ngx_palloc_large分配大块内存
    printf("\t\tlarge next after=0x%x\n", pool->large);
    for (int i=0; i< array_size_large; i++)
    {
        a3[i] = i+1;
    }
    print_pool(pool);
    print_array(a1,array_size);
    print_array(a2,array_size);
    print_array(a3,array_size_large);
    ngx_destroy_pool(pool);
    return 0;
}
 

运行结果:

通过红框可以看到ngx_pool_t中只有第一个内存池节点的头部信息是有意义的,后续调用ngx_palloc_block创建的节点的头部信息都已经被数据覆盖。

五、总结

     nginx的代码设计的十分灵活,既方便我们开发,也方便我们复用其中的结构,其中内存池的使用 对我们学习nginx,了解nginx如何管理内存有着十分重要的意义。

 

初识nginx——内存池篇的更多相关文章

  1. nginx——内存池篇

    nginx--内存池篇 一.内存池概述 内存池是在真正使用内存之前,预先申请分配一定数量的.大小相等(一般情况下)的内存块留作备用.当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续 ...

  2. NGINX 内存池有感

    写在前面 写NGINX系列的随笔,一来总结学到的东西,二来记录下疑惑的地方,在接下来的学习过程中去解决疑惑. 也希望同样对NGINX感兴趣的朋友能够解答我的疑惑,或者共同探讨研究. 整个NGINX系列 ...

  3. nginx 内存池分析

    最近nginx的源码刚好研究到内存池,这儿就看下nginx内存池的相关的东西. 一,为什么要使用内存池 大多数的解释不外乎提升程序的处理性能及减小内存中的碎片,对于性能优化这点主要体现在: (1)系统 ...

  4. nginx 内存池

    参考 https://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html?tdsourcetag=s_pctim_aiomsg 源码版本 n ...

  5. 初识nginx——配置解析篇

    一.nginx的介绍 nginx是由俄罗斯人开发的一款高性能的http和反向代理服务器,也可以用来作为邮件代理.相比较于其他的服务器,具有占用内存少,稳定性高等优势 二.nginx的配置 nginx的 ...

  6. nginx内存池

    一.设计原则 (1)降低内存碎片 (2)降低向操作系统申请内存的次数 (3)减少各个模块的开发效率 二.源代码结构 struct ngx_pool_s {     ngx_pool_data_t    ...

  7. Nginx系列三 内存池的设计

    Nginx的高性能的是用非常多细节来保证,epoll下的多路io异步通知.阶段细分化的异步事件驱动,那么在内存管理这一块也是用了非常大心血.上一篇我们讲到了slab分配器,我们能够能够看到那是对共享内 ...

  8. nginx源码学习----内存池

    最近在进行监控平台的设计,之前一直觉得C/C++中最棘手的部分是内存的管理上,远不止new/delete.malloc/free这么简单.随着代码量的递增,程序结构复杂度的提高.各种内存方面的问题悄然 ...

  9. nginx源码分析—内存池结构ngx_pool_t及内存管理

    Content 0. 序 1. 内存池结构 1.1 ngx_pool_t结构 1.2 其他相关结构 1.3 ngx_pool_t的逻辑结构 2. 内存池操作 2.1 创建内存池 2.2 销毁内存池 2 ...

随机推荐

  1. 实时监听input输入的变化(兼容主流浏览器)【转】

    遇到如此需求,首先想到的是change事件,但用过change的都知道只有在input失去焦点时才会触发,并不能满足实时监测的需求,比如监测用户输入字符数. 在经过查阅一番资料后,欣慰的发现firef ...

  2. 求助pycharm里import pandas遇到No module named pandas怎么办

    目测是安转pandas库之后pycharm对库的检索没有更新.(能理解意思就行,我也是小白,不会专业术语,刚巧我也遇到了这样的问题所以来回答)我当时解决这个问题也算是瞎猫遇见死耗子. &amp ...

  3. Echo团队Alpha冲刺随笔集合

    班级:软件工程1916|W 作业:项目Alpha冲刺(团队) 团队名称:Echo 作业目标:完成项目Alpha冲刺 代码规范.冲刺任务与计划 Day 0: 代码规范.任务及计划 冲刺随笔 Day 1: ...

  4. jQuery 动画效果

    推荐网址:http://www.php100.com/manual/jquery/,用法教学,包括实例. 分类:显示隐藏.淡入淡出.滑动.自定义. <%@ Page Language=" ...

  5. ubuntu打开windows下txt文档乱码问题的解决

    昨天晚上安装了Ubuntu11.10,打开TXT文件的时候发现中文乱码问题,在网上查了一下,一些网友提供了下面的方法: “按Alt+F2,打开“运行应用程序”对话框,输入“gconf-editor”, ...

  6. co模块源码学习笔记

    // Sorrow.X --- 添加注释,注释纯属个人理解 /** * slice变量 引用 数组的 slice方法 */ var slice = Array.prototype.slice; /** ...

  7. tab键设置为空4格和自动缩进

    .打开vim配置文件~@vim etc/vim/vimrc 二 .设置tab和自动缩进即:添加如下内容 "set tab by charly!" softtabstop= (注意等 ...

  8. token令牌

    本文摘自 WebApi安全性 使用TOKEN+签名验证 首先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或 ...

  9. 4.3《想成为黑客,不知道这些命令行可不行》(Learn Enough Command Line to Be Dangerous)—链接到目录

    在4.2章中我们已经会用cd进入到指定的目录中.这是导航最常见的用途之一,但是它还有几个值得关注的用途.第一个是使用cd ..(读作'see-dee 点点')返回当前目录级别的上一级: $ pwd / ...

  10. Luogu2045 方格取数加强版(K取方格数) 费用流

    题目传送门 题意:给出一个$N \times N$的方格,每个格子中有一个数字.你可以取$K$次数,每次取数从左上角的方格开始,每一次只能向右或向下走一格,走到右下角结束,沿路的方格中的数字将会被取出 ...