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


#define NGX_ALIGNMENTsizeof(unsigned long)// 8static void *ngx_palloc_block(ngx_pool_t *pool, size_t size){u_char*m;size_tpsize;ngx_pool_t*p, *new;psize = (size_t) (pool->d.end - (u_char *) pool);// 每一个内存池的大小都相同m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);// 16 字节对齐申请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;}分配一块内存池后逻辑结构如下:

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

文章插图
 
大块内存申请大块内存是通过 large 连接的,并且都属于 ngx_create_pool 返回的 ngx_pool_t 结构 。malloc 分配的内存由一个 ngx_pool_large_t 节点来挂载,而这个 ngx_pool_large_t 节点又是从小块内存池中分配的 。
  • 为避免large链表长度过大导致在遍历寻找空闲挂载节点耗时过长,限制了遍历的节点为3,如果没有满足要求则直接分配
  • 头插法 插入至large链表中,新的节点后面也是最先被访问
static void *ngx_palloc_large(ngx_pool_t *pool, size_t size){void*p;ngx_uint_tn;ngx_pool_large_t*large;p = ngx_alloc(size, pool->log);// 调用 mallocif (p == NULL) {return NULL;}n = 0;for (large = pool->large; large; large = large->next) {// 从large 中链表中找到 alloc 为 NULL 的节点,将分配的内存挂在该节点上if (large->alloc == NULL) {large->alloc = p;return p;}if (n++ > 3) {// 为了避免过多的遍历,限制次数为 0break;}}// 当遍历的 ngx_pool_large_t 节点中 alloc 都有指向的内存时,从小块内存中分配一个 ngx_pool_large_t 节点用于挂载新分配的大内存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 内存池似懂非懂?一文带你看清高性能服务器内存池

文章插图
 
完整内存池结构逻辑
  • 所有的内存池结构都通过 d.next 连接
  • 前两个内存池结构的 current 都指向第三个内存池结构
  • 所有的 ngx_pool_large_t 节点都是从小内存池中分配的
  • 所有的 ngx_pool_large_t 节点都是连接在首个内存池结构上的
  • ngx_pool_large_t 节点的 alloc 被释放但 ngx_pool_large_t 节点不回收

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

文章插图
 
总结ngx_pool_t 内存分配方面
  • 通过 current 和 d.next 来访问其他的内存池结构
  • 插入方式
    • 小块内存池通过尾插法插入至内存池链表的尾端
    • 大块内存通过头插法插入至large链表的首部
  • 限制次数
    • 小内存分配失败(failed)次数大于4次后就不再作为分配内存的池子了
    • 大内存只寻找 large 链表中前三节点是否可以挂载新分配的内存
  • 内存对齐,多处内存对齐减少内存跨 cache 的数量
其实总体而言这是一个比较简单的内存池了,还是有一些内存浪费的地方,限制次数 可以说明这个情况,不过这也是在简单、高效和内存分配上的一个平衡了




推荐阅读