### 概述
? ? ? Nginx 使用內存池對內存進行管理,內存管理的實現類似于前面文章介紹的《[STL源碼剖析——空間配置器](http://blog.csdn.net/chenhanzhun/article/details/39153797#t7)》,把內存分配歸結為**大內存分配**和**小內存分配**。若申請的內存大小比同頁的內存池最大值 max 還大,則是大內存分配,否則為小內存分配。
1. 大塊內存的分配請求不會直接在內存池上分配內存來滿足請求,而是直接向系統申請一塊內存(就像直接使用 malloc 分配內存一樣),然后將這塊內存掛到內存池頭部的 large 字段下。
1. 小塊內存分配,則是從已有的內存池數據區中分配出一部分內存。
Nginx 內存管理相關文件:
1. ?src/os/unix/ngx_alloc.h/.c
- 內存相關的操作,封裝了最基本的內存分配函數。
- 如 free / malloc / memalign / posix_memalign,分別被封裝為 ngx_free,ngx_alloc / ngx_calloc, ngx_memalign
- ngx_alloc:封裝malloc分配內存
- ngx_calloc:封裝malloc分配內存,并初始化空間內容為0
- ngx_memalign:返回基于一個指定 alignment 的大小為 size 的內存空間,且其地址為 alignment 的整數倍,alignment 為2的冪。
1. ?src/core/ngx_palloc.h/.c
- 封裝創建/銷毀內存池,從內存池分配空間等函數。
Nginx 內存分配總流圖如下:**其中 size 是用戶請求分配內存的大小,pool是現有內存池。**

### 內存池基本結構
Nginx 內存池基本結構定義如下:
~~~
/* 內存池結構 */
/* 文件 core/ngx_palloc.h */
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;/* 大塊內存鏈表,即分配空間超過 max 的內存 */
ngx_pool_cleanup_t *cleanup;/* 析構函數,釋放內存池 */
ngx_log_t *log;/* 內存分配相關的日志信息 */
};
/* 文件 core/ngx_core.h */
typedef struct ngx_pool_s ngx_pool_t;
typedef struct ngx_chain_s ngx_chain_t;
~~~
大塊內存分配的數據結構如下:
~~~
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s{
ngx_pool_large_t *next; //指向下一塊大塊內存
void *alloc; //指向分配的大塊內存
};
~~~
其他數據結構如下:
~~~
typedef void (*ngx_pool_cleanup_pt)(void *data); //cleanup的callback類型
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s{
ngx_pool_cleanup_pt handler;
void *data; //指向要清除的數據
ngx_pool_cleanup_t *next; //下一個cleanup callback
};
typedef struct {
ngx_fd_t fd;
u_char *name;
ngx_log_t *log;
} ngx_pool_cleanup_file_t;
~~~
內存池基本機構之間的關系如下圖所示:

**ngx_pool_t 的邏輯結構**
上面數據結構之間邏輯結構圖如下:**該圖是采用 UML 畫的,第一行黑色粗體表示對應數據結構,第二行是結構內的成員,冒號左邊是變量,冒號右邊是變量的類型;**

### 內存池的操作
### **創建內存池**
~~~
/* 創建內存池,該函數定義于 src/core/ngx_palloc.c 文件中 */
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p; /* 執行內存池頭部 */
/* 分配大小為 size 的內存 */
/* ngx_memalign 函數實現于 src/os/unix/ngx_alloc.c 文件中 */
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}
/* 以下是初始化 ngx_pool_t 結構信息 */
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); /* 可供分配的空間大小 */
/* 不能超過最大的限定值 4096B */
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;
}
~~~
其中內存分配函數 ngx_memalign 定義如下:
~~~
void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
void *p;
int err;
err = posix_memalign(&p, alignment, size);
//該函數分配以alignment為對齊的size字節的內存大小,其中p指向分配的內存塊。
if (err) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"posix_memalign(%uz, %uz) failed", alignment, size);
p = NULL;
}
ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
"posix_memalign: %p:%uz @%uz", p, size, alignment);
return p;
}
//函數分配以NGX_POOL_ALIGNMENT字節對齊的size字節的內存,在src/core/ngx_palloc.h文件中:
#define NGX_POOL_ALIGNMENT 16
~~~
### 銷毀內存池
? ? ? 銷毀內存池由 ?void ngx_destroy_pool(ngx_pool_t *pool)?函數完成。該函數將遍歷內存池鏈表,釋放所有內存,如果注冊了clenup (也是一個鏈表結構),亦將遍歷該 cleanup 鏈表結構依次調用 clenup 的 handler 清理。同時,還將遍歷 large 鏈表,釋放大塊內存。
~~~
/* 銷毀內存池 */
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
/* 若注冊了cleanup,則遍歷該鏈表結構,依次調用handler函數清理數據 */
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);
}
}
/* 遍歷 large 鏈表,釋放大塊內存 */
for (l = pool->large; l; l = l->next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
if (l->alloc) {
ngx_free(l->alloc); /* 釋放內存 */
}
}
/* 在debug模式下執行 if 和 endif 之間的代碼;
* 主要是用于log記錄,跟蹤函數銷毀時日志信息
*/
#if (NGX_DEBUG)
/*
* we could allocate the pool->log from this pool
* so we cannot use this log while free()ing the pool
*/
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p, unused: %uz", p, p->d.end - p->d.last);
if (n == NULL) {
break;
}
}
#endif
/* 遍歷所有分配的內存池,釋放內存池結構 */
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}
~~~
### 重置內存池
? ? ? 重置內存池由 ?void ngx_reset_pool(ngx_pool_t *pool)?函數完成。該函數將釋放所有 large 內存,并且將 d->last 指針重新指向 ngx_pool_t 結構之后數據區的開始位置,使內存池恢復到剛創建時的位置。由于內存池剛被創建初始化時是不包含大塊內存的,所以必須釋放大塊內存。
~~~
/* 重置內存池
* 定義于 src/core/ngx_palloc.c 文件中
*/
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);
}
}
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;
}
~~~
### 內存分配
### 小塊內存分配
小塊內存分配,即請求分配空間 size 小于內存池最大內存值 max。小內存分配的接口函數如下所示:
~~~
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
~~~
? ? ? ngx_palloc 和 ngx_pnalloc 都是從內存池里分配 size 大小內存。他們的不同之處在于,palloc 取得的內存是對齊的,pnalloc 則不考慮內存對齊問題。ngx_pcalloc 是直接調用 palloc 分配內存,然后進行一次 0 初始化操作。ngx_pmemalign 將在分配 size 大小的內存并按 alignment 對齊,然后掛到 large 字段下,當做大塊內存處理。
? ? ? ngx_palloc的過程一般為,首先判斷待分配的內存是否大于 pool->max,如果大于則使用 ngx_palloc_large 在 large 鏈表里分配一段內存并返回, 如果小于測嘗試從鏈表的 pool->current 開始遍歷鏈表,嘗試找出一個可以分配的內存,當鏈表里的任何一個節點都無法分配內存的時候,就調用 ngx_palloc_block 生成鏈表里一個新的節點, 并在新的節點里分配內存并返回, 同時, 還會將pool->current 指針指向新的位置(從鏈表里面pool->d.failed小于等于4的節點里找出) 。
~~~
/* 分配內存 */
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
/* 若請求的內存大小size小于內存池最大內存值max,
* 則進行小內存分配,從current開始遍歷pool鏈表
*/
if (size <= pool->max) {
p = pool->current;
do {
/* 執行對齊操作 */
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
/* 檢查現有內存池是否有足夠的內存空間,
* 若有足夠的內存空間,則移動last指針位置,
* 并返回所分配的內存地址的起始地址
*/
if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size; /* 在該節點指向的內存塊中分配size大小的內存 */
return m;
}
/* 若不滿足,則查找下一個內存池 */
p = p->d.next;
} while (p);
/* 若遍歷所有現有內存池鏈表都沒有可用的內存空間,
* 則分配一個新的內存池,并將該內存池連接到現有內存池鏈表中
* 同時,返回分配內存的起始地址
*/
return ngx_palloc_block(pool, size);
}
/* 若所請求的內存大小size大于max則調用大塊內存分配函數 */
return ngx_palloc_large(pool, size);
}
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new, *current;
/* 計算pool的大小,即需要分配新的block的大小 */
psize = (size_t) (pool->d.end - (u_char *) pool);
/* NGX_POOL_ALIGNMENT對齊操作 */
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}
/* 計算需要分配的block的大小 */
new = (ngx_pool_t *) m;
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
/* 初始化新的內存池 */
/* 讓m指向該塊內存ngx_pool_data_t結構體之后數據區起始位置 */
m += sizeof(ngx_pool_data_t);
/* 在數據區分配size大小的內存并設置last指針 */
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
current = pool->current;
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
/* 失敗4次以上移動current指針 */
current = p->d.next;
}
}
/* 將分配的block連接到現有的內存池 */
p->d.next = new;
/* 如果是第一次為內存池分配block,這current將指向新分配的block */
pool->current = current ? current : new;
return m;
}
~~~
~~~
/* 直接調用palloc函數,再進行一次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;
}
/* 按照alignment對齊分配size內存,然后將其掛到large字段,當做大塊內存處理 */
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
void *p;
ngx_pool_large_t *large;
p = ngx_memalign(alignment, size, pool->log);
if (p == NULL) {
return NULL;
}
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
~~~
小內存分配之后如下圖所示:

? ? ? 上圖是由3個小內存池組成的內存池模型,由于第一個內存池上剩余的內存不夠分配了,于是就創建了第二個新的內存池,第三個內存池是由于前面兩個內存池的剩余部分都不夠分配,所以創建了第三個內存池來滿足用戶的需求。由圖可見:所有的小內存池是由一個單向鏈表維護在一起的。這里還有兩個字段需要關注,failed和current字段。failed表示的是當前這個內存池的剩余可用內存不能滿足用戶分配請求的次數,即是說:一個分配請求到來后,在這個內存池上分配不到想要的內存,那么就failed就會增加1;這個分配請求將會遞交給下一個內存池去處理,如果下一個內存池也不能滿足,那么它的failed也會加1,然后將請求繼續往下傳遞,直到滿足請求為止(如果沒有現成的內存池來滿足,會再創建一個新的內存池)。current字段會隨著failed的增加而發生改變,如果current指向的內存池的failed達到了4的話,current就指向下一個內存池了。
### 大塊內存分配
~~~
/* 分配大塊內存 */
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;
/* 若在該pool之前已經分配了large字段,
* 則將所分配的大塊內存掛載到內存池的large字段中
*/
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ > 3) {
break;
}
}
/* 若在該pool之前并未分配large字段,
* 則執行分配ngx_pool_large_t 結構體,分配large字段內存,
* 再將大塊內存掛載到pool的large字段中
*/
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
void *
ngx_alloc(size_t size, ngx_log_t *log)
{
void *p;
p = malloc(size);
if (p == NULL) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"malloc() %uz bytes failed", size);
}
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
return p;
}
/* 釋放大塊內存 */
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;
}
~~~
大塊內存申請之后如下所示:

### cleanup 資源
~~~
/* 注冊cleanup;
* size 是 data 字段所指向的資源的大小;
*/
ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
/* 對內存池進行文件清理操作,即執行handler,此時handler==ngx_pool_cleanup_file */
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
/* 關閉data指定的文件句柄 */
void ngx_pool_cleanup_file(void *data);
/* 刪除data指定的文件 */
void ngx_pool_delete_file(void *data);
/* 注冊cleanup */
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t *c;
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = NULL;
c->next = p->cleanup;
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
/* 清理內存池的文件 */
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
ngx_pool_cleanup_t *c;
ngx_pool_cleanup_file_t *cf;
/* 遍歷cleanup結構鏈表,并執行handler */
for (c = p->cleanup; c; c = c->next) {
if (c->handler == ngx_pool_cleanup_file) {
cf = c->data;
if (cf->fd == fd) {
c->handler(cf);
c->handler = NULL;
return;
}
}
}
}
/* 關閉data指定的文件句柄 */
void
ngx_pool_cleanup_file(void *data)
{
ngx_pool_cleanup_file_t *c = data; /* 指向data所指向的文件句柄 */
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
c->fd);
/* 關閉指定文件 */
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", c->name);
}
}
/* 刪除data所指向的文件 */
void
ngx_pool_delete_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_err_t err;
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
c->fd, c->name);
/* 刪除data所指向的文件 */
if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
err = ngx_errno;
if (err != NGX_ENOENT) {
ngx_log_error(NGX_LOG_CRIT, c->log, err,
ngx_delete_file_n " \"%s\" failed", c->name);
}
}
/* 關閉文件句柄 */
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", c->name);
}
}
~~~
參考資料:
《 [Nginx源碼剖析之內存池,與內存管理](http://blog.csdn.net/v_july_v/article/details/7040425)》
《[nginx源碼分析—內存池結構ngx_pool_t及內存管理](http://blog.csdn.net/livelylittlefish/article/details/6586946)》
《[Nginx內存池實現源碼分析](http://blog.chinaunix.net/uid-24830931-id-3764858.html) 》
《[Nginx源碼分析-內存池](http://www.alidata.org/archives/1390)》
《[Ningx代碼研究](https://code.google.com/p/nginxsrp/wiki/NginxCodeReview)》
- 前言
- Nginx 配置文件
- Nginx 內存池管理
- Nginx 基本數據結構
- Nginx 數組結構 ngx_array_t
- Nginx 鏈表結構 ngx_list_t
- Nginx 隊列雙向鏈表結構 ngx_queue_t
- Nginx 哈希表結構 ngx_hash_t
- Nginx 紅黑樹結構 ngx_rbtree_t
- Nginx 模塊開發
- Nginx 啟動初始化過程
- Nginx 配置解析
- Nginx 中的 upstream 與 subrequest 機制
- Nginx 源碼結構分析
- Nginx 事件模塊
- Nginx 的 epoll 事件驅動模塊
- Nginx 定時器事件
- Nginx 事件驅動模塊連接處理
- Nginx 中 HTTP 模塊初始化
- Nginx 中處理 HTTP 請求
- Nginx 中 upstream 機制的實現
- Nginx 中 upstream 機制的負載均衡