一、内存池
内存池主要是为了解决内存碎片的问题,如果有大量客户端连接,并且每次只占用一点内存,会出现很多的内存碎片。
nginx中为了更好地管理内存,分为大块和小块(chunk),大块用于存储大的内存,小块用于存储比较小的内存。
应用:
nginx中一个tcp连接来了之后,处理该连接的所有数据,内存都由内存池来管理。
二、大块
nginx中大块的结构
typedef struct ngx_pool_large_s ngx_pool_large_t; //大块 struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
大块的存储结构图
通过链表将节点串起来
三、chunck(小块)
//ngx_pool_data_t可以理解为是嵌入到ngx_pool_s里面的 typedef struct { u_char *last;//内存块中last指向还没有用过的内存的头部(比如要分配5个字节,那么从last开始分配) u_char *end;//当前内存块中末尾 ngx_pool_t *next;//下一块chunck ngx_uint_t failed; } ngx_pool_data_t; //内存池 struct ngx_pool_s { ngx_pool_data_t d;//存储一些chunck块的指针信息 size_t max;//chunk块的大小 ngx_pool_t *current;//当前pool(chunck)查找空余chunck的起始遍历的地址 ngx_chain_t *chain;//用于存储buffer的链结构(在内存池中并没有使用) ngx_pool_large_t *large;//大块 ngx_pool_cleanup_t *cleanup;//用于清理小块内存 ngx_log_t *log;//日志相关 };
只有第一个chunck块(x小块),才会指向大块。后面几个结构中的large都是没有用的。
四、nginx内存池的结构图
五、源码阅读
1、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);//分配一块根据size大小,并以16字节对齐(是要16字节的整数倍)的内存。 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;//不超过(uintptr_t)-1的大小,也就是uintptr_t类型的最大值 p->current = p;//指向当前的内存池 p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; return p; }
2、ngx_destroy_pool
销毁内存池
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) {//遍历chunck,并调用当前chunck的清理chunck内存的回调函数 if (c->handler) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "run cleanup: %p", c); c->handler(c->data); } } //清理大块 for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } } //释放每个chunck的头部 for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { ngx_free(p); if (n == NULL) { break; } } }
3、ngx_reset_pool
重置内存池
1.释放所有大块
2.将所有chunk中的last指针重置,也就是放在头部的后面。不需要清空chunck里面的数据,使用的时候直接覆盖就行了
void ngx_reset_pool(ngx_pool_t *pool) { ngx_pool_t *p; ngx_pool_large_t *l; //释放所有的大块内存(但是,大块的链表节点依然还在,因为它存储在chunck里面) for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } } //将chunck中last指针初始化,chunck内存中的数据不需要清理,因为,可以直接覆盖 for (p = pool; p; p = p->d.next) { p->d.last = (u_char *) p + sizeof(ngx_pool_t); p->d.failed = 0; } pool->current = pool; pool->chain = NULL; pool->large = NULL; }
4、ngx_palloc
从内存池中取一块来进行分配。
如果小于chunck可以分配的最大大小,那么就分配到chunck中(小块)
如果大于chunck可以分配的最大大小,那么就分配到大块中
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); }
5、ngx_pnalloc
和ngx_palloc类似,只是,不采用内存对齐
void * ngx_pnalloc(ngx_pool_t *pool, size_t size) { #if !(NGX_DEBUG_PALLOC) if (size <= pool->max) { return ngx_palloc_small(pool, size, 0);//不适用内存对齐 } #endif return ngx_palloc_large(pool, size); }
6、ngx_palloc_small
在chunck(小块)中分配一块内存
遍历chunck链,如果找到一块可以存放的空间,就返回可分配的内存空间的起始地址
如果找不到,就创建一个新的chunck
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) {//如果使用内存对齐,那么下面就执行对齐。比如按照4个字节对齐,内存已经使用了14字节,那么都当作已经用完了16个字节,从这后面再开始使用 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;//如果当前chunck没有内存可以供当前大小内存分配,就查找下一个chunck } while (p); return ngx_palloc_block(pool, size);//遍历完所有chunck都没有可以分配的,那么就新创建一个chunck }
7、ngx_palloc_block
创建一块新的chunk内存空间,也就是在ngx_palloc_small
的chunck链中没找到可以分配的空间,那么就创建一块。
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);//分配一块psize大小的内存 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) {//如果failed > 4, 就漏过这个chunck(也就是说每个块,在查找块的时候只允许失败4次,失败4次后,让current指向该节点的下一个) pool->current = p->d.next; } } //链表尾部用来存新的chunck p->d.next = new; return m; }
8、ngx_palloc_large
分配一个大块节点(节点存到(chunck)小块里面),通过链表的方式,串起来。
然后节点上的指针,指向大块,通过这种方式,将大块组织起来
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) {//如果查找3次还没有找到空的节点,那么就直接break,然后采用头插法 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; }
9、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; }
10、ngx_pfree
释放内存池,小块不会被释放,只有大块释放了
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) { ngx_pool_large_t *l; for (l = pool->large; l; l = l->next) {//遍历链表,释放大块 if (p == l->alloc) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); ngx_free(l->alloc); l->alloc = NULL; return NGX_OK; } } return NGX_DECLINED; }
大块可以通过ngx_pfree来释放内存,那么小块呢?
一个tcp连接,就会创建内存池,小块在tcp断开连接时候,才会释放,就是ngx_destory_pool的时候
11、ngx_pcalloc
从内存池中分配一块内存,并初始化为0
//分配内存,并初始化为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; }