1. 内存池概述
nginx的内存池本质上是一个内存链表。链表的每一个节点由内存池头部结构体和内存块组成。内存池头部结构体负责链接内存链表的其他节点,同时负责本节点内存块的分配;内存块则是实际提供存储的区域。
在内存分配的时候,实际上又区分为"大块内存"和"小块内存"的分配。所谓小块内存是指:所需要的内存空间大小小于或等于当前内存池链表节点内存块的最大空间。这个时候,通过直接移动内存块上的指针即可分配满足要求的内存空间;大块内存则是指所需要的内存空间超过当前内存池链表节点内存块的最大空间,对于这种情况,nginx首先会按照申请的大小,新开辟一块内存空间(这块新开辟的空间是返回给调用者使用的),然后在当前内存块节点上分配一块小的空间用于存储记录新开辟的空间,同时将所有"大块内存"区域连接起来,形成一个链表,以达到复用。
内存池链表在进程虚拟地址空间中的大概情况如下图所示:
2. 内存池相关结构体
内存池头部结构体
- 大块内存结构体
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s {
ngx_pool_large_t * next; // 指向下一个大块内存
void * alloc; // 指向分配好的大块内存
};
- 内存池头部结构体
typedef struct {
u_char * last; // 当前内存块开始分配的起始地址
u_char * end; // 本节点内存块的结束位置
ngx_pool_t * next; // 指向一个内存池链表节点
ngx_uint_t failed; // 内存池分配失败次数
} ngx_pool_data_t;
struct ngx_pool_s {
ngx_pool_data_t d; // 指向内存池的数据块
size_t max; // 内存池数据块的最大分配空间
ngx_pool_t * current; // 指向当前内存池位置
ngx_chain_t * chain; // 挂接的 ngx_chain_t
ngx_pool_large_t * large; // 大块内存链表头
ngx_pool_cleanup_t * cleanup; // 释放内存的回调函数
ngx_log_t * log; // 日志记录信息
};
3. 内存池相关函数
1). 创建内存池
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 );
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 = 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;
}
2). 从内池中分配内存(接口)
// 对齐分配内存
void * ngx_palloc( ngx_pool_t * pool, size_t size )
{
if( size <= pool->max ) {
return ngx_palloc_small(pool, size, 1);
}
return ngx_palloc_large(pool, size);
}
// 非对齐分配内存
void * ngx_pnalloc( ngx_pool_t * pool, size_t size )
{
if( size <= pool->max ) {
return ngx_palloc_small(pool, size, 0);
}
return ngx_palloc_large(pool, size);
}
// 对齐分配内存, 并初始化为0
void * ngx_pcalloc( ngx_pool_t * pool, size_t size )
{
void * p;
p = ngx_palloc(pool, size);
if( p ) {
ngx_memzero(p, size);
}
return p;
}
// 以指定大小字节对齐, 本质上属于大块内存的分配
void * ngx_pmemalign( ngx_pool_t * pool, size_t size, size_t alignment )
{
void * p;
ngx_pool_large_t * large;
// 创建大小为size的内存,并以alignment 字节对齐
p = ngx_memalign(alignment, size, pool->log);
if( p == NULL ){
return NULL;
}
// 分配大块内存结构体 用于记录前面分配的内存块信息
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if(large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
// 添加到内存池 大块内存链表中
large->next = pool->large;
pool->large = large;
return p;
}
3). 销毁内存池
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) {
c->handler(c->data);
}
}
// 释放大块内存链表
for( l = pool->large; l; l = l->next ) {
if( l->alloc ) {
ngx_free(l->alloc);
}
}
// 释放内存池链表
for( p = pool, n = pool->d->next; p = n, n = n->d.next ) {
ngx_free(p);
if( n == NULL ) {
break;
}
}
}
4). 小块内存分配
static ngx_inline void * ngx_palloc_small(ngx_pool_t * pool, size_t size, ngx_uint_t align)
{
u_char * m;
ngx_pool_t *p;
p = pool->current;
do {
// 找到待分配(未使用)的起始地址
m = p->d.last;
// 根据需要 进行地址对齐
if(align) {
m = ngx_align_ptr( m, 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 );
}
5). 大块内存分配
static void * ngx_palloc_large( ngx_pool_t * pool, size_t size )
{
void *p;
ngx_uint_t n;
ngx_pool_large_t * large;
// 申请指定大小的空间
p = ngx_alloc( size, pool->log );
if( p == NULL ) {
return NULL;
}
// 找未使用的大块内存结构体
n = 0;
for( large = pool->large; large; large = large->next ) {
if( large->alloc == NULL ){
large->alloc = p;
return ;
}
if(n++ > 3){
break;
}
}
// 未找到可用的, 重新申请 大块内存结构体的 空间
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if( large == NULL ) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
4. nginx对内存池的使用
在有新连接建立时,nginx会为每个连接创建一个内存池,其大小可以通过配置项(connection_pool_size)进行调整,然后在内存池中进行内存的分配;在连接断开时,销毁内存池。
针对http请求,每个请求都会创建一个内存池,然后在该内存池中进行内存的分配。同样,该内存池的大小也是可以通过配置项(request_pool_size)进行配置的;在请求结束时,销毁内存池。