1. 程式人生 > >nginx原始碼剖析--記憶體池

nginx原始碼剖析--記憶體池

作者:July、dreamice、阿波、yixiao。
出處:http://blog.csdn.net/v_JULY_v/

引言    

    Nginx(發音同 engine x)是一款輕量級的Web 伺服器/反向代理伺服器及電子郵件(IMAP/POP3)代理伺服器,並在一個BSD-like 協議下發行。由俄羅斯的程式設計師Igor Sysoev所開發,最初供俄國大型的入口網站及搜尋引擎Rambler(俄文:Рамблер)使用。  

    其特點是佔有記憶體少,併發能力強,事實上nginx的併發能力確實在同類型的網頁伺服器中表現較好,目前中國大陸使用nginx網站使用者有:新浪、網易、 騰訊,另外知名的微網誌Plurk也使用nginx,以及諸多暫不曾得知的玩意兒。

    讀者可以到此處下載Nginx最新版本的原始碼:http://nginx.org/en/download.html。同時,本文字不想給原始碼太多註釋,因為這不像講解演算法,演算法講解的越通俗易懂越好,而原始碼剖析則不同,緣由在於不同的讀者對同一份原始碼有著不同的理解,或深或淺,所以,更多的是靠讀者自己去思考與領悟。

    ok,本文之中有任何疏漏或不正之處,懇請批評指正。謝謝。

Nginx原始碼剖析之記憶體池

1、記憶體池結構

    記憶體相關的操作主要在檔案 os/unix/ngx_alloc.{h,c} 和 core/ngx_palloc.{h,c} 中實現,ok,咱們先來看記憶體管理中幾個主要的資料結構:

  1. typedefstruct {    //記憶體池的資料結構模組
  2.     u_char               *last;    //當前記憶體分配結束位置,即下一段可分配記憶體的起始位置
  3.     u_char               *end;     //記憶體池的結束位置
  4.     ngx_pool_t           *next;    //連結到下一個記憶體池,記憶體池的很多塊記憶體就是通過該指標連成連結串列的
  5.     ngx_uint_t            failed;  //記錄記憶體分配不能滿足需求的失敗次數
  6. } ngx_pool_data_t;   //結構用來維護記憶體池的資料塊,供使用者分配之用。
  1. struct ngx_pool_t {  //記憶體池的管理分配模組
  2.     ngx_pool_data_t       d;         //記憶體池的資料塊(上面已有描述),設為d
  3.     size_t                max;       //資料塊大小,小塊記憶體的最大值
  4.     ngx_pool_t           *current;   //指向當前或本記憶體池
  5.     ngx_chain_t          *chain;     //該指標掛接一個ngx_chain_t結構
  6.     ngx_pool_large_t     *large;     //指向大塊記憶體分配,nginx中,大塊記憶體分配直接採用標準系統介面malloc
  7.     ngx_pool_cleanup_t   *cleanup;   //解構函式,掛載記憶體釋放時需要清理資源的一些必要操作
  8.     ngx_log_t            *log;       //記憶體分配相關的日誌記錄
  9. };  
再來看看大塊資料分配的結構體:
  1. struct ngx_pool_large_t {  
  2.     ngx_pool_large_t     *next;  
  3.     void                 *alloc;  
  4. };  
其它的資料結構與相關定義:
  1. typedefstruct {  
  2.     ngx_fd_t              fd;  
  3.     u_char               *name;  
  4.     ngx_log_t            *log;  
  5. } ngx_pool_cleanup_file_t;  
  1. #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)  //在x86體系結構下,該值一般為4096B,即4K

上述這些資料結構的邏輯結構圖如下(下圖最左上角部分沒有與上文的第一個資料結構內的ngx_uint_t對應起來,特此說明):


1.1、ngx_pool_t的邏輯結構

    再看一下用UML繪製的ngx_pool_t的邏輯結構圖:

    在下一節,我們將會深入分析記憶體管理的主要函式。

Nginx原始碼剖析之記憶體管理

2、記憶體池操作

2.1、建立記憶體池

  1. ngx_pool_t *  
  2. ngx_create_pool(size_t size, ngx_log_t *log)  
  3. {  
  4.     ngx_pool_t  *p;  
  5.     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  
  6.     //ngx_memalign()函式執行記憶體分配,該函式的實現在src/os/unix/ngx_alloc.c檔案中(假定NGX_HAVE_POSIX_MEMALIGN被定義):
  7.     if (p == NULL) {  
  8.         return NULL;  
  9.     }  
  10.     p->d.last = (u_char *) p + sizeof(ngx_pool_t);  
  11.     p->d.end = (u_char *) p + size;  
  12.     p->d.next = NULL;  
  13.     p->d.failed = 0;  
  14.     size = size - sizeof(ngx_pool_t);  
  15.     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;   
  16.     //最大不超過4095B,別忘了上面NGX_MAX_ALLOC_FROM_POOL的定義
  17.     p->current = p;  
  18.     p->chain = NULL;  
  19.     p->large = NULL;  
  20.     p->cleanup = NULL;  
  21.     p->log = log;  
  22.     return p;  
  23. }  

    例如,呼叫ngx_create_pool(1024, 0x80d1c4c)後,建立的記憶體池物理結構如下圖:

    緊接著,咱們就來分析下上面程式碼中所提到的:ngx_memalign()函式。
  1. void *  
  2. ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)  
  3. {  
  4.     void  *p;  
  5.     int    err;  
  6.     err = posix_memalign(&p, alignment, size);  
  7.     //該函式分配以alignment為對齊的size位元組的記憶體大小,其中p指向分配的記憶體塊。
  8.     if (err) {  
  9.         ngx_log_error(NGX_LOG_EMERG, log, err,  
  10.             "posix_memalign(%uz, %uz) failed", alignment, size);  
  11.         p = NULL;  
  12.     }  
  13.     ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,  
  14.         "posix_memalign: %p:%uz @%uz", p, size, alignment);  
  15.     return p;  
  16. }  
  17. //從這個函式的實現體,我們可以看到p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
  18. //函式分配以NGX_POOL_ALIGNMENT位元組對齊的size位元組的記憶體,在src/core/ngx_palloc.h檔案中:
  1. #define NGX_POOL_ALIGNMENT       16

    因此,nginx的記憶體池分配,是以16位元組為邊界對齊的。

2.1、銷燬記憶體池
    接下來,咱們來看記憶體池的銷燬函式,pool指向需要銷燬的記憶體池
  1. void
  2. ngx_destroy_pool(ngx_pool_t *pool)  
  3. {  
  4.     ngx_pool_t          *p, *n;  
  5.     ngx_pool_large_t    *l;  
  6.     ngx_pool_cleanup_t  *c;  
  7.     for (c = pool->cleanup; c; c = c->next) {  
  8.         if (c->handler) {  
  9.             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,  
  10.                 "run cleanup: %p", c);  
  11.             c->handler(c->data);  
  12.         }  
  13.     }  
  14.     //前面講到,cleanup指向解構函式,用於執行相關的記憶體池銷燬之前的清理工作,如檔案的關閉等,
  15.     //清理函式是一個handler的函式指標掛載。因此,在這部分,對記憶體池中的解構函式遍歷呼叫。
  16.     for (l = pool->large; l; l = l->next) {  
  17.         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);  
  18.         if (l->alloc) {  
  19.             ngx_free(l->alloc);  
  20.         }  
  21.     }  
  22.     //這一部分用於清理大塊記憶體,ngx_free實際上就是標準的free函式,
  23.     //即大記憶體塊就是通過malloc和free操作進行管理的。
  24. #if (NGX_DEBUG)
  25.     /** 
  26.     * we could allocate the pool->log from this pool 
  27.     * so we can not use this log while the free()ing the pool 
  28.     */
  29.     for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {  
  30.         ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,  
  31.             "free: %p, unused: %uz", p, p->d.end - p->d.last);  
  32.         if (n == NULL) {  
  33.             break;  
  34.         }  
  35.     }  
  36.     //只有debug模式才會執行這個片段的程式碼,主要是log記錄,用以跟蹤函式銷燬時日誌記錄。
  37. #endif
  38.     for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {  
  39.         ngx_free(p);  
  40.         if (n == NULL) {  
  41.             break;  
  42.         }  
  43.     }  
  44. }  
  45. //該片段徹底銷燬記憶體池本身。
    該函式將遍歷記憶體池連結串列,所有釋放記憶體,如果註冊了clenup(也是一個連結串列結構),亦將遍歷該cleanup連結串列結構依次呼叫clenup的handler清理。同時,還將遍歷large連結串列,釋放大塊記憶體。 

2.3、重置記憶體池

void ngx_reset_pool(ngx_pool_t *pool)
    重置記憶體池,將記憶體池恢復到剛分配時的初始化狀態,注意記憶體池分配的初始狀態時,是不包含大塊記憶體的,因此初始狀態需要將使用的大塊記憶體釋放掉,並把記憶體池資料結構的各項指標恢復到初始狀態值。程式碼片段如下:
  1. void
  2. ngx_reset_pool(ngx_pool_t *pool)  
  3. {  
  4.     ngx_pool_t        *p;  
  5.     ngx_pool_large_t  *l;  
  6.     for (l = pool->large; l; l = l->next) {  
  7.         if (l->alloc) {  
  8.             ngx_free(l->alloc);  
  9.         }  
  10.     }  
  11.     //上述片段主要用於清理使用到的大塊記憶體。
  12.     pool->large = NULL;  
  13.     for (p = pool; p; p = p->d.next) {  
  14.         p->d.last = (u_char *) p + sizeof(ngx_pool_t);