Nginx 源碼分析-- 內存池(pool)的分析 三


  上一篇已經通過對 ngx_palloc 這個內存池(pool)管理的核心函數--內存分配函數進行解析,我們窺探到了Nginx內存管理的主體方法還有對於大內存需求的解決之道,同時也對管理內存池的數據結構有了更深一步的認識,通過這些認識我們可以得到以下這樣一張數據結構的示意圖:

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

View Code
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 這樣的一個函數,和預想的一樣,它就是分配釋放函數的內存,並返回地址。附上源碼:

View Code
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)的分析到此處就結束了。經典的內存池管理機制!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM