Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

Nginx 内存池 ngx_pool_tnginx 是自己实现了内存池的,所以在nginx ngx_pool_t 这个结构也随处可见,这里主要分析一下内存池的分配逻辑 。
内存池实现了包括小块内存、大块内存和清理资源几种资源的处理,应该来说覆盖了绝大数的使用场景了 。
文章相关视频讲解:【Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池】高性能服务器为什么需要内存池?内存如何分配? 如何设计内存 ?看视频讲解:「链接」
Nginx源码分析之内存池与线程池:「链接」
相关结构定义// 大块内存typedef struct ngx_pool_large_sngx_pool_large_t;struct ngx_pool_large_s {ngx_pool_large_t*next;// 下一个大块内存池void*alloc;// 实际分配内存};// 小块内存池typedef struct {u_char*last;// 可分配内存起始地址u_char*end;// 可分配内存结束地址ngx_pool_t*next;// 指向内存管理结构ngx_uint_tfailed;// 内存分配失败次数} ngx_pool_data_t;// 内存池管理结构typedef struct ngx_pool_sngx_pool_t;struct ngx_pool_s {ngx_pool_data_td;// 小块内存池size_tmax;// 小块内存最大的分配内存,评估大内存还是小块内存ngx_pool_t*current;// 当前开始分配的小块内存池ngx_chain_t*chain;// chainngx_pool_large_t*large;// 大块内存ngx_pool_cleanup_t*cleanup;// 待清理资源ngx_log_t*log;// 日志对象};ngx_pool_t 是整个内存池的管理结构,这种结构对于个内存池对象来说可能存在多个,但是对于用户而言,第一下访问的始终是创建时返回的那个 。多个 ngx_pool_t 通过 d.next 来进行连接,current 指向 当前开始分配的小块内存池,注意 ngx_pool_data_t 在内存池结构的起始处,可以进行类型转换访问到不同的成员 。
实现内存对齐#define ngx_align(d, a)(((d) + (a - 1)) & ~(a - 1))#define ngx_align_ptr(p, a)(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))参考 ngx_align 值对齐宏 分析,ngx_align_ptr 同理
创建内存池max 的最大值为 4095,当从内存池中申请的内存大小大于 max 时,不会从小块内存中进行分配 。
ngx_uint_tngx_pagesize = getpagesize();// linux 上是 4096#define NGX_POOL_ALIGNMENT 16#define NGX_MAX_ALLOC_FROM_POOL(ngx_pagesize - 1)// 4095ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log){ngx_pool_t*p;p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);// 16 字节对齐申请 size 大小的内存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);// 设置小块内存可分配的最大值(小于 4095)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;}内存池创建后的结构逻辑如图所示:

Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

文章插图
 
内存申请申请的内存块以 max 作为区分
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);}#endifreturn ngx_palloc_large(pool, size);}小块内存申请current 指向每次申请内存时开始检索分配的小块内存池,而 ngx_palloc_small 的参数 pool 在内存池没有回收时,是固定不变的 。
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;// 从 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); // 没有满足分配的内存池,再申请一个小块内存池}当在小块内存池中找到了合适的内存后的结构如下:
Nginx 内存池似懂非懂?一文带你看清高性能服务器内存池

文章插图
 
当没有小块内存池满足申请时,会再申请一个小块内存池来满足分配,在设置完 last 和 end 两个内存指示器后,对从 current 开始的内存池成员 failed 进行自增操作,并且当这个内存池的 failed 分配次数大于 4 时,表面这个内存分配失败的次数太多,根据经验应该下一次分配可能还是失败,所以直接跳过这个内存池,移动 current 。
新的内存块插入至内存池链表的尾端 。


推荐阅读