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)進行配置的;在請求結束時,銷燬內存池。

相關文章
相關標籤/搜索