Nginx——2
例如对于内存的管理,如果我们需要使用内存,那么总是从一个ngx_pool_t的对象中获取内存,在最终的某个时刻,我们销毁这个ngx_pool_t对象,所有这些内存都被释放了。这样我们就不必要对对这些内存进行malloc和free的操作,不用担心是否某块被malloc出来的内存没有被释放。因为当ngx_pool_t对象被销毁的时候,所有从这个对象中分配出来的内存都会被统一释放掉。
注:笔者的nginx版本为1.10.1
一、数据结构定义
ngx_pool_t定义在[nginx源码目录]/src/core/ngx_palloc.h以及[nginx源码目录]/src/core/ngx_palloc.c,与之相关的头文件和源文件好包括[nginx源码目录]/src/os/unix/ngx_alloc.h以及ngx_alloc.c中。
- struct ngx_pool_t { //内存池的管理分配模块
- ngx_pool_data_t d; //内存池的数据块
- size_t max; //数据块大小,可分配小块内存的最大值
- ngx_pool_t *current; //指向当前或本内存池,以后的内存分配从该指针指向的内存池中分配
- ngx_chain_t *chain; //该指针挂接一个ngx_chain_t结构
- ngx_pool_large_t *large; //指向大块内存分配,nginx中,大块内存分配直接采用标准系统接口malloc
- ngx_pool_cleanup_t *cleanup; //析构函数,挂载内存释放时需要清理资源的一些必要操作
- ngx_log_t *log; //内存分配相关的日志记录
- };
其中内存池的数据块ngx_pool_data_t的数据结构定义如下:
- typedef struct { //内存池的数据结构模块
- u_char *last; //当前内存分配结束位置,即下一段可分配内存的起始位置
- u_char *end; //内存池的结束位置
- ngx_pool_t *next; //链接到下一个内存池,内存池的很多块内存就是通过该指针连成链表的
- ngx_uint_t failed; //记录内存分配不能满足需求的失败次数
- } ngx_pool_data_t; //结构用来维护内存池的数据块
还有一个很重要的大块内存分配数据结构模块定义:
- struct ngx_pool_large_t {
- ngx_pool_large_t *next; //指向下一个大内存块
- void *alloc; //实际利用malloc分配得到的内存的首地址
- };
大致数据结构图示如下:
二、内存管理解析
- 内存的创建、销毁和重置
- 2.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); //ngx_memlign定义在ngx_alloc.h中,主要用于分配16字节边界对其的的内存块,返回指向该内存块的指针
- if (p == NULL) { //NGC_POOL_ALIGNMENT定义为16
- return NULL;
- }
- p->d.last = (u_char *) p + sizeof(ngx_pool_t); //d.last指向当前已经分配的内存的末端地址,即下一个可以分配内存的首地址
- 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; //NGC_MAX_ALLOC_FROM_POOL最大不超过4095B
- p->current = p; //初始化指向当前从内存中取得的内存块的首地址
- p->chain = NULL;
- p->large = NULL; //创建内存池时,并没有需要很大内存块,所以为空
- p->cleanup = NULL;
- p->log = log;
- return p;
- }
ngx_memalign函数定义如下:
- void *
- ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
- {
- void *p;
- p = memalign(alignment, size);
- if (p == NULL) {
- ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
- "memalign(%uz, %uz) failed", alignment, size);
- }
- ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
- "memalign: %p:%uz @%uz", p, size, alignment);
- return p;
- }
- 2.2 内存池销毁
- 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;
- }
- }
- }
- //该片段彻底销毁内存池本身。内存池是又多个链表相连接,d.next指向下一个内存池
- 2.3 重置内存块
- void
- ngx_reset_pool(ngx_pool_t *pool) //重置poo内存池,使得pool内存池回归初始状态
- {
- ngx_pool_t *p;
- ngx_pool_large_t *l;
- for (l = pool->large; l; l = l->next) {
- if (l->alloc) {
- ngx_free(l->alloc); //释放所有大内存块
- }
- }
- for (p = pool; p; p = p->d.next) {
- p->d.last = (u_char *) p + sizeof(ngx_pool_t); //使得last指向ngx_pool_t后的内存地址
- p->d.failed = 0;
- }
- pool->current = pool;
- pool->chain = NULL;
- pool->large = NULL;
- }
- 从内存池中分配内存
从内存池中取得size大小的内存主要通过ngx_palloc以及ngx_pnalloc,两者的区别是,前者从内存池中取得NGX_ALIGNMENT字节对其的内陈块,而后者不考虑字节对其,NGX_ALIGNMENT定义在ngx_config.h中:
- #ifndef NGX_ALIGNMENT
- #define NGX_ALIGNMENT sizeof(unsigned long) /* platform word */
- #endif
以ngx_palloc分析:
- void *
- ngx_palloc(ngx_pool_t *pool, size_t size)
- {
- #if !(NGX_DEBUG_PALLOC)
- if (size <= pool->max) { //若需要分配的内存大小大于内存池中的可分配内存,直接调用ngx_palloc_large分配,否则调用ngx_palloc_small分配,1代表直接对齐
- return ngx_palloc_small(pool, size, 1);
- }
- #endif
- return ngx_palloc_large(pool, size);
- }
- 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;
- //执行对齐操作,
- //即以last开始,计算以NGX_ALIGNMENT对齐的偏移位置指针,
- if (align) {
- m = ngx_align_ptr(m, NGX_ALIGNMENT);
- }
- //然后计算end值减去这个偏移指针位置的大小是否满足索要分配的size大小,
- //如果满足,则移动last指针位置,并返回所分配到的内存地址的起始地址;
- if ((size_t) (p->d.end - m) >= size) {
- p->d.last = m + size;
- return m;
- }
- //如果不满足,则查找下一个内存池。
- p = p->d.next;
- } while (p);
- //如果遍历完整个内存池链表均未找到合适大小的内存块供分配,则执行ngx_palloc_block()来分配。
- //ngx_palloc_block()函数为该内存池再分配一个block,该block的大小为链表中前面每一个block大小的值。
- //一个内存池是由多个block链接起来的。分配成功后,将该block链入该poll链的最后,
- //同时,为所要分配的size大小的内存进行分配,并返回分配内存的起始地址。
- return ngx_palloc_block(pool, size);
- }
当需要进行ngx_palloc_block进行小内存分块时,ngx_palloc_block定义如下:
- ngx_palloc_block分配的可分配内存大小和pool指向的可分配内存大小一样
- 该函数分配一块内存后,last指针指向的是ngx_pool_data_t结构体(大小16B)之后数据区的起始位置,而创建内存池时时,last指针指向的是ngx_pool_t结构体(大小40B)之后数据区的起始位置。
- 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 = (size_t) (pool->d.end - (u_char *) pool);
- //计算pool的大小,即需要分配的block的大小
- m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
- if (m == NULL) {
- return NULL;
- }
- //执行按NGX_POOL_ALIGNMENT对齐方式的内存分配,假设能够分配成功,则继续执行后续代码片段。
- //这里计算需要分配的block的大小
- new = (ngx_pool_t *) m;
- new->d.end = m + psize;
- new->d.next = NULL;
- new->d.failed = 0;
- //执行该block相关的初始化。
- m += sizeof(ngx_pool_data_t);
- //让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置
- m = ngx_align_ptr(m, NGX_ALIGNMENT);
- new->d.last = m + size;
- //在数据区分配size大小的内存并设置last指针
- current = pool->current;
- for (p = current; p->d.next; p = p->d.next) {
- if (p->d.failed++ > 4) {
- current = p->d.next;
- //失败4次以上移动current指针
- }
- }
- p->d.next = new;
- //将分配的block链入内存池
- pool->current = current ? current : new;
- //如果是第一次为内存池分配block,这current将指向新分配的block。
- return m;
- }
当请求的内存分配大小size大于当前内存池的可分配内存时(由于内存池是一个链表,随着不断地申请内存,current的指针会指向不同的内存池首地址),此时会调用ngx_palloc_large,ngx_palloc_large实现如下:
- //这个函数在头文件里面并没有明确声明出来,而是在源文件中定义
- //即nginx在进行内存分配需求时,不会自行去判断是否是大块内存还是小块内存,
- //而是交由内存分配函数去判断,对于用户需求来说是完全透明的。
- 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); //下文紧接着将分析此ngx_alloc函数
- if (p == NULL) {
- return NULL;
- }
- n = 0;
- //以下几行,将分配的内存链入pool的large链中,
- //这里指原始pool在之前已经分配过large内存的情况。
- for (large = pool->large; large; large = large->next) {
- if (large->alloc == NULL) {
- large->alloc = p;
- return p;
- }
- if (n++ > 3) {
- break;
- }
- }
- //如果该pool之前并未分配large内存,则就没有ngx_pool_large_t来管理大块内存
- //执行ngx_pool_large_t结构体的分配,用于来管理large内存块。ngx_pool_large_t所需内存依然遵循从内存池中取所需内存的原则
- large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
- if (large == NULL) {
- ngx_free(p);
- return NULL;
- }
- large->alloc = p;
- large->next = pool->large;
- pool->large = large;
- return p;
- }
其中,ngx_alloc定义在文件src/os/unix/ngx_alloc.c中:
- void *
- ngx_alloc(size_t size, ngx_log_t *log)
- {
- void *p;
- p = malloc(size);
- if (p == NULL) {
- ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
- "malloc(%uz) failed", size);
- }
- ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
- return p;
- }
随后是内存的释放过程:通过调用ngx_destroy_pool对内存池的所有内存进行统一的释放,避免了内存泄露的可能。
三、案例分析
下面以一个案例简单的分析内存池分配内存的过程:
整个示例代码如下:
- /**
- * ngx_pool_t test, to test ngx_palloc, ngx_palloc_block, ngx_palloc_large
- */
- #include <stdio.h>
- #include "ngx_config.h"
- #include "ngx_conf_file.h"
- #include "nginx.h"
- #include "ngx_core.h"
- #include "ngx_string.h"
- #include "ngx_palloc.h"
- void dump_pool(ngx_pool_t* pool)
- {
- while (pool)
- {
- printf("pool = 0x%x\n", pool);
- printf("\t.d = 0x%x\n",&pool->d);
- printf("\t.last = 0x%x\n", pool->d.last);
- printf("\t.end = 0x%x\n", pool->d.end);
- printf("\t.next = 0x%x\n", pool->d.next);
- printf("\t.failed = %d\n", pool->d.failed);
- printf("\t.max = %d\n", pool->max);
- printf("\t.current = 0x%x\n", pool->current);
- printf("\t.chain = 0x%x\n", pool->chain);
- printf("\t.large = 0x%x\n", pool->large);
- printf("\t.cleanup = 0x%x\n", pool->cleanup);
- printf("\t.log = 0x%x\n", pool->log);
- printf("available pool memory = %d\n\n", pool->d.end - pool->d.last);
- pool = pool->d.next;
- }
- }
- int main()
- {
- ngx_pool_t *pool;
- printf("--------------------------------\n");
- printf("the size of ngx_pool_t is:%d,and the size of intptr_t is:%d\n",sizeof(ngx_pool_t),sizeof(intptr_t));
- printf("--------------------------------\n");
- printf("the size of size_t is:%d\n",sizeof(size_t));
- printf("--------------------------------\n");
- printf("create a new pool:\n");
- printf("--------------------------------\n");
- pool = ngx_create_pool(1024, NULL);
- dump_pool(pool);
- printf("--------------------------------\n");
- printf("alloc block 1 from the pool:\n");
- printf("--------------------------------\n");
- ngx_palloc(pool, 1024);
- dump_pool(pool);
- printf("--------------------------------\n");
- printf("alloc block 2 from the pool:\n");
- printf("--------------------------------\n");
- ngx_palloc(pool, 1024);
- dump_pool(pool);
- printf("--------------------------------\n");
- printf("alloc block 3 from the pool :\n");
- printf("--------------------------------\n");
- ngx_palloc(pool, 512);
- dump_pool(pool);
- ngx_pool_large_t *p;
- p=pool->large;
- int i=1;
- while(p)
- {
- printf("%dth large block address is:0x%x\n",i,p);
- ++i;
- p=p->next;
- }
- ngx_destroy_pool(pool);
- return 0;
- }
1、首先创建内存池:
- pool = ngx_create_pool(1024, NULL);
2、随着向内存池中申请1024Bytes数据,由于当前可分配内存为944Bytes,所以需要调用ngx_palloc_large取得所需内存:
图示如下:
4、从内存池中申请512Bytes内存,由于512Bytes小于当前可分配内存912Bytes,因此调用ngx_palloc_small进行内存分配
转自:http://blog.****.net/mao19931004/article/details/54377101