nginx内存池
Posted ApeLife
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nginx内存池相关的知识,希望对你有一定的参考价值。
一、nginx内存池的使用
nginx采用内存池对内存进行管理。即先开辟一个内存池空间,之后就从内存池中获取内存了,避免频繁的调用malloc/free操作。如果内存池空间不够,才会调用malloc
分配一个新的内存块,并加入到内存池中。
1、对于每一个客户端发起的http请求,nginx服务器都需要开辟空间来接收客户端的请求行,请求包头、以及请求的包体。这些都是从内存池中获取空间,并把数据存储到这些空间。
2、nginx服务器对客户端请求的响应,分为响应包头,响应包体。这些数据也需要从内存池中获取空间并进行存储
3、nginx作为反向代理服务器时,也要从内存池中获取空间,并存储上游服务器的响应包体。进而转发给下游客户端。
总之,nginx对内存的操作,都是从内存池中获取空间。如果内存池空间不够时,才会调用malloc重新开辟一个内存块,并加入到内存池中。当然,这样处理的缺点也是很明显的,申请一块大的内存必然会导致内存空间的浪费,但是比起频繁地malloc和free,这样做的代价是非常小的,这是典型的以空间换时间。
二、内存池源码分析
1、创建内存池
对于每一个客户端发起的请求,nginx都会为其创建一个请求结构。而每一个请求结构,都将创建一个内存池。用于接收客户端的http请求报文,以及存储发给客户端的http响应报文。创建内存池由ngx_create_pool函数完成,参数size为要创建的内存块大小,返回值为ngx_pool_t内存池句柄
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p;
//使用malloc开辟一个size大小的空间
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL)
{
return NULL;
}
//last指向数据空间
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
//end指向整个内存块的结束位置
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;
//size表示数据空间的大小(真正存放数据的空间,不包括ngx_pool_t表头结构所占用的大小)
size = size - sizeof(ngx_pool_t);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
//current当前块执行刚创建的这个内存块
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}
例如:要创建一个1K大小的内存池,调用ngx_create_pool(1024, log)后的内存布局如下
图:创建内存池
last字段指向是数据空间的首地址,这片空间用于存放实际的数据。当从这个内存块获取内存后,last指针也会跟着移动,指向最新的可用空间的首地址。
end指向的是这个内存块的结束地址。
需要说明的是max字段,max表示的是小内存块最大的数据空间。在这个例子中sizeof(ngx_pool_s)占用40字节,因此申请1k的内存池,实际可以存储数据的空间只有984字节。
max字段有什么作用呢? 如果还需要在开辟一个小内存块,则这个小内存块的最大空间为max大小(由ngx_pool_data_t与实际存放的数据组成); 另外这个字段也是一个临界值,如果需要从内存池中获取的空间小于max, 则开辟一个小内存块就可以了,否则需要开辟一个大内存块。
2、从内存池中获取空间
在nginx处理客户端请求以及发送响应时,需要从内存池中获取空间,用于存储报文。以下这4个函数都可以用来从内存池中获取空间,区别是内存字节对其方式,以及获取后的内存是否进行清0处理。其它没什么差别。
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
下面以ngx_pnalloc函数为例,分析从内存池中获取一片内存的流程。
//从内存池获取获取大小为size的内存,返回值为获取到的内存地址
void * ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
//待分配的数据空间小于等于内存块实际最大数据空间,则从小内存块获取内存
if (size <= pool->max)
{
p = pool->current;
do
{
m = p->d.last;
//该内存块剩余空间满足待获取的数据大小,则直接返回内存地址
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);
}
再次强调下,max成员表示的是每一个小内存块数据空间的最大值。
1、函数首先会判断要获取的内存size是否小于等于max,如果是,则从current指向的小内存块开始遍历,直到找到一个内存块的剩余空间能够存放size大小的空间,则返回这个内存块。如果所有的小内存块都没有空间容纳size大小的空间,则会重新开辟一个小内存块,并加入到ngx_pool_data_t小内存块链表中
2、如果要获取的内存size比max还大,则说明每一个小内存块都没有空间存放size大小的内容。则需要使用malloc开辟一个大内存块,并加入到large大内存块链表中。
调用ngx_pnalloc(pool, 200)获取200字节的内存布局如下图:
图:获取空间
在这里只要last指针发生了改变,指向了最新可用空间的首地址。max字段从ngx_create_pool创建内存池开始到销毁内存池,大小都不会发生变化,是一个固定值。
//功能: 开辟一个新的小内存块,小内存块大小为ngx_pool_t中的max成员。
// 注意:新开辟的小内存块由ngx_pool_t中的ngx_pool_data_t字段加上数据空间组成,
// 不包括ngx_pool_t的其它字段空间
//参数: size表示需要从新开辟的小内存块获取多大的空间
//返回值:获取到的size大小空间的首地址
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new, *current;
//psize为实际的数据空间,也就是ngx_pool_t中的max成员
psize = (size_t) (pool->d.end - (u_char *) pool);
//使用malloc生成一个psize大小的内存块
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL)
{
return NULL;
}
new = (ngx_pool_t *) m;
//end指向内存块的末尾
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
//m执向内存块的数据空间
m += sizeof(ngx_pool_data_t);
m = ngx_align_ptr(m, NGX_ALIGNMENT);
//last指向可以使用的空间
new->d.last = m + size;
current = pool->current;
for (p = current; p->d.next; p = p->d.next)
{
//如果失败超过次数,则移动current指针。之后获取空间会从新的current指向的内存块获取
if (p->d.failed++ > 4)
{
current = p->d.next;
}
}
//将新开辟的小内存块插入到链表尾部
p->d.next = new;
pool->current = current ? current : new;
return m;
}
如果在所有小内存块中都没有满足条件的空间,则会调用ngx_palloc_block重新分配一个小内存块。需要注意的是failed字段: 默认情况current指针都是指向第一个内存块,在需要从内存池中获取空间时,都是从current指向的内存块开始遍历,判断当前块剩余空间是否满足条件。 而在调用ngx_palloc_block分配的小内存块时过多时,必然会有一个内存块的failed字段值大于4,则会改变current指针。之后需要在从内存池中获取内存,就会从当前的current指针指向的内存块开始遍历了,直到找到满足条件的内存块为止。也就是failed指向的内存块不会在被利用了,但很好奇这个内存块不能在被利用了,为什么不马上free呢?估计也是为了减少频繁进行free系统调用吧,等到内存时销毁时在释放空间。
在上图的例子中,内存块中只剩下784字节的空间了。如果此时需要获取800字节的空间,则由于这个内存块剩余的空间不能满足800字节,因此需要重新开辟一个小内存块,新开辟内存块大小为ngx_pool_s中的max成员,也就是984字节。开辟一个新内存块后,内存布局如下。