1. 程式人生 > >nginx原始碼學習(二) 記憶體池結構 ngx_pool_t

nginx原始碼學習(二) 記憶體池結構 ngx_pool_t

1,nginx的記憶體池介紹

    為了方便系統模組對記憶體的使用,方便記憶體的管理,nginx自己實現了程序池的機制來進行記憶體的分配和釋放, 首先nginx會在特定的生命週期幫你

   統一建立記憶體池,當需要進行記憶體分配的時候統一通過記憶體池中的記憶體進行分配,最後nginx會在適當的時候釋放記憶體池的資源,開發者只要在需要

   的時候對記憶體進行申請即可,不用過多考慮記憶體的釋放等問題,大大提高了開發的效率。

  例如對於記憶體的管理,如果我們需要使用記憶體,那麼總是從一個ngx_pool_t的物件中獲取記憶體,在最終的某個時刻,我們銷燬這個ngx_pool_t物件,

  所有這些記憶體都被釋放了。這樣我們就不必要對對這些記憶體進行malloc和free的操作,不用擔心是否某塊被malloc出來的記憶體沒有被釋放。因為當

   ngx_pool_t物件被銷燬的時候,所有從這個物件中分配出來的記憶體都會被統一釋放掉。

2,nginx記憶體管理相關結構

 下面介紹下 nginx 封裝的一些有關記憶體分配、管理相關函式,記憶體相關的操作主要在檔案 os/unix/ngx_alloc.{h,c} 和 core/ngx_palloc.{h,c} 中實現:

 A  ./src/os/unix/ngx_alloc.{h,c}中包括所有nginx記憶體申請的相關函式。

    ngx_alloc() 包裝了malloc(),僅添加了記憶體分配失敗時的,log輸出和debug時的log輸出。

    ngx_calloc() 呼叫上面的函式,成功分配後,將記憶體清零。

    ngx_memalign() 也是向作業系統申請記憶體,只不過採用記憶體對齊方式。是為了減少記憶體碎片。如果作業系統支援posix_memalign()就採用它,如果

    支援memalign()則用memalign()。在0.8.19版本中,作者不再使用ngx_alloc(),而全部改用ngx_memalign(),函式ngx_memalign()返回基於一個指

   定alignment的大小為size的記憶體 空間,且其地址為alignment的整數倍,alignment為2的冪。

 B,./src/core/ngx_palloc.{h,c}

  包含了記憶體管理的資料結構,封裝建立

/銷燬記憶體池,從記憶體池分配空間等函式。

  記憶體管理中幾個主要的資料結構:
  在檔案ngx_palloc.h定義的記憶體管理相關資料結構。

//該結構用來維護記憶體池的資料塊,供使用者分配之用
typedef struct {
    u_char               *last;  //當前記憶體分配結束位置,即下一段可分配記憶體的起始位置
    u_char               *end;   //記憶體池結束位置
    ngx_pool_t           *next;  //記憶體池裡面有很多塊記憶體,這些記憶體塊就是通過該指標連成連結串列的
    ngx_uint_t            failed;//統計該記憶體池不能滿足分配請求的次數,即分配失敗次數
} ngx_pool_data_t;//記憶體池的資料塊位置資訊 

ngx_pool_t結構

typedef struct ngx_pool_s        ngx_pool_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; //釋放記憶體池的callback   
 ngx_log_t            *log;	   //日誌資訊
};

其中,sizeof(ngx_pool_data_t)=16Bsizeof(ngx_pool_t)=40B

大塊記憶體結構:

//大記憶體結構
struct ngx_pool_large_s {
    ngx_pool_large_t     *next; //下一個大塊記憶體
    void                 *alloc;//指向分配的大塊記憶體
};

當待分配空間已經超過了池子自身大小,nginx也沒有別的好辦法,只好按照你需要分配的大小,實際去呼叫malloc()函式去分配,例如池子的大小是1K,

待分配的大小是1M。實際上池子裡只儲存了ngx_pool_large_t結構,這個結構中的alloc指標,指向被分配的記憶體,並把這個指標返回給系統使用。

上述這些資料結構的邏輯結構圖如下:

          

  :在nginx的main()函式中,通過將ngx_pagesize 設定為1024來指定記憶體 分配按1024bytes對齊。這意味著你雖指示分配10 bytes的記憶體,

  實際上nginx也向 作業系統申請至少1024bytes的記憶體。 nginx將幾乎所有的結構體放在ngx_core.h檔案中重新進行了申明,形式如:

  typedefstruct ngx_**_s      ngx_**_t;

3,nginx 記憶體池操作相關。

 A,記憶體池的建立銷燬
               

//建立記憶體池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;
   // ngx_memalign 返回值為void*,void*可以執指向任何型別的資料
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  // 記憶體分配,該函式的實現在src/os/unix/ngx_alloc.c檔案中,uinx,windows分開走
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t); //last指向ngx_pool_t結構體之後資料的起始
    p->d.end = (u_char *) p + size; //end指向分配的整個size大小的記憶體的末尾 
    p->d.next = NULL;
    p->d.failed = 0;
   //max中存放的數指所申請記憶體塊中空閒的大小,因此,在計算max之前先減去了管理結點本身的大小。	 
    size = size - sizeof(ngx_pool_t); 
	//最大不超過 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小,即4095B
    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的pool,log為後續在該pool上進行操作時輸出日誌的物件。 需要說明的是size的選擇,size的大小必須小於等於NGX_MAX_ALLOC_FROM_POOL 且必須大於sizeof(ngx_pool_t)。

選擇大於NGX_MAX_ALLOC_FROM_POOL的值會造成浪費,因為大於該限制的空間不會被用到(只是說在第一個由ngx_pool_t物件管理的記憶體

塊上的記憶體,後續的分配如果第一個記憶體塊上的空閒部分已用完,會再分配的)。選擇小於sizeof(ngx_pool_t)的值會造成程式崩潰。由於初始大

小的記憶體塊中要用一部分來儲存ngx_pool_t這個資訊本身。當一個ngx_pool_t 物件被建立以後,該物件的max欄位被賦值為size-sizeof(ngx_pool_t)

和NGX_MAX_ALLOC_FROM_POOL這兩者中比較小的。後續的從這個pool中分配的記憶體塊,在第一塊記憶體使用完成以後,如果要繼續分配的話,

就需要繼續從作業系統申請記憶體。當記憶體的大小小於等於max欄位的時候,則分配新的記憶體塊,連結在d這個欄位(實際上是d.next欄位)管理的一

條連結串列上。當要分配的記憶體塊比max大的,那麼從系統中申請的記憶體是被掛接在large欄位管理的一條連結串列上。我們暫且把這個稱之為大塊記憶體鏈

和小塊記憶體鏈。

總結起來,ngx_create_pool有如下三步:

第一步,呼叫ngx_memalign()申請記憶體;

第二步,設定ngx_pool_t中的成員d(即型別ngx_pool_data_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;
  d.last則指向未使用記憶體的開始處,而d.end指向記憶體塊的結尾處。剛申請的記憶體中佔用ngx_pool_t結構體作為管理單元。所以,此時d.last指向

  (u_char *) p + sizeof(ngx_pool_t)處。

第三步,設定ngx_pool_t 除d變數的其它成員;
    ...
    p->max =...
    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
     ...
在計算max時,max中存放的數指所申請記憶體塊中空閒記憶體的大小。因此,在計算max之前先減去了管理結點本身的大小。

 下面介紹 ngx_create_pool 裡分配記憶體的 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;  
}  

  從這個函式的實現體,我們可以看到p =ngx_memalign(NGX_POOL_ALIGNMENT, size, log);

  函式分配以NGX_POOL_ALIGNMENT位元組對齊的size位元組的記憶體,在src/core/ngx_palloc.h

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

  首先我們要理解為什麼要記憶體對齊?  為了移植性和 程式的效能,可以參考這篇文章

 看下記憶體池的銷燬函式,pool指向需要銷燬的記憶體池:

    void  
    ngx_destroy_pool(ngx_pool_t *pool)  
    {  
        ngx_pool_t          *p, *n;  
        ngx_pool_large_t    *l;  
        ngx_pool_cleanup_t  *c;  
        // 遍歷 cleanup連結串列結構依次呼叫clenup的handler清理
     //cleanup指向解構函式,用於執行相關的記憶體池銷燬之前的清理工作,如檔案的關閉等.
        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);  
            }  
        }  
        //清理大塊記憶體,ngx_free實際上就是標準的free函式
        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 can not use this log while the 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;  
            }  
        }  
        //只有debug模式才會執行這個片段的程式碼,主要是log記錄,用以跟蹤函式銷燬時日誌記錄。  
    #endif  
        //釋放小記憶體塊
        for (p = pool, n = pool->d.next; /** void */; p = n, n = n->d.next) {  
            ngx_free(p);  
              
            if (n == NULL) {  
                break;  
            }  
        }  
    }  

   該函式將遍歷記憶體池連結串列,釋放大塊記憶體(通過ngx_free),而沒有提供對小塊記憶體的釋放。如果註冊了clenup(也是一個連結串列結構),

   亦將遍歷該cleanup連結串列結構依次呼叫clenup的handler清理和該記憶體相關聯的的其它資源,也就是當我們從記憶體池中申請資源時,

   可能會附帶一些其它的資源(比如開啟的檔案),這些資源的使用和申請的記憶體是繫結在一起的,所以在進行資源釋放時,希望這些資

  源的釋放能夠和記憶體池釋放時一起進行(通過handler()回撥函式),避免了資源洩露和單獨執行釋放相關資源的麻煩。

總結起來 ngx_destroy_pool() 釋放記憶體池,一共分三步
第一步、在釋放前先對業務邏輯進行釋放前的處理
    for (c = pool->cleanup; c; c = c->next) {
          ...
    }
第二步、釋放large佔用的記憶體(大記憶體塊連結串列)
    for (l = pool->large; l; l = l->next) {
          ....
    }
第三步、釋放所有的池子(即小記憶體塊連結串列)
for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
       ...
  }

 B,記憶體申請和釋放

 下面簡單介紹有關記憶體申請函式。 

,void *ngx_palloc(ngx_pool_t *pool, size_t size); 程式碼如下:

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    ngx_pool_t  *p;

    if (size <= pool->max) {//判斷待分配記憶體與max值 
        p = pool->current; //小於max值,則從current節點開始遍歷pool連結串列 
        do {
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); // 對齊記憶體指標,加快存取速度
            if ((size_t) (p->d.end - m) >= size) { //找到合適記憶體塊
                p->d.last = m + size;//在該節點指向的記憶體塊中分配size大小的記憶體
                return m;
            }
			//如果不滿足,則查詢下一個節點
            p = p->d.next;
        } while (p);
		
		//連結串列裡沒有能分配size大小記憶體的節點,則生成一個新的節點並在其中分配記憶體  
        return ngx_palloc_block(pool, size);
    }
	//大於max值,則在large連結串列裡分配記憶體
    return ngx_palloc_large(pool, size);
}

  ngx_palloc(pool, size); 從這個pool中分配一塊為size大小的記憶體。注意,此函式分配的記憶體的起始地址按照NGX_ALIGNMENT進行了對齊。

關於記憶體位元組對齊,看下面的巨集定義:

#define ngx_align(d, a)     (((d) + (a - 1)) & ~(a - 1))

這個巨集使用來計算儲存d位元組的資料,如果按照a位元組對齊,需要多少位元組來儲存對齊的資料,其中a為2的指數冪。

比如 ngx_align(9,4)  則其結果為12,因為按4位元組對齊4的整數倍最小值 則為 12, 巨集 ngx_align_ptr 和ngx_align同樣的道理和作用。

對齊操作會提高系統處理的速度,上面已經解釋過記憶體位元組對齊的諸多好處,下面給出 ngx_palloc的流程圖。

                                

界定小記憶體塊和大記憶體塊的值是 min (size, NGX_MAX_ALLOC_FROM_POOL (4095))。

,void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

      從這個pool中分配一塊為size大小的記憶體。ngx_pnalloc() 函式與ngx_palloc()函式唯一不同之處,就是在計算申請記憶體的指標的方式未按32位對齊方式計算。

,static void * ngx_palloc_block(ngx_pool_t *pool, size_t size);

       待申請新的記憶體池節點小於等於max是呼叫這個函式。

       ngx_palloc_block() 函式用來分配新的記憶體池塊,形成一個連結串列。這個函式用static 修飾 可見其為內調函式,不會被外部呼叫。程式碼如下:

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new, *current;
    psize = (size_t) (pool->d.end - (u_char *) pool);//計算pool的大小
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配一塊與pool大小相同的記憶體
    if (m == NULL) {
        return NULL;
    }
    new = (ngx_pool_t *) m;
    new->d.end = m + psize;//設定end指標
    new->d.next = NULL;
    new->d.failed = 0;
    m += sizeof(ngx_pool_data_t);//讓m指向該塊記憶體ngx_pool_data_t結構體之後資料區起始位置
    m = ngx_align_ptr(m, NGX_ALIGNMENT);//按NGX_ALIGNMENT位元組對齊
    new->d.last = m + size;//在資料區分配size大小的記憶體並設定last指標  
    current = pool->current;
	//更新current
    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {//failed的值只在此處被修改
            current = p->d.next;//失敗6次以上移動current指標
        }
    }
	//將分配的block鏈入記憶體池 
    p->d.next = new;//尾插法
	//如果直到最後節點failed計數>=6次,則current指向新分配的記憶體池節點block
    pool->current = current ? current : new;

    return m;
}
這個函式中申請到新的記憶體池塊後,在該塊中分配完ngx_pool_data_t結點後,將這個結點掛在記憶體池佇列的結尾處。
 有兩點 需要注意的:

1.新建的記憶體池節點的開頭部分都只有結構體ngx_pool_data_t(注意不是40B的ngx_pool_t(建立記憶體池時才這樣)而是16 B的ngx_pool_data_t,因為

記憶體池除頭節點外的其它節點沒有必要更多的ngx_pool_t描述結構,多麼節省記憶體), 空閒記憶體的開始處new->d.last 不僅去除ngx_pool_data_t大小

的頭結構體而且還需 要 ngx_align_ptr(m, NGX_ALIGNMENT)進行記憶體對齊。

2.ngx_pool_data_t結構中的failed及current的妙用。failed實際上是用來計數用的,current欄位記錄了後續從記憶體池分配記憶體的起始記憶體節點,即從

current指向 的記憶體池節點開始搜尋可分配的記憶體,其中current的變動是根據統計來做的。如下
   for (p = current; p->d.next; p = p->d.next) {
    if (p->d.failed++ > 4) {
        current = p->d.next;
     }
 }
   當連結串列較長,由於記憶體池管理佇列是單向連結串列, 所以每次從頭到尾搜尋是很費時的。每次搜尋失敗的結點(非尾部結點)的failed加1。failed指出了

 該結點經歷多少次查尋,如果從當前記憶體池節點分配記憶體總失敗次數大於等於6次(由於p->d.failed初始值為0,至少失敗6次才為真),就將current欄位

 移動到下一個記憶體池節點,如下一個節點的failed次數也大於等於6次,再下一個。這樣,下次再做類似查詢時,可以跳過若干不必要的結點加快查詢

速度。最後新申請的記憶體池節點採用尾插法插入記憶體池連結串列中。

,static void *ngx_palloc_large(ngx_pool_t *pool, size_t size);

      待申請新的記憶體池節點大於於max是呼叫這個函式。

//控制大塊記憶體的申請
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;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;//把新分配的記憶體塊設定在其空出的large的alloc指標欄位下
            return p;
        }

        if (n++ > 3) {//嘗試5次仍未找到已釋放大記憶體塊後空出的ngx_pool_large_t頭結構體
            break;
        }
    }
	// 重新分配ngx_pool_large_t結構體
    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;
}

ngx_palloc_large() 函式專用來申請大塊記憶體,其過程總結為如下兩步:

第一步,呼叫ngx_alloc申請的大塊記憶體。

第二步,在ngx_pool_t中大塊記憶體節點large連結串列中尋找空閒的ngx_pool_larger結點。如果找到,將大塊記憶體掛在該結點上。ngx_pool_larger佇列中

查詢空閒結點數不會超過五次。超過五個結點沒找到空閒結點就放棄。如果超過5次仍沒找到空閒的large節點,則建立一個新的ngx_pool_large_t結

構體,並將申請到大塊記憶體掛在這個新結點上,最後將這個節點採用頭插法插入連結串列頭部。

綜合 函式⑶、⑷ 可知 ngx_palloc_block,ngx_palloc_large 為nginx從系統申請新的記憶體池節點加入到ngx_pool_t這個記憶體池管理容器中。

,void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

       該函式也是從ngx_pool_t記憶體池中分配size大小的記憶體,並且對分配的記憶體塊進行了清零。內部實際上是呼叫ngx_palloc申請記憶體,然後呼叫

       ngx_memzero清零。

,void *ngx_prealloc(ngx_pool_t *pool, void *p, size_t old_size, size_t new_size);

        對指標p指向的一塊記憶體再分配。如果p是NULL,則直接分配一塊新的new_size大小的記憶體。如果p不是NULL, 新分配一塊記憶體,並把舊記憶體

       中的內容拷貝 至新記憶體塊中,然後釋放p的舊記憶體(具體能不能釋放舊的,要視具體的情況而定,這裡不再詳述)。這個函式實際上也是使用

        ngx_palloc實現的。

, void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

         按照指定對齊大小alignment來申請一塊大小為size的記憶體。此處獲取的記憶體不管大小都將被置於大記憶體塊鏈中管理。

(8),ngx_int_t  ngx_pfree(ngx_pool_t *pool, void *p);// 釋放指定的記憶體

       對於被置於大塊記憶體鏈,也就是被large欄位管理的一列記憶體中的某塊進行釋放。該函式的實現是順序遍歷large管理的大塊記憶體連結串列。所以效率

      比較低下。如果在這個連結串列中找到了這塊記憶體,則釋放,並返回NGX_OK。否則返回NGX_DECLINED。由於這個操作效率比較低下,除非必要,

     也就是說這塊記憶體非 常大,確應及時釋放,否則一般不需要呼叫。反正記憶體在這個pool被銷燬的時候,總歸會都釋放掉的嘛!

     需要注意的是該函式只釋放large連結串列中註冊的記憶體,普通記憶體在ngx_destroy_pool中統一釋放。

(9),ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);// 註冊cleanup回叫函式(結構體)

        ngx_pool_t中的cleanup欄位管理著一個特殊的連結串列,該連結串列的每一項都記錄著一個特殊的需要釋放的資源。對於這個連結串列中每個節點所包含的

      資源如何去釋放, 是自說明的。這也就提供了非常大的靈活性。意味著,ngx_pool_t不僅僅可以管理記憶體,通過這個機制,也可以管理任何需要

      釋放的資源,例如,關閉檔案,或 者刪除檔案等等的。 (這個過程在nginx裡面出現的比較多,也就是 xxxx_add 操作通常不是實際的新增操作,

     而是分配空間並返回一個指標,後續我們還要通過操作指標指向的空間來實現所謂的add)

下面介紹這個連結串列(在檔案ngx_palloc.h定義):

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;
typedef void (*ngx_pool_cleanup_pt)(void *data);
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;// 是一個函式指標,指向一個可以釋放data所對應資源的函式。該函式的只有一個引數,就是data
    void                 *data;//指向要清除的資料  
    ngx_pool_cleanup_t   *next;//下一個cleanup callback
};

看到這裡,ngx_pool_cleanup_add這個函式的用法,我相信大家都應該有一些明白了。但是這個引數size是起什麼作用的呢?這個 size就是要儲存這個

data欄位所指向 的資源的大小。比如我們需要最後刪除一個檔案。那我們在呼叫這個函式的時候,把size指定為儲存檔名的字串的大小,然後呼叫

這個函式給cleanup連結串列中增加一項。 該函式會返回新新增的這個節點。我們然後把這個節點中的data欄位拷貝為檔名。把hander欄位賦值為一個刪

除檔案的函式 (當然該函式的原型為:void(*ngx_pool_cleanup_pt)(void *data))。

Nginx中預定義了兩個cleanup函式。

void ngx_pool_cleanup_file(void *data) 用來關閉開啟的檔案。

void ngx_pool_delete_file(void *data) 用來刪除檔案並且試圖關閉檔案。

概括起來如下圖:

                   

由圖可知,每個需要清理的資源都對應有一個頭部結構,這個結構中有一個關鍵的欄位handler,handler是一個函式指標,在掛載一個資源到記憶體池上的

時候,同時也會註冊一個清理資源的函式到這個handler上。即是說,記憶體池在清理cleanup的時候,就是呼叫這個handler來清理對應的資源。  

4,下面是記憶體操作的一些例子

毫無疑問記憶體池的使用給nginx帶來很大好處,比如記憶體使用便利、邏輯程式碼的簡化、程式效能的提升等。 

為了 更好的理解 nginx 記憶體管理相關 的設計 和 使用 方法,下面我們寫一個測試例子 進行 編譯除錯:

程式碼如下:

//ngx_pool_test.c 
#include <stdio.h>
#include <string.h>
#include "ngx_config.h"//包含相關 nginx 標頭檔案
#include "nginx.h"
#include "ngx_conf_file.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"

volatile ngx_cycle_t *ngx_cycle; // 測試需要
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log,
			ngx_err_t err, const char *fmt, ...)
{
}
// 自定義結構體型別
typedef struct demo_s 
{
	int key;
	char *name;
}demo_t;

//輸出記憶體池相關資訊:小塊記憶體連結串列上個記憶體節點資訊
void pool_blocks(ngx_pool_t *pool)
{
	int n = 0;
	ngx_pool_t *pool_head = pool;//記錄記憶體池頭(連結串列)部節點
        // ngxin 對於一個記憶體池(連結串列) 只有頭節點 包含 max,current等資訊 
        while(pool)
	{
		printf("Block %d:\n", n+1);
		printf("block addr = 0x%0x\n", pool);
		printf("  .current = 0x%0x\n", pool_head->current); 
		printf("unused memory size is %d\n", (ngx_uint_t)(pool->d.end - 
						pool->d.last));
		printf("Block %d failed %d\n", n+1, pool->d.failed);
		pool = pool->d.next;
		++n;
	}
	printf("-------------------------------\n");
}


int main()
{
	ngx_pool_t *pool;
	demo_t *demo;
	char name[] = "hello NGX!";
 	char *buf;
       // 建立一個新的記憶體池
        pool = ngx_create_pool(1024, NULL);
	printf("pool max is %d\n\n", pool->max);
	pool_blocks(pool);
        // 向NGX申請記憶體
	demo = ngx_palloc(pool, sizeof(demo_t));
	buf = ngx_palloc(pool, strlen(name)+1);
	demo->key = 1;
	demo->name = buf;
	strcpy(buf, name);
        // 輸出資料
        printf("Data\n");
    	printf("demo->key=%d, demo->name=%s\n", demo->key, demo->name);
	pool_blocks(pool);
       
        // 申請記憶體
	ngx_palloc(pool, 970);
	pool_blocks(pool);

	ngx_palloc(pool, 970);
	pool_blocks(pool);

	ngx_destroy_pool(pool);
	return 0;
}

上面的程式碼注意新增  ngx_cycle_t *ngx_cycle , ngx_log_error_core的相關宣告,不然會出現如下錯誤:

undefined reference to `ngx_cycle'
undefined reference to `ngx_log_error_core'
對於上面的程式碼, 編寫 相應的Makefile(不熟悉make的可以 參考這裡)檔案如下:

CC=gcc
C_FLAGS = -g -Wall -Wextra  
DIR=/home/dane/nginx-1.2.0
TARGETS=ngx_pool_test
TARGETS_FILE=$(TARGETS).c

all:$(TARGETS)


clean:
	rm -f $(TARGETS) *.o

CORE_INCS=-I $(DIR)/src/core/ \
		  -I $(DIR)/objs/ \
		  -I $(DIR)/src/event \
		  -I $(DIR)/src/event/modules \
		  -I $(DIR)/src/os/unix \
		  -I $(DIR)/Nginx_Pre/pcre-8.32/

NGX_OBJ = $(DIR)/objs/src/core/ngx_palloc.o \
		  $(DIR)/objs/src/core/ngx_string.o \
		  $(DIR)/objs/src/os/unix/ngx_alloc.o

$(TARGETS):$(TARGETS_FILE)
	$(CC) $(C_FLAGS) $(TARGETS_FILE) $(CORE_INCS) $(NGX_OBJ) -o [email protected]

makefile 檔案 需要指定 相應的 依賴檔案 包含檔案 路徑等。

上面的Makefile 編寫好後, 直接 make 就可產生 出 可執行檔案 ngx_pool_test

./ngx_pool_test 即可執行 可執行檔案。

結果如下:

              

 由程式及上圖執行結果可知,程式開始呼叫ngx_create_pool 建立大小為 1024大小的記憶體池頭部節點,因為ngx_pool_t 的大小為40位元組,

所以此時可用最大記憶體 pool->max 大小為 1024-40 為 984位元組。隨後呼叫函式ngx_palloc 在記憶體池上分配 sizeof(demo_t)大小的記憶體,並

申請其第二個成員對應的記憶體char* name,其總共申請記憶體大小為 8+11 為19位元組,因此 第一個記憶體塊所剩記憶體大小為 965位元組。隨後 我們

申請 970位元組的記憶體 此時記憶體池中沒有滿足要求的記憶體塊,因此記憶體池呼叫函式ngx_palloc_block向系統申請 size即1024位元組大小的記憶體,並

掛接在記憶體池之上,此時新申請的記憶體池節點的頭部只是佔用ngx_pool_data_t (16位元組)結構體大小的記憶體,所以可用記憶體大小為 1024-16 為

1008 位元組,所以對於第二個block 去除 970位元組所申請記憶體大小 還剩 38位元組,隨後的程式讀者可以 根據程式 自行分析。

參考資料:

http://blog.csdn.net/livelylittlefish/article/details/6586946

http://code.google.com/p/nginxsrp/wiki/NginxCodeReview

http://www.alidata.org/archives/1390