上一篇已經通過對 ngx_palloc 這個內存池(pool)管理的核心函數--內存分配函數進行解析,我們窺探到了Nginx內存管理的主體方法還有對於大內存需求的解決之道,同時也對管理內存池的數據結構有了更深一步的認識,通過這些認識我們可以得到以下這樣一張數據結構的示意圖:
圖3 Nginx內存管理數據結構示意圖
做說明下,這里示意的是有需求大內存分配時的結構示意圖,為了圖示的方便,我們將 large_t 特殊話到了和 large所在的同一個pool單元里面,其實實際運行中並非一定在同一個pool單元中。如果沒有大內存需求時 large_t 也並不存在。
分析完了,內存分配函數 ngx_palloc,可以說nginx內存分配機制的解析我們已經完成了一半,對於 ngx_palloc 的變種函數如:ngx_pcalloc、ngx_pnalloc等就不一一詳細說明了,和 ngx_palloc 結構類似 或者 只是添加了少許功能(比如,初始化內存數據為0等)。我們再來分析內存池的其他函數
1、創建函數 ngx_create_pool

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; } 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; }
此函數結構簡明,是對 ngx_pool_t 按照 size 的大小進行些必要的初始化,並返回一個指針。
2、內存池pool的注銷函數 ngx_destroy_pool
分析這個函數,首先得分析Nginx內存池的注銷機制。我知道在需求內存時很可能用到一些數據結構,注銷時需要特別的對它進行釋放,否則可能發生內存泄漏。對這樣的情況,Nginx內存管理中,采用了一個有特點的結構體來應對--ngx_pool_cleanup_t,對應的就是pool單元中的 cleanup。
struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next; };
一個看顯然就明白是個鏈表,不過,ngx_pool_cleanup_pt 是什么呢?請看下面這個定義
typedef void (*ngx_pool_cleanup_pt)(void *data);
很明白了,原來是一個函數指針。這一下問題就顯然解決了,在內存管理中遇到這樣的特殊的數據結構,只要編寫它相應的釋放函數,用函數指針加載到pool單元上,在注銷pool內存池時調用即可。特別說明下是,ngx_pool_cleanup_t 數據結構中的 data 存儲的是 handler 指向的函數可能需要用到的參數。看起來很有C++中類的析構函數的味道,可以有多個這樣的函數用 ngx_pool_cleanup_t 鏈表的形式保存。回到,Nginx內存管理的源代碼里,我們可以找到 ngx_pool_cleanup_add 這樣的一個函數,和預想的一樣,它就是分配釋放函數的內存,並返回地址。附上源碼:

ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) { ngx_pool_cleanup_t *c; /* 分配ngx_pool_cleanup_t 空間 */ c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t)); if (c == NULL) { return NULL; } /* 分配參數空間 值得注意以上兩次分配空間都是分配在POOL池中! */ 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; }
好了,理解了釋放特殊結構體后,我們再來看 ngx_destroy_pool 就比較簡單了。回顧內存池的建設過程,先建pool單元,如果有大內存需求再建large。那么注銷正好相反就是。首先處理 ngx_pool_cleanup_t 的特殊需求,如果有的話然后釋放分配large,最后釋放pool。其中如果配置了要寫的DEBUG日志的,就還對DEBUG日志進行記錄。附上源碼:
void ngx_destroy_pool(ngx_pool_t *pool) { ngx_pool_t *p, *n; ngx_pool_large_t *l; ngx_pool_cleanup_t *c; /*
處理 ngx_pool_cleanup_t 的特殊需求
*/ 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); } } #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 /*
釋放pool
*/ for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { ngx_free(p); if (n == NULL) { break; } } }
大家或許有點疑惑,內存真的沒有泄漏了么?ngx_pool_cleanup_t 所占的內存,釋放了么?答案當然是肯定的!如果有這樣的疑惑,請仔細看 ngx_pool_cleanup_add 函數他給 ngx_pool_cleanup_t 分配的內存空間也是pool單元中的內存( 同樣的 ngx_pool_large_t ),釋放pool的時候一並釋放出來。
分析到這里,Nginx內存管理機制源碼的分析就基本上結束了。感慨到Nginx內存池的設計確實精美,pool單元數據結構的設計(實用)、內存分配機制(大內存特殊處理,大內存信息的結構體也在內存池中)、內存釋放機制(經典的函數指針用法,很巧妙的防止內存泄漏),很多地方都可以借鑒!
在最后,我們再用一個小小的驗證程序來檢驗我們分析出來的成果。我們在 ngx_palloc 添加上一條 printf("call ngx_palloc\n") 其他函數也添加上類似的語句,編寫如下main代碼:
void fun(void *p) { printf("call fun\n"); } int ngx_cdecl main(int argc, char *const *argv) { ngx_pool_t *t; ngx_pool_cleanup_t *clt; void *p1,*p2,*p3; t=ngx_create_pool(512,NULL); /*創建pool*/ printf("\n"); p1=ngx_palloc(t,416); /*分配內存,在可分配內*/ printf("\n"); p2=ngx_palloc(t,216);/*分配內存,在可分配內,但pool中沒有足夠的內存空間*/ printf("\n"); p3=ngx_palloc(t,624);/*分配大內存*/ printf("\n"); clt=ngx_pool_cleanup_add(t, sizeof(void *));/*添加釋放特殊結構體的函數*/ clt->handler=&fun; ngx_destroy_pool(t);/*pool池注銷*/ printf("\n"); return 0; }
實驗結果,截圖如下:
圖4 Nginx內存管理機制實驗截圖
實驗結果簡單說明下:
call ngx_create_pool //創建了pool池
call ngx_palloc //分配內存,在可分配內
call ngx_palloc //分配內存,在可分配內,但pool中沒有足夠的內存空間
call ngx_palloc_block // 分配新的pool單元
call ngx_palloc //分配大內存
call ngx_palloc_large //調用大內存分配函數
call ngx_palloc //分配 ngx_pool_large_t 結構體空間
call ngx_pool_cleanup_add //添加釋放特殊結構體的函數
call ngx_palloc //分配ngx_pool_cleanup_t 的空間
call ngx_palloc //分配函數參數的空間
call ngx_destroy_pool //內存池pool池注銷
call fun //調用釋放特殊結構體的函數釋放
以上實驗結果,完全符合我們分析Nginx內存分配機制,Nginx內存池(pool)的分析到此處就結束了。經典的內存池管理機制!