Nginx源码分析-内存池
2014-05-15 22:37
183 查看
总的来说内存池基本都有一个宗旨:申请大块内存,避免“细水长流”。
Nginx在特定的生命周期统一建立内存池,需要内存时统一在内存池中申请内存,并且在适当的时候释放内存池的内存。
内存池主要由两个结构来维护,一个是头部一个是数据部。此处数据部就是用来分配小块内存的地方。
ngx_pool_data_t用来维护内存池数据块
下面来看一下这个结构体
下面来分析这个函数
![](http://hi.csdn.net/attachment/201112/4/0_1323022665nuH9.gif)
图中红色的字段表示上述的第一个结构,维护数据部分:end表示内存池的结束位置,所有内存分配不可以越过end。蓝色的max字段表示数据段最大大小是多少,如果申请的内存大于max,就认为申请的是大内存,就会在large字段下分配内存,如果小于max字段就在数据段进行内存分配,并且移动last指针
内存池物理结构图如下
![](http://hi.csdn.net/attachment/201107/5/0_1309882022ZpNM.gif)
紧接着,咱们就来分析下上面代码中所提到的:ngx_memalign()函数
void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
void *p;
int err;
err = posix_memalign(&p, alignment, size);
//该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
if (err) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"posix_memalign(%uz, %uz) failed", alignment, size);
p = NULL;
}
ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
"posix_memalign: %p:%uz @%uz", p, size, alignment);
return p;
}
//从这个函数的实现体,我们可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
//函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
view
plain
#define NGX_POOL_ALIGNMENT 16
因此,nginx的内存池分配,是以16字节为边界对齐的。
分配内存调用函数
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_palloc函数
上面函数中调用了ngx_palloc_block,下面来分析一下这个函数
![](http://hi.csdn.net/attachment/201112/4/0_13230227867YcO.gif)
内存池的物理结构如下图
![](http://hi.csdn.net/attachment/201107/5/0_130988203286UE.gif)
内存池第一块内存前40字节为ngx_pool_t结构,后续加入的内存块前16个字节为ngx_pool_data_t结构,这两个结构之后便是真正可以分配内存区域。
大块内存的分配不会在内存池中进行申请内存,而是直接向操作系统申请内存(与直接使用malloc申请一样),然后将申请到的内存挂到内存池头部large字段下。内存池的作用在于解决小块内存池的频繁申请问题的,对于这种大块内存是可以忍受直接申请的。
![](http://hi.csdn.net/attachment/201112/4/0_1323022828Quou.gif)
销毁内存池
接下来,咱们来看内存池的销毁函数,pool指向需要销毁的内存池
view
plain
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);
}
}
//前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,
//清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。
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);
}
}
//这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,
//即大内存块就是通过malloc和free操作进行管理的。
#if (NGX_DEBUG)
/**
* we could allocate the pool->log from this pool
* so we can not use this log while the 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;
}
}
//只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
#endif
for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
//该片段彻底销毁内存池本身。
该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构),亦将遍历该cleanup链表结构依次调用clenup的handler清理。同时,还将遍历large链表,释放大块内存。
cleanup资源
![](http://hi.csdn.net/attachment/201112/4/0_1323022871C1w5.gif)
可以看到所有挂载在内存池上的资源将形成一个循环链表,一路走来,发现链表这种看似简单的数据结构却被频繁使用。
由图可知,每个需要清理的资源都对应有一个头部结构,这个结构中有一个关键的字段handler,handler是一个函数指针,在挂载一个资源到内存池上的时候,同时也会注册一个清理资源的函数到这个handler上。即是说,内存池在清理cleanup的时候,就是调用这个handler来清理对应的资源。
比如:我们可以将一个开打的文件描述符作为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来处理文件描述符资源了。
内存的释放
nginx只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成;
一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。
也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,为此连接创建一个内存池;连接上到来一个request后,又为request创建起一个内存池。
这样,在request被处理完后,就会释放request的整个内存池,但是不释放connection的内存池,因为还可能又其他请求;连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。
小结:通过内存的分配和释放可以看出,nginx只是将小块内存的申请聚集到一起申请,然后一起释放。避免了频繁申请小内存,降低内存碎片的产生等问题。
Nginx在特定的生命周期统一建立内存池,需要内存时统一在内存池中申请内存,并且在适当的时候释放内存池的内存。
内存池主要由两个结构来维护,一个是头部一个是数据部。此处数据部就是用来分配小块内存的地方。
ngx_pool_data_t用来维护内存池数据块
下面来看一下这个结构体
<span style="font-size:12px;">typedef struct { u_char *last; //当前内存分配结束位置,即下一段可分配内存的起始位置 u_char *end; //内存池结束位置 ngx_pool_t *next; //链接到下一个内存池 ngx_uint_t failed;//统计该内存池不能满足分配请求的次数 } ngx_pool_data_t;</span>ngx_pool_t维护整个内存池的头部信息,结构体如下
struct ngx_pool_s { ngx_pool_data_t d; //数据块 size_t max; //数据块大小,即小块内存的最大值 ngx_pool_t *current; //保存当前内存值 ngx_chain_t *chain; //可以挂一个chain结构 ngx_pool_large_t *large; //分配大块内存用,即超过max的内存请求 ngx_pool_cleanup_t *cleanup; //挂载一些内存池释放的时候,同时释放的资源 ngx_log_t *log; };上面两个结构就可以创建内存池了,用来创建内存池的接口是:ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)(位于src/core/ngx_palloc.c中);
下面来分析这个函数
00015: ngx_pool_t * 00016: ngx_create_pool(size_t size, ngx_log_t *log) 00017: { 00018: ngx_pool_t *p; 00019: 00020: p = <span style="color:#ff0000;">ngx_memalign(NGX_POOL_ALIGNMENT, size, log)</span>;//此函数下面分析 00021: if (p == NULL) { 00022: return NULL; 00023: } 00024: 00025: p->d.last = (u_char *) p + sizeof(ngx_pool_t); //last指向ngx_pool_t结构体之后数据取起始位置 00026: p->d.end = (u_char *) p + size; //end指向分配的整个size大小的内存的末尾 00027: p->d.next = NULL; 00028: p->d.failed = 0; 00029: 00030: size = size - sizeof(ngx_pool_t); 00031: p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //最大不超过4095B 00032: 00033: p->current = p; 00034: p->chain = NULL; 00035: p->large = NULL; 00036: p->cleanup = NULL; 00037: p->log = log; 00038: 00039: return p; 00040: }调用这个函数就可以创建一个大小为size的内存池了,内存池的结构图如下
![](http://hi.csdn.net/attachment/201112/4/0_1323022665nuH9.gif)
图中红色的字段表示上述的第一个结构,维护数据部分:end表示内存池的结束位置,所有内存分配不可以越过end。蓝色的max字段表示数据段最大大小是多少,如果申请的内存大于max,就认为申请的是大内存,就会在large字段下分配内存,如果小于max字段就在数据段进行内存分配,并且移动last指针
内存池物理结构图如下
![](http://hi.csdn.net/attachment/201107/5/0_1309882022ZpNM.gif)
紧接着,咱们就来分析下上面代码中所提到的:ngx_memalign()函数
void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
void *p;
int err;
err = posix_memalign(&p, alignment, size);
//该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
if (err) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"posix_memalign(%uz, %uz) failed", alignment, size);
p = NULL;
}
ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
"posix_memalign: %p:%uz @%uz", p, size, alignment);
return p;
}
//从这个函数的实现体,我们可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
//函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
view
plain
#define NGX_POOL_ALIGNMENT 16
因此,nginx的内存池分配,是以16字节为边界对齐的。
分配内存调用函数
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_palloc函数
<span style="font-size:12px;"><span style="color:#333333;">00115: void * 00116: ngx_palloc(ngx_pool_t *pool, size_t size) 00117: { 00118: u_char *m; 00119: ngx_pool_t *p; 00120: 00121: if (size <= pool->max) {//判断待分配内存与max值 00122: 00123: p = pool->current; //小于max值,则从current节点开始遍历pool链表 00124: 00125: do { 00126: m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//</span><span style="color:#ff0000;">对齐内存指针,加快存取速度</span><span style="color:#333333;"> 00127: 00128: if ((size_t) (p->d.end - m) >= size) { 00129: p->d.last = m + size; //在该节点指向的内存块中分配size大小的内存 00130: 00131: return m; 00132: } 00133: 00134: p = p->d.next; 00135: 00136: } while (p); 00137: 00138: return </span><span style="color:#ff0000;">ngx_palloc_block(pool, size)</span><span style="color:#333333;">; //链表里没有能分配size大小内存的节点,则生成一个新的节点并在其中分配内存 00139: } 00140: 00141: return ngx_palloc_large(pool, size); //大于max值,则在large链表里分配内存 00142: }</span></span>
上面函数中调用了ngx_palloc_block,下面来分析一下这个函数
<span style="font-size:12px;">00175: static void * 00176: ngx_palloc_block(ngx_pool_t *pool, size_t size) 00177: { 00178: u_char *m; 00179: size_t psize; 00180: ngx_pool_t *p, *new, *current; 00181: 00182: psize = (size_t) (pool->d.end - (u_char *) pool); //计算pool的大小 00183: 00184: m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配一块与pool大小相同的内存 00185: if (m == NULL) { 00186: return NULL; 00187: } 00188: 00189: new = (ngx_pool_t *) m; 00190: 00191: new->d.end = m + psize; //设置end指针 00192: new->d.next = NULL; 00193: new->d.failed = 0; 00194: 00195: m += sizeof(ngx_pool_data_t); //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置 00196: m = ngx_align_ptr(m, NGX_ALIGNMENT); //按4字节对齐 00197: new->d.last = m + size; //在数据区分配size大小的内存并设置last指针 00198: 00199: current = pool->current; 00200: 00201: for (p = current; p->d.next; p = p->d.next) { 00202: if (p->d.failed++ > 4) { //failed的值只在此处被修改 00203: current = p->d.next; //失败4次以上移动current指针 00204: } 00205: } 00206: 00207: p->d.next = new; //将这次分配的内存块new加入该内存池 00208: 00209: pool->current = current ? current : new; 00210: 00211: return m; 00212: }</span>注意:该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置。而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。
![](http://hi.csdn.net/attachment/201112/4/0_13230227867YcO.gif)
内存池的物理结构如下图
![](http://hi.csdn.net/attachment/201107/5/0_130988203286UE.gif)
内存池第一块内存前40字节为ngx_pool_t结构,后续加入的内存块前16个字节为ngx_pool_data_t结构,这两个结构之后便是真正可以分配内存区域。
大块内存的分配不会在内存池中进行申请内存,而是直接向操作系统申请内存(与直接使用malloc申请一样),然后将申请到的内存挂到内存池头部large字段下。内存池的作用在于解决小块内存池的频繁申请问题的,对于这种大块内存是可以忍受直接申请的。
![](http://hi.csdn.net/attachment/201112/4/0_1323022828Quou.gif)
销毁内存池
接下来,咱们来看内存池的销毁函数,pool指向需要销毁的内存池
view
plain
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);
}
}
//前面讲到,cleanup指向析构函数,用于执行相关的内存池销毁之前的清理工作,如文件的关闭等,
//清理函数是一个handler的函数指针挂载。因此,在这部分,对内存池中的析构函数遍历调用。
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);
}
}
//这一部分用于清理大块内存,ngx_free实际上就是标准的free函数,
//即大内存块就是通过malloc和free操作进行管理的。
#if (NGX_DEBUG)
/**
* we could allocate the pool->log from this pool
* so we can not use this log while the 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;
}
}
//只有debug模式才会执行这个片段的代码,主要是log记录,用以跟踪函数销毁时日志记录。
#endif
for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
//该片段彻底销毁内存池本身。
该函数将遍历内存池链表,所有释放内存,如果注册了clenup(也是一个链表结构),亦将遍历该cleanup链表结构依次调用clenup的handler清理。同时,还将遍历large链表,释放大块内存。
cleanup资源
![](http://hi.csdn.net/attachment/201112/4/0_1323022871C1w5.gif)
可以看到所有挂载在内存池上的资源将形成一个循环链表,一路走来,发现链表这种看似简单的数据结构却被频繁使用。
由图可知,每个需要清理的资源都对应有一个头部结构,这个结构中有一个关键的字段handler,handler是一个函数指针,在挂载一个资源到内存池上的时候,同时也会注册一个清理资源的函数到这个handler上。即是说,内存池在清理cleanup的时候,就是调用这个handler来清理对应的资源。
比如:我们可以将一个开打的文件描述符作为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来处理文件描述符资源了。
内存的释放
nginx只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问题,nginx利用了web server应用的特殊场景来完成;
一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级的内存池、request级的内存池。
也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,为此连接创建一个内存池;连接上到来一个request后,又为request创建起一个内存池。
这样,在request被处理完后,就会释放request的整个内存池,但是不释放connection的内存池,因为还可能又其他请求;连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。
小结:通过内存的分配和释放可以看出,nginx只是将小块内存的申请聚集到一起申请,然后一起释放。避免了频繁申请小内存,降低内存碎片的产生等问题。
相关文章推荐
- Nginx源码分析-内存池
- Nginx 源码分析-- 内存池(pool)的分析 二
- Nginx高级数据结构源码分析(四)-----内存池
- nginx源码分析—内存池结构ngx_pool_t及内存管理(精辟)
- nginx源码分析——内存池
- Nginx源码分析-内存池
- Nginx源码分析(3)之——内存池(ngx_pool_t)分析
- nginx源码分析--内存池ngx_poll_t 内存池管理
- nginx源码分析—内存池结构ngx_pool_t及内存管理
- nginx源码分析—内存池结构ngx_pool_t及内存管理
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- Nginx源码分析 - 基础数据结构篇 - 内存池 ngx_palloc.c
- Nginx 源码分析-- 内存池(pool)的分析 一
- Nginx源码分析-内存池
- Nginx源码分析-内存池
- nginx源码分析之内存池实现原理
- nginx源码分析—内存池结构ngx_pool_t及内存管理
- nginx源码分析2———基础数据结构三(内存池)