初識nginx——內存池篇

初識nginx——內存池篇

 

     爲了自身使用的方便,Nginx封裝了不少有用的數據結構,好比ngx_str_t ,ngx_array_t, ngx_pool_t 等等,對於內存池,nginx設計的十分精煉,值得咱們學習,本文介紹內存池基本知識,nginx內存池的結構和關鍵代碼,並用一個實際的代碼例子做了進一步的講解nginx

 

 

1、內存池概述

    內存池是在真正使用內存以前,預先申請分配必定數量的、大小相等(通常狀況下)的內存塊留做備用。當有新的內存需求時,就從內存池中分出一部份內存塊,若內存塊不夠用時,再繼續申請新的內存。程序員

   內存池的好處有減小向系統申請和釋放內存的時間開銷,解決內存頻繁分配產生的碎片,提示程序性能,減小程序員在編寫代碼中對內存的關注等數組

   目前一些常見的內存池實現方案有STL中的內存分配區,boost中的object_pool,nginx中的ngx_pool_t,google的開源項目TCMalloc等數據結構

 

2、nginx內存池綜述

     nginx爲每個層級都會建立一個內存池,進行內存的管理,好比一個模板,tcp鏈接,http請求等,在對應的生命週期結束的時候會摧毀整個內存池,把分配的內存一次性歸還給操做系統。tcp

     在分配的內存上,nginx有小塊內存和大塊內存的概念,小塊內存 nginx在分配的時候會嘗試在當前的內存池節點中分配,而大塊內存會調用系統函數malloc向操做系統申請函數

     在釋放內存的時候,nginx沒有專門提供針對釋放小塊內存的函數,小塊內存會在ngx_destory_pool 和 ngx_reset_pool的時候一併釋放性能

     區分小塊內存和大塊內存的緣由有2個,學習

     一、針對大塊內存  若是它的生命週期遠遠短於所屬的內存池,那麼提供一個單獨的釋放函數是十分有意義的,但不區分大塊內存和小塊內存,針對大的內存塊 便會沒法提早釋放了ui

     二、大塊內存與小塊內存的界限是一頁內存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值經過調用getpagesize()得到),大於一頁的內存在物理上不必定是連續的,因此若是分配的內存大於一頁的話,從內存池中使用,和向操做系統從新申請效率差很少是等價的this

 

      nginx內存池提供的函數主要有如下幾個

     NewImage

 

 

3、nginx內存池詳解

    nginx使用了ngx_pool_s用於表示整個內存池對象,ngx_pool_data_t表示單個內存池節點的分配信息,ngx_pool_large_s表示大塊內存

它們的結構和含義以下

struct ngx_pool_large_s {
ngx_pool_large_t *next;
void *alloc;
};

next:   指向下一個大塊內存

alloc:指向分配的大塊內存

 

struct ngx_pool_s {
   ngx_pool_data_t d;
   size_t max;
   ngx_pool_t *current;
   ngx_chain_t *chain;
   ngx_pool_large_t *large;
   ngx_pool_cleanup_t *cleanup;
   ngx_log_t *log;
};

d:內存池的節點的數據分配狀況

max:      單個內存池節點容量的最大值

current: 指向當前的內存池節點

chain: 指向一個ngx_chain_t結構

large:  指向大塊內存鏈表

cleanup:釋放內存池的callback

log:     用於輸出日誌

 

typedef struct {
   u_char *last;
   u_char *end;
   ngx_pool_t *next;
   ngx_uint_t failed;
} ngx_pool_data_t;

last:    內存池節點已分配的末位地址,下一次分配會嘗試今後開始

end: 內存池節點的結束位置

next:next指向下一個內存池節點

failed: 當前內存池節點分配失敗次數

 

NewImage

 

       nginx 內存池示意圖1

 

    在分配內存的時候,nginx會判斷當前要分配的內存是小塊內存仍是大塊內存,大塊內存調用ngx_palloc_large進行分配,小塊內存nginx先會嘗試從內存池的當前節點(p->current)中分配,若是內存池當前節點的剩餘空間不足,nginx會調用ngx_palloc_block新建立一個內存池節點並從中分配,

若是內存池當前節點的分配失敗次數已經大於等於6次(p->d.failed++ > 4),則將當前內存池節點前移一個

(這裏有個須要注意的地方,噹噹前內存節點的剩餘空間不夠分配時,nginx會從新建立一個ngx_pool_t對象,而且將pool.d->next指向新的ngx_pool_t,新分配的ngx_pool_t對象只用到了ngx_pool_data_t區域,並無頭部信息,頭部信息部分已經被當作內存分配區域了)

 

 

 

NewImage

 

                 nginx 內存池示意圖2(新建了一個內存池節點和分配了2個大塊內存,其中一個已經釋放) 

 

 

關鍵代碼

建立內存池代碼

 

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 ); //間接調用了posix_memalign分配內存
     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; //因爲當前內存池只有一個節點因此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_palloc分配函數代碼

void  *
ngx_palloc(ngx_pool_t *pool,  size_t  size)
{
     u_char *m;
     ngx_pool_t *p;
     if  (size <= pool->max)  //判斷是小塊內存 仍是大塊內存
     {
         p = pool->current;
         do  {
             m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
             if  (( size_t ) (p->d.end - m) >= size) { //嘗試在已有的內存池節點中分配內存
                 p->d.last = m + size;
                 return  m;
             }
             p = p->d.next;
         while  (p);
         return  ngx_palloc_block(pool, size); //當前已有節點都分配失敗,建立一個新的內存池節點
     }
     return  ngx_palloc_large(pool, size); //分配大塊內存
}

 

消耗內存池

 

void
ngx_destroy_pool(ngx_pool_t *pool)
{
     ngx_pool_t *p, *n;
     ngx_pool_large_t *l;
     ngx_pool_cleanup_t *c;
     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); //調用須要在內存池釋放時同步調用的方法
         }
     }
     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
     for  (p = pool, n = pool->d.next;  /* void */ ; p = n, n = n->d.next) {
         ngx_free(p); //間接調用free釋放內存
         if  (n == NULL) {
             break ;
         }
     }
}

 

4、示例代碼

 這裏是直接替換了原有nginx代碼的main函數 (src/core/nginx.c)

void  print_pool(ngx_pool_t *pool)
{
     if  (pool->large != NULL)
     {
         printf ( "has large memory\n" );
         for (ngx_pool_large_t* i = pool->large; i!=NULL; i = i->next)
         {
             printf ( "\t\tlarge next=0x%x\n" , i->next);
             printf ( "\t\tlarge alloc=0x%x\n" , i->alloc);
         }
         }
         int  i=1;
         while (pool)
         {
             printf ( "pool=0x%x,index:%d\n" , pool, i++);
             printf ( "\t\tlast=0x%x\n" , (pool->d).last);
             printf ( "\t\tend=0x%x\n" ,(pool->d).end);
             printf ( "\t\tnext=0x%x\n" ,(pool->d).next);
             printf ( "\t\tfailed=%d\n" ,pool->d.failed);
             printf ( "\t\tmax=%d\n" ,pool->max);
             printf ( "\t\tcurrent=0x%x\n" ,pool->current);
             printf ( "\t\tchain=0x%x\n" ,pool->chain);
             printf ( "\t\tlarge=0x%x\n" ,pool->large);
             printf ( "\t\tcleanup=0x%x\n" ,pool->cleanup);
             printf ( "\t\tlog=0x%x\n" ,pool-> log );
             printf ( "\t\tavailable pool memory=%d\n" , pool->d.end-pool->d.last);
             printf ( "\n" );
             pool=pool->d.next;
         }
     }
void  print_array( int  *a, int  size)
{
     for ( int  i=0; i<size; i++)
     {
         printf ( "%d," ,a[i]);
     }
     printf ( "\n" );
}
int  main()
{
     ngx_pool_t *pool;
     int  array_size = 128;
     int  array_size_large = 1024;
     int  page_size = getpagesize(); //得到一頁的大小
     printf ( "page_size:%d\n" , page_size);
     printf ( "----------------------------\n" );
     printf ( "create a new pool" );
     pool = ngx_create_pool(1024, NULL); //建立一個大小爲1024的內存池
     print_pool(pool);
     printf ( "----------------------------\n" );
     printf ( "alloc block 1 from the pool:\n" );
     int  *a1 = ngx_palloc(pool,  sizeof ( int ) * array_size); //分配第一塊內存 用於建立數組
     for  ( int  i=0; i< array_size; i++)
     {
         a1[i] = i+1;
     }
     print_pool(pool);
     printf ( "----------------------------\n" );
     printf ( "alloc block 2 from the pool:\n" );
     int  *a2 = ngx_palloc(pool,  sizeof ( int ) * array_size); //分配第二塊內存 用於建立數組,這個時候會建立第二個內存池節點
     for  ( int  i=0; i< array_size; i++)
     {
         a2[i] = 12345678;
     }
     print_pool(pool);
     printf ( "----------------------------\n" );
     printf ( "alloc large memory:\n" );
     printf ( "\t\tlarge next before=0x%x\n" , pool->current->d.last);
     int  * a3 = ngx_palloc(pool,  sizeof ( int ) * array_size_large); //因爲大小超過了max的值 ngx_palloc中會調用ngx_palloc_large分配大塊內存
     printf ( "\t\tlarge next after=0x%x\n" , pool->large);
     for  ( int  i=0; i< array_size_large; i++)
     {
         a3[i] = i+1;
     }
     print_pool(pool);
     print_array(a1,array_size);
     print_array(a2,array_size);
     print_array(a3,array_size_large);
     ngx_destroy_pool(pool);
     return  0;
}

 

 

 

 

  運行結果:

 NewImage

NewImage

 

 

 

NewImage 

 

 經過紅框能夠看到ngx_pool_t中只有第一個內存池節點的頭部信息是有意義的,後續調用ngx_palloc_block建立的節點的頭部信息都已經被數據覆蓋。

 

5、總結

nginx的代碼設計的十分靈活,既方便咱們開發,也方便咱們複用其中的結構,其中內存池的使用 對咱們學習nginx,瞭解nginx如何管理內存有着十分重要的意義。

相關文章
相關標籤/搜索