初识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 memoryn"); for(ngx_pool_large_t* i = pool->large; i!=NULL; i = i->next) { printf("ttlarge next=0x%xn", i->next); printf("ttlarge alloc=0x%xn", i->alloc); } } int i=1; while(pool) { printf("pool=0x%x,index:%dn", pool, i++); printf("ttlast=0x%xn", (pool->d).last); printf("ttend=0x%xn",(pool->d).end); printf("ttnext=0x%xn",(pool->d).next); printf("ttfailed=%dn",pool->d.failed); printf("ttmax=%dn",pool->max); printf("ttcurrent=0x%xn",pool->current); printf("ttchain=0x%xn",pool->chain); printf("ttlarge=0x%xn",pool->large); printf("ttcleanup=0x%xn",pool->cleanup); printf("ttlog=0x%xn",pool->log); printf("ttavailable pool memory=%dn", 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:%dn", 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("ttlarge next before=0x%xn", pool->current->d.last); int * a3 = ngx_palloc(pool, sizeof(int) * array_size_large);//由于大小超过了max的值 ngx_palloc中会调用ngx_palloc_large分配大块内存 printf("ttlarge next after=0x%xn", 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如何管理内存有着十分重要的意义。
- JDK容器学习之Map: HashMap,TreeMap,LinkedHashMap对比小结
- 14.4 exportfs命令
- 不用那么多,每天一点点,学习React,贵在持之以恒
- Linux基础(day56)
- android classloader双亲委托模式
- 14.3 NFS配置选项
- 14.2 NFS服务端安装配置
- React Native组件之Button
- JDK容器学习之TreeMap (二) : 使用说明
- 从React和angular看技术路线的分歧
- JDK容器学习之LinkedHashMap (一):底层存储结构分析
- Linux基础(day60)
- 16.5/16.6/16.7 配置Tomcat虚拟主机
- JDK容器学习之LinkedHashMap(二):迭代遍历的实现方式
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 《剑指offer》第十天:青蛙跳台阶
- Kettle构建Hadoop ETL实践(一):ETL与Kettle
- 初识ABP vNext(2):ABP启动模板
- 维度爆炸?Python实现数据压缩竟如此简单!
- 如何扩展单个Prometheus实现近万Kubernetes集群监控?
- 一文带你彻底厘清 Isito 中的证书工作机制
- 如何将第三方服务注册集成到 Istio ?
- 【Pod Terminating原因追踪系列】之 containerd 中被漏掉的 runc 错误信息
- 【Pod Terminating原因追踪系列之二】exec连接未关闭导致的事件阻塞
- CD+服务网格灰度发布实践,一文带你体验如何编排更灵活
- 花十分钟的时间武装你的代码库
- 对HTML-input的一些思考和理解
- 【投稿】刀哥:Rust学习笔记 1
- 【Rust日报】2020-08-13 关于群集(Bevy)引擎ECS框架中system的语法糖是怎么实现的
- 最新情报:所有的递归都可以改写成非递归?