初識nginx——內存池篇


初識nginx——內存池篇

 

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

 

 

一、內存池概述

    內存池是在真正使用內存之前,預先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠用時,再繼續申請新的內存。

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

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

 

二、nginx內存池綜述

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

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

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

     區分小塊內存和大塊內存的原因有2個,

     1、針對大塊內存  如果它的生命周期遠遠短於所屬的內存池,那么提供一個單獨的釋放函數是十分有意義的,但不區分大塊內存和小塊內存,針對大的內存塊 便會無法提前釋放了

     2、大塊內存與小塊內存的界限是一頁內存(p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL,NGX_MAX_ALLOC_FROM_POOL的值通過調用getpagesize()獲得),大於一頁的內存在物理上不一定是連續的,所以如果分配的內存大於一頁的話,從內存池中使用,和向操作系統重新申請效率差不多是等價的

 

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

     NewImage

 

 

三、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 ;
         }
     }
}

 

四、示例代碼

 這里是直接替換了原有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創建的節點的頭部信息都已經被數據覆蓋。

 

五、總結

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

 

 


免責聲明!

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



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