nginx源码分析——内存池

1. 内存池概述

nginx的内存池本质上是一个内存链表。链表的每个节点由内存池头部结构体和内存块组成。内存池头部结构体负责连接内存链表的其余节点,同时负责本节点内存块的分配;内存块则是实际提供存储的区域。nginx

在内存分配的时候,实际上又区分为"大块内存"和"小块内存"的分配。所谓小块内存是指:所须要的内存空间大小小于或等于当前内存池链表节点内存块的最大空间。这个时候,经过直接移动内存块上的指针便可分配知足要求的内存空间;大块内存则是指所须要的内存空间超过当前内存池链表节点内存块的最大空间,对于这种状况,nginx首先会按照申请的大小,新开辟一块内存空间(这块新开辟的空间是返回给调用者使用的),而后在当前内存块节点上分配一块小的空间用于存储记录新开辟的空间,同时将全部"大块内存"区域链接起来,造成一个链表,以达到复用。函数

内存池链表在进程虚拟地址空间中的大概状况以下图所示:ui

2. 内存池相关结构体

内存池头部结构体spa

  • 大块内存结构体
typedef struct ngx_pool_large_s  ngx_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_t   failed;  // 内存池分配失败次数
} ngx_pool_data_t;

struct ngx_pool_s {
    ngx_pool_data_t  d;            // 指向内存池的数据块
    size_t           max;          // 内存池数据块的最大分配空间
    ngx_pool_t *     current;      // 指向当前内存池位置
    ngx_chain_t *    chain;        // 挂接的 ngx_chain_t
    ngx_pool_large_t * large;      // 大块内存链表头
    ngx_pool_cleanup_t * cleanup;  // 释放内存的回调函数
    ngx_log_t *          log;      // 日志记录信息
};

3. 内存池相关函数 

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 );
    if( p == NULL) {
        return NULL;
    }

    // last 指向对外分配内存的起始地址
    p->d.last = (u_char *)p + sizeof(ngx_pool_t);
    // end 指向本块内存的结束位置
    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;
}

2). 从内池中分配内存(接口)日志

// 对齐分配内存
void * ngx_palloc( ngx_pool_t * pool, size_t size )
{
    if( size <= pool->max ) {
        return ngx_palloc_small(pool, size, 1);
    }

    return ngx_palloc_large(pool, size);
}

// 非对齐分配内存
void * ngx_pnalloc( ngx_pool_t * pool, size_t size )
{
    if( size <= pool->max ) {
        return ngx_palloc_small(pool, size, 0);
    }

    return ngx_palloc_large(pool, size);
}

// 对齐分配内存, 并初始化为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;
}

// 以指定大小字节对齐, 本质上属于大块内存的分配
void * ngx_pmemalign( ngx_pool_t * pool, size_t size, size_t alignment )
{
    void * p;
    ngx_pool_large_t * large;

    // 建立大小为size的内存,并以alignment 字节对齐
    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;
}

3). 销毁内存池code

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) {
            c->handler(c->data);
        }
    }

    // 释放大块内存链表
    for( l = pool->large; l; l = l->next ) {
        if( l->alloc ) {
            ngx_free(l->alloc);
        }
    }

    // 释放内存池链表
    for( p = pool, n = pool->d->next; p = n, n = n->d.next ) {
        ngx_free(p);
        if( n == NULL ) {
            break;
        }
    }
}

4). 小块内存分配接口

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 );
}

5). 大块内存分配进程

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 ;
        }

        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;
}

4. nginx对内存池的使用

在有新链接创建时,nginx会为每一个链接建立一个内存池,其大小能够经过配置项(connection_pool_size)进行调整,而后在内存池中进行内存的分配;在链接断开时,销毁内存池。内存

针对http请求,每一个请求都会建立一个内存池,而后在该内存池中进行内存的分配。一样,该内存池的大小也是能够经过配置项(request_pool_size)进行配置的;在请求结束时,销毁内存池。