关于
palloc
是nginx自身实现的一个内存池模块,其遍及整个nginx的源码之中,也是nginx能简洁高效处理各个请求的基础所在。本文先从ngx_alloc
和ngx_palloc
2个文件来解读内存模块。
ngx_alloc
文件
整个ngx_alloc
包含了3个函数:ngx_alloc
、ngx_calloc
和ngx_memalign
。
ngx_alloc
和ngx_calloc
方法都是利用malloc
方法来分配内存,不同的是ngx_calloc
方法会在分配后进行初始化工作。而ngx_memalign
方法,则是利用memalign
或posix_memalign
方法申请一个内存对齐的内存块。 内存对齐的用处首先是可以提高cpu效率,因为不对齐会导致cpu访问内存时候需要拆分内存块;第二是方便平台的移植。
ngx_palloc
模块结构体
上节的ngx_alloc
文件是对c语言内存的封装,此后的内存分配都是通过调取其中的三个方法进行的。那么我们先来了解一下ngx_palloc
包含的结构体。
ngx_pool_s
结构体
struct ngx_pool_s { ngx_pool_data_t d; size_t max; ngx_pool_t *current; ngx_chain_t *chain; ngx_pool_large_t *large; ngx_pool_cleanup_t *cleanup; ngx_log_t *log;};
ngx_pool_s
结构体是整个内存池的核心结构体。它本身是一个记录表,其中记录了整个内内存池的内存分配信息链的头指针。其中主要的属性分别是d
、large
和cleanup
三个属性,这也是我们接下来要了解的三个结构体的指针。
ngx_pool_data_t
结构体
typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed;} ngx_pool_data_t;
ngx_pool_data_t
结构体其实就像是ngx_pool_s
结构体的一个详细描述,其中描述了一个内存池的信息,包括当前分配完的内存地址、内存池最后的内存地址、下一个内存池指针以及分配内存失败次数。
ngx_pool_large_s
结构体
struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc;};
这个结构体就比较简单,就算一个链表,并包含一个指针指向当前分配的内存块。
ngx_pool_cleanup_s
结构体
struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next;};
ngx_pool_cleanup_s
结构体的功能主要是用来在销毁内存池时,需要处理一下其他的操作来保证内存的正常销毁,避免内存的泄露。因此,在销毁内存期间,会触发这个ngx_pool_cleanup_s
的链表,并以此执行销毁函数。
ngx_pool_cleanup_file_t
结构体
typedef struct { ngx_fd_t fd; u_char *name; ngx_log_t *log;} ngx_pool_cleanup_file_t;
这个结构体,主要用途就是为了在销毁内存块的时候,能对文件描述符进行关闭等操作。(感觉是这样)
ngx_palloc
模块函数
ngx_create_pool
方法
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; } p->d.last = (u_char *) p + sizeof(ngx_pool_t); 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;}
这个方法主要是利用ngx_memalign
方法来分配内存块,然后计算出d.last
和d.end
的2个属性,其他属性都比较容易理解。
ngx_palloc
方法以及ngx_pnalloc
方法
void *ngx_palloc(ngx_pool_t *pool, size_t size){#if !(NGX_DEBUG_PALLOC) if (size <= pool->max) { return ngx_palloc_small(pool, size, 1); }#endif return ngx_palloc_large(pool, size);}
该函数理解比较简单,就算判断内存块大小是否大于最大的内存块,若大于则使用大块内存的分配。
ngx_palloc_small
方法
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);}
在分配小块内存时,就算不断的寻找是否存在符合条件的内存大小,若存在,则将内存块地址返回,并将d.last
往后移动分配的内存大小,即完成了内存分配。若不存在,则利用ngx_palloc_block
方法去生成一个新的内存块。
ngx_palloc_block
方法
static void *ngx_palloc_block(ngx_pool_t *pool, size_t size){ u_char *m; size_t psize; ngx_pool_t *p, *new; psize = (size_t) (pool->d.end - (u_char *) pool); m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); if (m == NULL) { return NULL; } new = (ngx_pool_t *) m; new->d.end = m + psize; new->d.next = NULL; new->d.failed = 0; m += sizeof(ngx_pool_data_t); m = ngx_align_ptr(m, NGX_ALIGNMENT); new->d.last = m + size; for (p = pool->current; p->d.next; p = p->d.next) { if (p->d.failed++ > 4) { pool->current = p->d.next; } } p->d.next = new; return m;}
该函数其实用途在于重新生成一个新的内存池,同时内存池的大小和最初的内存池是相同大小。关键在于,他会对失败大于4次的内存池的当前指针进行移动,这样可以提高之后的内存查找的效率。
ngx_palloc_large
方法
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 p; } 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;}
nginx内存池有趣的地方就在于,他们直接可能会互相调用来实现自己的功能,例如当前的方法,首先它回去直接申请一个需要的内存块,之后它需要去查找ngx_pool_large_t
的链表,看看有没有某个ngx_pool_large_t
的alloc
是为空的,这样就可以将分配好的地址挂载上去。
ngx_pool_large_t
的节点,然后将其加入ngx_pool_large_t
的链表中。 ngx_pmemalign
方法
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment){ void *p; ngx_pool_large_t *large; 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;}
该方法就是ngx_palloc_large
简单暴力版,直接申请ngx_pool_large_t
并加入链表中。
ngx_destroy_pool
方法
voidngx_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); } }#if (NGX_DEBUG) ... ...#endif for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } } for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { ngx_free(p); if (n == NULL) { break; } }}
ngx_destroy_pool
方法的执行流程主要如下:先进行cleanup
操作,触发销毁方法、再进行大块内存的销毁、最后销毁销毁内存。销毁方法都是使用ngx_free
,其实就算free
方法。
ngx_pool_cleanup_add
方法
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){ ngx_pool_cleanup_t *c; c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); if (c == NULL) { return NULL; } if (size) { c->data = ngx_palloc(p, size); if (c->data == NULL) { return NULL; } } else { c->data = NULL; } c->handler = NULL; c->next = p->cleanup; p->cleanup = c; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c); return c;}
该方法主要是为内存池添加一个销毁的接口对象,先进行分配内存块,之后再在该内存上初始化变量,变量类似ngx_pool_cleanup_file_t
,然后设置handle
属性,用于以后内存池销毁。
总结
nginx的内存池功能相对stl的内存池更好理解,也许是代码风格问题导致阅读难度的增加。不过学习了nginx的内存分配后,就可以开始其他的模块的阅读。