Nginx — 內存池

2013年10月20日html

Nginx 之 內存池nginx

一、基本結構數據結構

先來學習一下nginx內存池的幾個主要數據結構:[見:./src/core/ngx_palloc.h/.c]函數

    ngx_pool_data_t(內存池數據塊結構)性能

   1: typedef struct {
   2:     u_char               *last;
   3:     u_char               *end;
   4:     ngx_pool_t           *next;
   5:     ngx_uint_t            failed;
   6: } ngx_pool_data_t;

 

ngx_pool_s(內存池頭部結構)學習

   1: struct ngx_pool_s {
   2:     ngx_pool_data_t       d;
   3:     size_t                max;
   4:     ngx_pool_t           *current;
   5:     ngx_chain_t          *chain;
   6:     ngx_pool_large_t     *large;
   7:     ngx_pool_cleanup_t   *cleanup;
   8:     ngx_log_t            *log;
   9: };

能夠說,ngx_pool_data_tngx_pool_s基本構成了nginx內存池的主體結構,下面詳細介紹一下nginx內存池的主體結構:ui

1

如上圖,nginx的內存池實際是一個由ngx_pool_data_tngx_pool_s構成的鏈表,其中:spa

ngx_pool_data_t中:debug

last:是一個unsigned char 類型的指針,保存的是/當前內存池分配到末位地址,即下一次分配今後處開始。unix

end:內存池結束位置;

next:內存池裏面有不少塊內存,這些內存塊就是經過該指針連成鏈表的,next指向下一塊內存。

failed:內存池分配失敗次數。

 

ngx_pool_s

d:內存池的數據塊;

max:內存池數據塊的最大值;

current:指向當前內存池;

chain:該指針掛接一個ngx_chain_t結構;

large:大塊內存鏈表,即分配空間超過max的狀況使用;

cleanup:釋放內存池的callback

log:日誌信息

 

以上是內存池涉及的主要數據結構,爲了儘可能簡化,其餘涉及的數據結構將在下面實際用到時候再作介紹。

 

二、內存池基本操做

內存池對外的主要方法有:

建立內存池 ngx_pool_t *  ngx_create_pool(size_t size, ngx_log_t *log);
銷燬內存池 void ngx_destroy_pool(ngx_pool_t *pool);
重置內存池 void ngx_reset_pool(ngx_pool_t *pool);
內存申請(對齊) void *  ngx_palloc(ngx_pool_t *pool, size_t size);
內存申請(不對齊) void *  ngx_pnalloc(ngx_pool_t *pool, size_t size);
內存清除 ngx_int_t  ngx_pfree(ngx_pool_t *pool, void *p);

注:

在分析內存池方法前,須要對幾個主要的內存相關函數做一下介紹(見:./src/Os/Unix(Win32)/ngx_alloc.h/.c)

這裏僅對Win32的做介紹:

ngx_alloc:(只是對malloc進行了簡單的封裝)

   1: void *ngx_alloc(size_t size, ngx_log_t *log)
   2: {
   3:     void  *p;
   4:
   5:     p = malloc(size);
   6:     if (p == NULL) {
   7:         ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
   8:                       "malloc(%uz) failed", size);
   9:     }
  10:
  11:     ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
  12:
  13:     return p;
  14: }

ngx_calloc:(調用malloc並初始化爲0)

   1: void *ngx_calloc(size_t size, ngx_log_t *log)
   2: {
   3:     void  *p;
   4:
   5:     p = ngx_alloc(size, log);
   6:
   7:     if (p) {
   8:         ngx_memzero(p, size);
   9:     }
  10:
  11:     return p;
  12: }

ngx_memzero:

   1: #define ngx_memzero(buf, n)       (void) memset(buf, 0, n)

ngx_free :

   1: #define ngx_free          free

ngx_memalign

   1: #define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)

這裏alignment主要是針對部分unix平臺須要動態的對齊,對POSIX 1003.1d提供的posix_memalign( )進行封裝,在大多數狀況下,編譯器和C庫透明地幫你處理對齊問題。nginx中經過宏NGX_HAVE_POSIX_MEMALIGN來控制;

2.一、內存池建立(ngx_create_pool)

ngx_create_pool用於建立一個內存池,咱們建立時,傳入咱們的初始大小:

   1: ngx_pool_t *
   2: ngx_create_pool(size_t size, ngx_log_t *log)
   3: {
   4:     ngx_pool_t  *p;
   5:
   6:     p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
   7:     if (p == NULL) {
   8:         return NULL;
   9:     }
  10:
  11:     p->d.last = (u_char *) p + sizeof(ngx_pool_t);//初始狀態:last指向ngx_pool_t結構體以後數據取起始位置
  12:     p->d.end = (u_char *) p + size;//end指向分配的整個size大小的內存的末尾
  13:     p->d.next = NULL;
  14:     p->d.failed = 0;
  15:     //#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
  16:     //內存池最大不超過4095,x86中頁的大小爲4K
  17:     size = size - sizeof(ngx_pool_t);
  18:     p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
  19:
  20:     p->current = p;
  21:     p->chain = NULL;
  22:     p->large = NULL;
  23:     p->cleanup = NULL;
  24:     p->log = log;
  25:
  26:     return p;
  27: }

nginx對內存的管理分爲大內存與小內存,當某一個申請的內存大於某一個值時,就須要從大內存中分配空間,不然從小內存中分配空間。 
nginx中的內存池是在建立的時候就設定好了大小,在之後分配小塊內存的時候,若是內存不夠,則是從新建立一塊內存串到內存池中,而不是將原有的內存池進行擴張。當要分配大塊內存是,則是在內存池外面再分配空間進行管理的,稱爲大塊內存池。

2.二、內存申請

ngx_palloc

   1: void *
   2: ngx_palloc(ngx_pool_t *pool, size_t size)
   3: {
   4:     u_char      *m;
   5:     ngx_pool_t  *p;
   6:
   7:     if (size <= pool->max) {//若是申請的內存大小大於內存池的max值,則走另外一條路,申請大內存
   8:
   9:         p = pool->current;
  10:
  11:         do {
  12:             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);//對內存地址進行對齊處理
  13:
  14:             if ((size_t) (p->d.end - m) >= size) {//若是在當前內存塊有效範圍內,進行內存指針的移動
  15:                 p->d.last = m + size;
  16:
  17:                 return m;
  18:             }
  19:
  20:             p = p->d.next;//若是當前內存塊有效容量不夠分配,則移動到下一個內存塊進行分配
  21:
  22:         } while (p);
  23:
  24:         return ngx_palloc_block(pool, size);
  25:     }
  26:
  27:     return ngx_palloc_large(pool, size);
  28: }

這裏須要說明的幾點:

一、ngx_align_ptr,這是一個用來內存地址取整的宏,很是精巧,一句話就搞定了。做用不言而喻,取整能夠下降CPU讀取內存的次數,提 高性能。由於這裏並無真正意義調用malloc等函數申請內存,而是移動指針標記而已,因此內存對齊的活,C編譯器幫不了你了,得本身動手。

   1: #define ngx_align_ptr(p, a)                                                   \
   2:     (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

 

二、ngx_palloc_block(ngx_pool_t *pool, size_t size)

這個函數是用來分配新的內存塊,爲pool內存池開闢一個新的內存塊,並申請使用size大小的內存;

   1: static void *
   2: ngx_palloc_block(ngx_pool_t *pool, size_t size)
   3: {
   4:     u_char      *m;
   5:     size_t       psize;
   6:     ngx_pool_t  *p, *new, *current;
   7:
   8:     psize = (size_t) (pool->d.end - (u_char *) pool);//計算內存池第一個內存塊的大小
   9:
  10:     m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//分配和第一個內存塊一樣大小的內存塊
  11:     if (m == NULL) {
  12:         return NULL;
  13:     }
  14:
  15:     new = (ngx_pool_t *) m;
  16:
  17:     new->d.end = m + psize;//設置新內存塊的end
  18:     new->d.next = NULL;
  19:     new->d.failed = 0;
  20:
  21:     m += sizeof(ngx_pool_data_t);//將指針m移動到d後面的一個位置,做爲起始位置
  22:     m = ngx_align_ptr(m, NGX_ALIGNMENT);//對m指針按4字節對齊處理
  23:     new->d.last = m + size;//設置新內存塊的last,即申請使用size大小的內存
  24:
  25:     current = pool->current;
  26:     //這裏的循環用來找最後一個鏈表節點,這裏failed用來控制循環的長度,若是分配失敗次數達到5次,
  27:      //就忽略,不須要每次都從頭找起
  28:     for (p = current; p->d.next; p = p->d.next) {
  29:         if (p->d.failed++ > 4) {
  30:             current = p->d.next;
  31:         }
  32:     }
  33:
  34:     p->d.next = new;
  35:
  36:     pool->current = current ? current : new;
  37:
  38:     return m;
  39: }

三、ngx_palloc_large(ngx_pool_t *pool, size_t size)

ngx_palloc中首先會判斷申請的內存大小是否超過內存塊的最大限值,若是超過,則直接調用ngx_palloc_large,進入大內存塊的分配流程;

   1: static void *
   2: ngx_palloc_large(ngx_pool_t *pool, size_t size)
   3: {
   4:     void              *p;
   5:     ngx_uint_t         n;
   6:     ngx_pool_large_t  *large;
   7:     // 直接在系統堆中分配一塊空間
   8:     p = ngx_alloc(size, pool->log);
   9:     if (p == NULL) {
  10:         return NULL;
  11:     }
  12:
  13:     n = 0;
  14:     // 查找到一個空的large區,若是有,則將剛纔分配的空間交由它管理
  15:     for (large = pool->large; large; large = large->next) {
  16:         if (large->alloc == NULL) {
  17:             large->alloc = p;
  18:             return p;
  19:         }
  20:
  21:         if (n++ > 3) {
  22:             break;
  23:         }
  24:     }
  25:     //爲了提升效率, 若是在三次內沒有找到空的large結構體,則建立一個
  26:     large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
  27:     if (large == NULL) {
  28:         ngx_free(p);
  29:         return NULL;
  30:     }
  31:
  32:     large->alloc = p;
  33:     large->next = pool->large;
  34:     pool->large = large;
  35:
  36:     return p;
  37: }

image

2.三、內存池重置

ngx_reset_pool

   1: void
   2: ngx_reset_pool(ngx_pool_t *pool)
   3: {
   4:     ngx_pool_t        *p;
   5:     ngx_pool_large_t  *l;
   6:     //釋放全部大塊內存
   7:     for (l = pool->large; l; l = l->next) {
   8:         if (l->alloc) {
   9:             ngx_free(l->alloc);
  10:         }
  11:     }
  12:
  13:     pool->large = NULL;
  14:     // 重置全部小塊內存區
  15:     for (p = pool; p; p = p->d.next) {
  16:         p->d.last = (u_char *) p + sizeof(ngx_pool_t);
  17:     }
  18: }

2.四、內存池清理

ngx_pfree

   1: ngx_int_t
   2: ngx_pfree(ngx_pool_t *pool, void *p)
   3: {
   4:     ngx_pool_large_t  *l;
   5:     //只檢查是不是大內存塊,若是是大內存塊則釋放
   6:     for (l = pool->large; l; l = l->next) {
   7:         if (p == l->alloc) {
   8:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
   9:                            "free: %p", l->alloc);
  10:             ngx_free(l->alloc);
  11:             l->alloc = NULL;
  12:
  13:             return NGX_OK;
  14:         }
  15:     }
  16:
  17:     return NGX_DECLINED;
  18: }

因此說Nginx內存池中大內存塊和小內存塊的分配與釋放是不同的。咱們在使用內存池時,可使用ngx_palloc進行分配,使用ngx_pfree釋放。而對於大內存,這樣作是沒有問題的,而對於小內存就不同了,分配的小內存,不會進行釋放。由於大內存塊的分配只對前3個內存塊進行檢查,不然就直接分配內存,因此大內存塊的釋放必須及時

 

ngx_pool_cleanup_s

Nginx內存池支持經過回調函數,對外部資源的清理。ngx_pool_cleanup_t是回調函數結構體,它在內存池中以鏈表形式保存,在內存池進行銷燬時,循環調用這些回調函數對數據進行清理。

   1: struct ngx_pool_cleanup_s {
   2:     ngx_pool_cleanup_pt   handler;
   3:     void                 *data;
   4:     ngx_pool_cleanup_t   *next;
   5: };

其中

handler:是回調函數指針;

data:回調時,將此數據傳入回調函數;

next://指向下一個回調函數結構體;

若是咱們須要添加本身的回調函數,則須要調用ngx_pool_cleanup_add來獲得一個ngx_pool_cleanup_t,而後設置 handler爲咱們的清理函數,並設置data爲咱們要清理的數據。這樣在ngx_destroy_pool中會循環調用handler清理數據;

好比:咱們能夠將一個開打的文件描述符做爲資源掛載到內存池上,同時提供一個關閉文件描述的函數註冊到handler上,那麼內存池在釋放的時候,就會調用咱們提供的關閉文件函數來處理文件描述符資源了。

image

2.五、內存池銷燬

ngx_destroy_pool

ngx_destroy_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:
   8:     //首先調用全部的數據清理函數
   9:     for (c = pool->cleanup; c; c = c->next) {
  10:         if (c->handler) {
  11:             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
  12:                            "run cleanup: %p", c);
  13:             c->handler(c->data);
  14:
相關文章
相關標籤/搜索