nginx中关于ngx_pool_t的数据结构位于src/core/ngx_palloc.c和src/core/ngx_palloc.h中,该数据结构主要是和内存池相关的,写下这篇博客前参考了网上很多文章,研究将近3个晚上,下面把网上的文章和自己的理解做一个汇总。
首先先看2个比较重要的结构体:
1、ngx_pool_data_t
1 typedef struct { 2 //申请过的内存的尾地址,可申请的首地址 3 //pool->d.last ~ pool->d.end 中的内存区便是可用数据区。 4 u_char *last; 5 //当前内存池节点可以申请的内存的最终位置 6 u_char *end; 7 //下一个内存池节点ngx_pool_t,见ngx_palloc_block 8 ngx_pool_t *next; 9 //当前节点申请内存失败的次数, 如果发现从当前pool中分配内存失败四次,则使用下一个pool,见ngx_palloc_block 10 ngx_uint_t failed; 11 } ngx_pool_data_t;
2、ngx_pool_s
1 struct ngx_pool_s { 2 //节点数据 3 ngx_pool_data_t d; 4 //当前内存节点可以申请的最大内存空间 5 size_t max; 6 //每次从pool中分配内存的时候都是从curren开始遍历pool节点获取内存的,其实就是内存池中可以申请内存的第一个节点 7 //之所以有节点的概念是因为后面我们会介绍到小块的内存池是通过尾插法维护了一个链表的 8 ngx_pool_t *current; 9 //暂时没有研究 10 ngx_chain_t *chain; 11 //节点中大内存块指针 12 ngx_pool_large_t *large; 13 //暂时没有研究 14 ngx_pool_cleanup_t *cleanup; 15 //暂时没有研究 16 ngx_log_t *log; 17 };
然后再看几个宏定义:
1 typedef struct ngx_pool_s ngx_pool_t; 2 typedef struct ngx_chain_s ngx_chain_t; 3 typedef struct ngx_log_s ngx_log_t;
这几个宏位于src/core/ngx_core.h中,就是上面的xxx_s都会被搞成xxx_t,免得看代码的时候对应不上。
再看一下用UML绘制的ngx_pool_t的逻辑结构图:
知道了基本结构体之后,我们就可以看内存池相关的函数了。
1、ngx_create_pool
1 ngx_pool_t * 2 ngx_create_pool(size_t size, ngx_log_t *log) 3 { 4 ngx_pool_t *p; 5 6 // 分配一块 size 大小的内存 内存空间16字节对齐 7 p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); 8 if (p == NULL) { 9 return NULL; 10 } 11 12 // 对pool中的数据项赋初始值 13 //可用空间要减去这个头部 首sizeof(ngx_pool_t)便是pool的header信息 14 //header信息中的各个字段用于管理整个pool 15 p->d.last = (u_char *) p + sizeof(ngx_pool_t); 16 //指向当前节点最后面 17 p->d.end = (u_char *) p + size; 18 p->d.next = NULL; 19 p->d.failed = 0; 20 21 size = size - sizeof(ngx_pool_t); 22 //不能超过NGX_MAX_ALLOC_FROM_POOL 23 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; 24 25 //指向当前使用的可以分配内存的节点 26 p->current = p; 27 p->chain = NULL; 28 p->large = NULL; 29 p->cleanup = NULL; 30 p->log = log; 31 32 //把空间最顶部头部返回 33 return p; 34 }
执行了上面的操作之后,内存的图示(下面4个图均表示同样的情况,均参考了网上博客,参考地址本文末尾列出)如下:
图1
图2
图3
图4
2、ngx_palloc
1 void * 2 ngx_palloc(ngx_pool_t *pool, size_t size) 3 { 4 u_char *m; 5 ngx_pool_t *p; 6 7 // 判断 size 是否大于 pool 最大可使用内存大小 8 if (size <= pool->max) { 9 10 //从current所在的pool数据节点开始往后遍历寻找哪个节点可以分配size内存 11 p = pool->current; 12 13 do { 14 // 将 m 对其到内存对齐地址 15 m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); 16 17 // 判断 pool 中剩余内存是否够用,够用就直接返回【情况1】 18 if ((size_t) (p->d.end - m) >= size) { 19 p->d.last = m + size; 20 21 return m; 22 } 23 24 //如果当前节点的内存不够,则在下一个节点的内存块中分配空间 25 p = p->d.next; 26 27 } while (p); 28 29 //都没有空间的话,就需要重新搞一个节点了,用尾插法插入链表【情况2】 30 return ngx_palloc_block(pool, size); 31 } 32 33 //判断size已经大于pool->max的大小了,所以直接调用ngx_palloc_large进行大内存分配【情况3】 34 return ngx_palloc_large(pool, size); 35 }
在上面内存分配的时候用到了一个宏:
1 #define ngx_align_ptr(p, a) \ 2 (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
对于这个宏参考网上流程如下:
下面还是通过图示来表示上面的流程(所有图示均参考了网上博客,参考地址本文末尾列出)
【情况1】就是在当前的节点下面找,如果可以找到,直接返回地址,并更改对应的last指针(对应下面图1、图2和图3的情况);当前节点下找不到,就去后面的节点找,如果可以找到也返回地址,并更改对应的last指针(对应下面图4和图5的情况)
图1
图2
图3
图4
图5
【情况2】对应现有所有节点下都不足以分配新的内存(该新内存值小于max),调用ngx_palloc_block重新生成节点并分配内存
1 static void * 2 ngx_palloc_block(ngx_pool_t *pool, size_t size) 3 { 4 u_char *m; 5 size_t psize; 6 ngx_pool_t *p, *new; 7 8 // 先前的整个 pool 的大小 9 psize = (size_t) (pool->d.end - (u_char *) pool); 10 11 // 在内存对齐了的前提下,新分配一块内存 12 m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); 13 if (m == NULL) { 14 return NULL; 15 } 16 17 new = (ngx_pool_t *) m; 18 19 new->d.end = m + psize; 20 new->d.next = NULL; 21 new->d.failed = 0; 22 23 //注意,新分配节点的头并不是全部了(ngx_pool_t),而是一部分(ngx_pool_data_t) 24 m += sizeof(ngx_pool_data_t); 25 m = ngx_align_ptr(m, NGX_ALIGNMENT); 26 new->d.last = m + size; 27 28 // 判断在当前 pool 分配内存的失败次数,即:不能复用当前 pool 的次数, 29 // 如果大于 4 次,这放弃在此 pool 上再次尝试分配内存,以提高效率 30 //如果失败次数大于4(不等于4),则更新current指针,放弃对老pool的内存进行再使用 31 for (p = pool->current; p->d.next; p = p->d.next) { 32 if (p->d.failed++ > 4) { 33 // 更新 current 指针, 每次从pool中分配内存的时候都是从curren开始遍历pool节点获取内存的 34 pool->current = p->d.next; 35 } 36 } 37 38 // 让旧指针数据区的 next 指向新分配的 pool,这里就把节点都链接起来了 39 p->d.next = new; 40 41 return m; 42 }
图1
图2
图3
图4
【情况3】想要分配的内存大于了允许分配的最大内存,那么直接就用malloc()系统函数分配空间,并且也不用对齐了。非常值得注意的一点,我在看的时候纠结了新分配的daneic大内存节点信息保存在哪里?实际上是保存在内存池节点里面的内存块中的。
借用网上资料的解释:注意每块大内存都对应有一个头部结构(next&alloc),这个头部结构是用来将所有大内存串成一个链表用的。这个头部结构不是直接向操作系统申请的,而是当做小块内存(头部结构没几个字节)直接在内存池里申请的。这样的大块内存在使用完后,可能需要第一时间释放,节省内存空间,因此nginx提供了接口函数:ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);此函数专门用来释放某个内存池上的某个大块内存,p就是大内存的地址。ngx_pfree只会释放大内存,不会释放其对应的头部结构,毕竟头部结构是当做小内存在内存池里申请的;遗留下来的头部结构会作下一次申请大内存之用。
1 static void * 2 ngx_palloc_large(ngx_pool_t *pool, size_t size) 3 { 4 void *p; 5 ngx_uint_t n; 6 ngx_pool_large_t *large; 7 8 // 调用系统调用malloc()函数重新申请一块大小为 size 的新内存 9 // 注意:此处不使用 ngx_memalign 的原因是,新分配的内存较大,对其也没太大必要 10 p = ngx_alloc(size, pool->log); 11 if (p == NULL) { 12 return NULL; 13 } 14 15 n = 0; 16 17 // 查找largt链表上空余的large 指针 18 for (large = pool->large; large; large = large->next) { 19 //有空余的话,就用这个空余的large,改变一下指针即可,免去再分配内存了 20 if (large->alloc == NULL) { 21 large->alloc = p; 22 return p; 23 } 24 25 // 如果当前 large 后串的 large 内存块数目大于 3 (不等于3), 26 // 则直接去下一步分配新内存,不再查找了,并且用头插法弄到链表表头 27 if (n++ > 3) { 28 break; 29 } 30 } 31 32 //在内存池中分配内存,这个内存非常小,仅仅是ngx_pool_large_t的头 33 large = ngx_palloc(pool, sizeof(ngx_pool_large_t)); 34 if (large == NULL) { 35 ngx_free(p); 36 return NULL; 37 } 38 39 // 将新分配的 large 串插入链表头部 40 large->alloc = p; 41 large->next = pool->large; 42 pool->large = large; 43 44 return p; 45 }
图1
图2
图3
图4
概况一下,总体的内存池结构图示如下:
图1
图2
图3
本文参考自:
http://www.cnblogs.com/v-July-v/archive/2011/12/04/2316410.html
http://blog.youkuaiyun.com/apelife/article/details/52974336
http://www.linuxidc.com/Linux/2011-08/41860.htm (此博客中图有误,我都重新改过来了)
http://developer.51cto.com/art/201108/283814.htm
http://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html
http://blog.zhipcui.com/nginx/2015/01/12/nginx-pool.html