nginx 內存池分析

nginx 內存池 ngx_pool_t

nginx 是本身實現了內存池的,因此在nginx ngx_pool_t 這個結構也隨處可見,這裏主要分析一下內存池的分配邏輯。html

內存池實現了包括小塊內存、大塊內存和清理資源幾種資源的處理,應該來講覆蓋了絕大數的使用場景了。nginx

相關結構定義

// 大塊內存
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;

// 內存池管理結構
typedef struct ngx_pool_s            ngx_pool_t;
struct ngx_pool_s {
    ngx_pool_data_t       d;            // 小塊內存池
    size_t                max;          // 小塊內存最大的分配內存,評估大內存仍是小塊內存
    ngx_pool_t           *current;      // 當前開始分配的小塊內存池
    ngx_chain_t          *chain;        // chain
    ngx_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 在內存池結構的起始處,能夠進行類型轉換訪問到不一樣的成員。ui

實現

內存對齊

#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 時,不會從小塊內存中進行分配。code

ngx_uint_t  ngx_pagesize = getpagesize();  // Linux 上是 4096
#define NGX_POOL_ALIGNMENT 16
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)  // 4095

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

內存池建立後的結構邏輯如圖所示:
htm

內存申請

申請的內存塊以 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);
    }
#endif

    return ngx_palloc_large(pool, size);
}

小塊內存申請

current 指向每次申請內存時開始檢索分配的小塊內存池,而 ngx_palloc_small 的參數 pool 在內存池沒有回收時,是固定不變的。blog

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); // 沒有知足分配的內存池,再申請一個小塊內存池
}

當在小塊內存池中找到了合適的內存後的結構以下:
內存

當沒有小塊內存池知足申請時,會再申請一個小塊內存池來知足分配,在設置完 last 和 end 兩個內存指示器後,對從 current 開始的內存池成員 failed 進行自增操做,而且當這個內存池的 failed 分配次數大於 4 時,表面這個內存分配失敗的次數太多,根據經驗應該下一次分配可能仍是失敗,因此直接跳過這個內存池,移動 current。資源

新的內存塊插入至內存池鏈表的尾端。

#define NGX_ALIGNMENT   sizeof(unsigned long)  // 8

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

分配一塊內存池後邏輯結構以下:

大塊內存申請

大塊內存是經過 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_t         n;
    ngx_pool_large_t  *large;

    p = ngx_alloc(size, pool->log);  // 調用 malloc
    if (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) {  // 爲了不過多的遍歷,限制次數爲 0
            break;
        }
    }

    // 當遍歷的 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;
}

第一次大塊內存分配後的結構以下:

完整內存池結構邏輯

  • 全部的內存池結構都經過 d.next 鏈接
  • 前兩個內存池結構的 current 都指向第三個內存池結構
  • 全部的 ngx_pool_large_t 節點都是從小內存池中分配的
  • 全部的 ngx_pool_large_t 節點都是鏈接在首個內存池結構上的
  • ngx_pool_large_t 節點的 alloc 被釋放但 ngx_pool_large_t 節點不回收

總結

ngx_pool_t 內存分配方面

  • 經過 current 和 d.next 來訪問其餘的內存池結構
  • 插入方式
    • 小塊內存池經過尾插法插入至內存池鏈表的尾端
    • 大塊內存經過頭插法插入至large鏈表的首部
  • 限制次數
    • 小內存分配失敗(failed)次數大於4次後就再也不做爲分配內存的池子了
    • 大內存只尋找 large 鏈表中前三節點是否能夠掛載新分配的內存
  • 內存對齊,多處內存對齊減小內存跨 cache 的數量

其實整體而言這是一個比較簡單的內存池了,仍是有一些內存浪費的地方,限制次數 能夠說明這個狀況,不過這也是在簡單、高效和內存分配上的一個平衡了

相關文章
相關標籤/搜索