初识nginx——内存池篇

初识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如何管理内存有着十分重要的意义。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏佳爷的后花媛

路由&模块化设计&命名空间

ThinkPHP采用模块化的架构思想,可以支持多模块应用的创建,让应用的扩展更加方便. 先简单说下路由规则:

22130
来自专栏跟着阿笨一起玩NET

sql 多条件查询的一种简单的方法

53320
来自专栏向治洪

命令模式

命令模式定义 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。 作用 命令模式主要应用于将行...

20290
来自专栏我的博客

PHP5.3~PHP5.5新特性汇总

一.PHP 5.3中的新特性 1. 支持命名空间 (Namespace) 2. 支持延迟静态绑定(Late Static Binding) 3. 支持got...

39880
来自专栏码神联盟

珍藏 | Java 岗位 100道 面试题及答案详解

1.1K50
来自专栏java工会

反射在微信公众平台开发中的应用

14940
来自专栏JavaEdge

MyBatis Generator 用法详解 XML配置详解 3. <generatorConfiguration>的子元素MyBatis Generator中文文档

56270
来自专栏杂烩

spring的事物配置 原

    xml配置这里不细说了,这里主要讲的是注解方式。     需要注意的是:        @Transactional只能被应用到public方法上, ...

4710
来自专栏用户2442861的专栏

Nginx源码剖析之内存池,与内存管理

    Nginx(发音同 engine x)是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like...

19840
来自专栏技术博文

phpcms v9 常用函数

常用函数 , 打开include/global.func.php,下面存放一些公共函数 view plaincopy to clipboardprint? fu...

39370

扫码关注云+社区

领取腾讯云代金券