nginx的記憶體池設計
阿新 • • 發佈:2019-01-05
記憶體池結構體
struct ngx_pool_s { ngx_pool_data_t d; /* 記憶體池連結串列頭指標 */ size_t max; /* 一個閾值,決定是用malloc分配記憶體還是從記憶體池分配 */ ngx_pool_t *current; /* 這個指標指向第一個可以分配小塊記憶體的節點 */ ngx_chain_t *chain; ngx_pool_large_t *large; /* 大於max用malloc分配記憶體,並生成ngx_pool_large_t結構體,大塊記憶體連結串列頭指標 */ ngx_pool_cleanup_t *cleanup; /* 需要釋放的資源,fd,檔案...,這也是個連結串列 */ ngx_log_t *log; /* 日誌 */ };
nginx的記憶體池在分配空間時有兩種方式,一種是從記憶體池分配,另外一種用malloc分配,
分配方式的選擇由預分配位元組大小來決定,如果預分配位元組大於max,就會使用malloc分配。
整個記憶體池就是一個單鏈表,其通過ngx_pool_data_t這個結構體鏈起來的,對於這個單鏈
表,每個節點都是個記憶體池,記憶體池大小一樣,但是隻有第一個記憶體池具有完整的ngx_pool_s
結構體結構,其他記憶體池的只有一個ngx_pool_data_t結構體:typedef struct { u_char *last; /* 記憶體池中當前未分配區域的首指標 */ u_char *end; /* 記憶體池尾指標 */ ngx_pool_t *next; /* 下一個ngx_pool_t */ ngx_uint_t failed; /* 分配的失敗次數,即記憶體池剩餘位元組不足分配,這個與ngx_pool_s的current結構體有關 */ } ngx_pool_data_t;
記憶體池通過此結構體的next指標連結起來形成單鏈表
建立記憶體池的函式:/* 建立記憶體池 至少分配的記憶體要大於sizeof(ngx_pool_t) 分配的記憶體會掛載ngx_pool_data_t中 這個就是記憶體池連結串列的頭結點,分配的記憶體的頭部sizeof(ngx_pool_t)位元組將會儲存 */ 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); /* 分配的記憶體要和NGX_POOL_ALIGNMENT對齊 */ 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); 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; }
再來看從記憶體池分配size(size <= max)位元組的函式
/* 按照align分配的記憶體是否和匯流排長度對齊 */ 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; /* 從current指標來遍歷連結串列,而不是遍歷整個連結串列 */ 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); /* 所有ngx_pool_data_t的剩餘空間都小與size了 */ return ngx_palloc_block(pool, size); } /* 處理記憶體池連結串列上面所有節點的剩餘空間全部不足size位元組 */ 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); /* 記憶體池加上sizeof(ngx_pool_t)的總位元組大小 因為第一個ngx_pool_t的d.end指標指向記憶體池尾, 第一個ngx_pool_t的地址是記憶體池首 */ /* 重新分配一個記憶體池 */ m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); 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); /* 不需要是sizeof(ngx_pool_t),因為其他成員只需要在第一塊記憶體池的中儲存一份 */ m = ngx_align_ptr(m, NGX_ALIGNMENT); new->d.last = m + size; /* 更新整個記憶體池連結串列的節點的分配失敗次數 */ for (p = pool->current; p->d.next; p = p->d.next) { /* 一旦記憶體池分配小塊記憶體失敗四次,currrent就會移到下一個記憶體池節點 */ if (p->d.failed++ > 4) { pool->current = p->d.next; } } /* 將新分配的記憶體池節點掛到連結串列尾 */ p->d.next = new; return m; }
大塊記憶體分配
大塊記憶體結構體 struct ngx_pool_large_s { ngx_pool_large_t *next; /* 連結串列下一節點 */ void *alloc; /* 指向分配的大塊記憶體 */ };
在分配大塊記憶體的時候,sizeof(ngx_pool_large_s)將會從記憶體池分配,alloc將會指向malloc分配的記憶體,
由於大塊記憶體一般較大,nginx提供提前釋放大塊記憶體的函式,對於釋放大塊記憶體時,僅僅釋放alloc指標指向的
記憶體,並將alloc置為NULL,在分配大塊記憶體時,分配的記憶體塊將會先遍歷大塊記憶體連結串列,找到是否有提前釋放的
節點,如果有,就把分配的記憶體掛到此節點上,即alloc指標上。
接下來看分配大塊記憶體的函式:
/* 分配大塊記憶體,返回其alloc指標 */
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
/* 大塊記憶體直接使用malloc來分配 */
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n = 0;
/* 大塊記憶體將掛載到大塊記憶體連結串列上
首先找到有沒有ngx_pool_large_t的alloc的記憶體已經釋放 */
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
/* 最多隻會遍歷三個節點,因為前面的是最早分配的,也有可能最早釋放,如果前面三個節點都
沒有釋放,在遍歷就有可能白費功夫 */
if (n++ > 3) {
break;
}
}
/* 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是一個單鏈表 */ 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) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "run cleanup: %p", c); c->handler(c->data); } } /* 釋放大塊記憶體,僅僅釋放alloc指標指向的記憶體,並不會釋放ngx_pool_large_t結構體 ngx_pool_large_t的釋放由下面迴圈來釋放 */ for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } } /* 釋放ngx_pool_t結構體,這個首指標將指向分配的小塊記憶體,因此這裡會釋放整個ngx_pool_t,也就是整個小的記憶體池 ngx_pool_large_t的結構體佔用的空間是從小記憶體池中分配的 */ for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { ngx_free(p); if (n == NULL) { break; } } } /* 重置pool 大塊記憶體釋放,小塊記憶體不釋放,但是記憶體中的資料將忽略 */ void ngx_reset_pool(ngx_pool_t *pool) { ngx_pool_t *p; ngx_pool_large_t *l; /* 釋放大塊記憶體 */ for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } } /* 小塊記憶體將會重置,重置的過程中ngx_pool_large_t也會"釋放" */ for (p = pool; p; p = p->d.next) { p->d.last = (u_char *) p + sizeof(ngx_pool_t); p->d.failed = 0; } pool->current = pool; pool->chain = NULL; pool->large = NULL; } /* 提前釋放ngx_pool_large_t的alloc記憶體 */ ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) { ngx_pool_large_t *l; for (l = pool->large; l; l = l->next) { if (p == l->alloc) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); ngx_free(l->alloc); l->alloc = NULL; return NGX_OK; } } return NGX_DECLINED; }