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 節點又是從小塊內存池中分配的。
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; }
第一次大塊內存分配後的結構以下:
ngx_pool_t 內存分配方面
其實整體而言這是一個比較簡單的內存池了,仍是有一些內存浪費的地方,限制次數
能夠說明這個狀況,不過這也是在簡單、高效和內存分配上的一個平衡了