初识nginx——内存池篇
为了自身使用的方便,Nginx封装了不少有用的数据结构,好比ngx_str_t ,ngx_array_t, ngx_pool_t 等等,对于内存池,nginx设计的十分精炼,值得咱们学习,本文介绍内存池基本知识,nginx内存池的结构和关键代码,并用一个实际的代码例子做了进一步的讲解nginx
1、内存池概述
内存池是在真正使用内存以前,预先申请分配必定数量的、大小相等(通常状况下)的内存块留做备用。当有新的内存需求时,就从内存池中分出一部份内存块,若内存块不够用时,再继续申请新的内存。程序员
内存池的好处有减小向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减小程序员在编写代码中对内存的关注等数组
目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等数据结构
2、nginx内存池综述
nginx为每个层级都会建立一个内存池,进行内存的管理,好比一个模板,tcp链接,http请求等,在对应的生命周期结束的时候会摧毁整个内存池,把分配的内存一次性归还给操做系统。tcp
在分配的内存上,nginx有小块内存和大块内存的概念,小块内存 nginx在分配的时候会尝试在当前的内存池节点中分配,而大块内存会调用系统函数malloc向操做系统申请函数
在释放内存的时候,nginx没有专门提供针对释放小块内存的函数,小块内存会在ngx_destory_pool 和 ngx_reset_pool的时候一并释放性能
区分小块内存和大块内存的缘由有2个,学习
一、针对大块内存 若是它的生命周期远远短于所属的内存池,那么提供一个单独的释放函数是十分有意义的,但不区分大块内存和小块内存,针对大的内存块 便会没法提早释放了ui
二、大块内存与小块内存的界限是一页内存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值经过调用getpagesize()得到),大于一页的内存在物理上不必定是连续的,因此若是分配的内存大于一页的话,从内存池中使用,和向操做系统从新申请效率差很少是等价的this
nginx内存池提供的函数主要有如下几个
3、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
;
}
}
}
|
4、示例代码
这里是直接替换了原有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建立的节点的头部信息都已经被数据覆盖。
5、总结
nginx的代码设计的十分灵活,既方便咱们开发,也方便咱们复用其中的结构,其中内存池的使用 对咱们学习nginx,了解nginx如何管理内存有着十分重要的意义。