nginx內(nèi)存池

nginx內(nèi)存池

拿來主義:https://www.cnblogs.com/xiekeli/archive/2012/10/17/2727432.html

參考:http://blog.csdn.net/houjixin/article/details/56294550

1集灌、基本結(jié)構(gòu)

先來學(xué)習(xí)一下nginx內(nèi)存池的幾個主要數(shù)據(jù)結(jié)構(gòu):[見:./src/core/ngx_palloc.h/.c]

ngx_pool_data_t(內(nèi)存池數(shù)據(jù)塊結(jié)構(gòu))

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

ngx_pool_s(內(nèi)存池頭部結(jié)構(gòu))

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

可以說袖订,ngx_pool_data_t和ngx_pool_s基本構(gòu)成了nginx內(nèi)存池的主體結(jié)構(gòu)拉一,下面詳細介紹一下nginx內(nèi)存池的主體結(jié)構(gòu):


nginx內(nèi)存池模型

如上圖,nginx的內(nèi)存池實際是一個由ngx_pool_data_t和ngx_pool_s構(gòu)成的鏈表拱绑,其中:

ngx_pool_data_t中:

last:是一個unsigned char 類型的指針,保存的是/當前內(nèi)存池分配到末位地址摹量,即下一次分配從此處開始季春。
end:內(nèi)存池結(jié)束位置;
next:內(nèi)存池里面有很多塊內(nèi)存热康,這些內(nèi)存塊就是通過該指針連成鏈表的召耘,next指向下一塊內(nèi)存。
failed:內(nèi)存池分配失敗次數(shù)褐隆。

ngx_pool_s

d:內(nèi)存池的數(shù)據(jù)塊;
max:內(nèi)存池數(shù)據(jù)塊的最大值剖踊;
current:指向當前內(nèi)存池庶弃;
chain:該指針掛接一個ngx_chain_t結(jié)構(gòu);
large:大塊內(nèi)存鏈表德澈,即分配空間超過max的情況使用歇攻;
cleanup:釋放內(nèi)存池的callback
log:日志信息

以上是內(nèi)存池涉及的主要數(shù)據(jù)結(jié)構(gòu),為了盡量簡化梆造,其他涉及的數(shù)據(jù)結(jié)構(gòu)將在下面實際用到時候再做介紹缴守。

2、內(nèi)存池基本操作

內(nèi)存池對外的主要方法有:

創(chuàng)建內(nèi)存池   ngx_pool_t *  ngx_create_pool(size_t size, ngx_log_t *log);
銷毀內(nèi)存池   void ngx_destroy_pool(ngx_pool_t *pool);
重置內(nèi)存池   void ngx_reset_pool(ngx_pool_t *pool);
內(nèi)存申請(對齊)    void *  ngx_palloc(ngx_pool_t *pool, size_t size);
內(nèi)存申請(不對齊)   void *  ngx_pnalloc(ngx_pool_t *pool, size_t size);
內(nèi)存清除    ngx_int_t  ngx_pfree(ngx_pool_t *pool, void *p);

2.1镇辉、內(nèi)存池創(chuàng)建(ngx_create_pool)

ngx_create_pool用于創(chuàng)建一個內(nèi)存池屡穗,我們創(chuàng)建時,傳入我們的初始大泻龈亍:

//通過ngx_create_pool可以創(chuàng)建一個內(nèi)存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p; 

    // #define NGX_POOL_ALIGNMENT       16 
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);  // 分配內(nèi)存函數(shù)村砂,uinx,windows分開走
    if (p == NULL) {
        return NULL;
    }   
    
    p->d.last = (u_char *) p + sizeof(ngx_pool_t); //初始指向 ngx_pool_t 結(jié)構(gòu)體后面
    p->d.end = (u_char *) p + size; //整個結(jié)構(gòu)的結(jié)尾后面
    p->d.next = NULL;
    p->d.failed = 0;
        
    //sizeof(ngx_pool_t)用來存儲自身
    size = size - sizeof(ngx_pool_t);
    
    // #define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
    // 最大不超過 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小
    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;
}

nginx對內(nèi)存的管理分為大內(nèi)存與小內(nèi)存,當某一個申請的內(nèi)存大于某一個值時屹逛,就需要從大內(nèi)存中分配空間础废,否則從小內(nèi)存中分配空間汛骂。

nginx中的內(nèi)存池是在創(chuàng)建的時候就設(shè)定好了大小,在以后分配小塊內(nèi)存的時候评腺,如果內(nèi)存不夠帘瞭,則是重新創(chuàng)建一塊內(nèi)存串到內(nèi)存池中,而不是將原有的內(nèi)存池進行擴張蒿讥。當要分配大塊內(nèi)存是蝶念,則是在內(nèi)存池外面再分配空間進行管理的,稱為大塊內(nèi)存池诈悍。

2.2祸轮、內(nèi)存申請

ngx_palloc

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

    // pool->max 數(shù)據(jù)塊大小,即小塊內(nèi)存的最大值
    if (size <= pool->max) {

        // pool->current 保存當前內(nèi)存值
        p = pool->current;

        do {
            m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT); // 對齊內(nèi)存指針侥钳,加快存取速度

            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);
    }
    //size過大适袜,需要在大數(shù)據(jù)塊內(nèi)存鏈表上申請內(nèi)存空間
    return ngx_palloc_large(pool, size);
}

這里需要說明的幾點:

1、ngx_align_ptr舷夺,這是一個用來內(nèi)存地址取整的宏苦酱,非常精巧,一句話就搞定了给猾。作用不言而喻疫萤,取整可以降低CPU讀取內(nèi)存的次數(shù),提高性能敢伸。因為這里并沒有真正意義調(diào)用malloc等函數(shù)申請內(nèi)存扯饶,而是移動指針標記而已,所以內(nèi)存對齊的活池颈,C編譯器幫不了你了尾序,得自己動手。

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

2躯砰、ngx_palloc_block(ngx_pool_t *pool, size_t size)

這個函數(shù)是用來分配新的內(nèi)存塊每币,為pool內(nèi)存池開辟一個新的內(nèi)存塊,并申請使用size大小的內(nèi)存琢歇;

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);

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }
    
    new = (ngx_pool_t *) m;
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    current = pool->current;

    for (p = current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            current = p->d.next;
        }
    }

    p->d.next = new;

    pool->current = current ? current : new;

    return m;
}

3兰怠、ngx_palloc_large(ngx_pool_t *pool, size_t size)

在ngx_palloc中首先會判斷申請的內(nèi)存大小是否超過內(nèi)存塊的最大限值,如果超過李茫,則直接調(diào)用ngx_palloc_large揭保,進入大內(nèi)存塊的分配流程;

//控制大塊內(nèi)存的申請
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    // 直接在系統(tǒng)堆中分配一塊空間  
    p = ngx_alloc(size, pool->log); // 在大數(shù)據(jù)塊鏈表中申請調(diào)用了ngx_alloc
    if (p == NULL) {
       return NULL;
    }

    n = 0;

    // 查找到一個空的large區(qū)涌矢,如果有掖举,則將剛才分配的空間交由它管理
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {
            break;
        }
    }

    // 為了提高效率, 如果在三次內(nèi)沒有找到空的large結(jié)構(gòu)體,則創(chuàng)建一個
    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;
}
nginx內(nèi)存池模型

2.3塔次、內(nèi)存池重置

ngx_reset_pool

//重置方篮,將內(nèi)存池恢復(fù)到初始分配的狀態(tài)
void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
     if (l->alloc) {
         ngx_free(l->alloc); //釋放大塊內(nèi)存
     }
    }

    pool->large = NULL;

    for (p = pool; p; p = p->d.next) {
       p->d.last = (u_char *) p + sizeof(ngx_pool_t); //小塊內(nèi)存結(jié)尾指針指向剛分配的位置,其中的數(shù)據(jù)將被覆蓋
    }
}

2.4励负、內(nèi)存池清理

ngx_pfree

//控制大塊內(nèi)存的釋放藕溅。注意,這個函數(shù)只會釋放大內(nèi)存继榆,不會釋放其對應(yīng)的頭部結(jié)構(gòu)巾表,遺留下來的頭部結(jié)構(gòu)會做下一次申請大內(nèi)存之用
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
       if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                  "free: %p", l->alloc);
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

所以說Nginx內(nèi)存池中大內(nèi)存塊和小內(nèi)存塊的分配與釋放是不一樣的。我們在使用內(nèi)存池時略吨,可以使用ngx_palloc進行分配集币,使用ngx_pfree釋放。而對于大內(nèi)存翠忠,這樣做是沒有問題的鞠苟,而對于小內(nèi)存就不一樣了,分配的小內(nèi)存秽之,不會進行釋放当娱。因為大內(nèi)存塊的分配只對前3個內(nèi)存塊進行檢查,否則就直接分配內(nèi)存考榨,所以大內(nèi)存塊的釋放必須及時跨细。

ngx_pool_cleanup_s

Nginx內(nèi)存池支持通過回調(diào)函數(shù),對外部資源的清理河质。ngx_pool_cleanup_t是回調(diào)函數(shù)結(jié)構(gòu)體冀惭,它在內(nèi)存池中以鏈表形式保存,在內(nèi)存池進行銷毀時掀鹅,循環(huán)調(diào)用這些回調(diào)函數(shù)對數(shù)據(jù)進行清理云头。

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;                                                                                                        

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;
    ngx_pool_cleanup_t   *next;
};

其中

handler:    // 是回調(diào)函數(shù)指針;
data:       // 回調(diào)時淫半,將此數(shù)據(jù)傳入回調(diào)函數(shù);
next:       // 指向下一個回調(diào)函數(shù)結(jié)構(gòu)體匣砖;

如果我們需要添加自己的回調(diào)函數(shù)科吭,則需要調(diào)用ngx_pool_cleanup_add來得到一個ngx_pool_cleanup_t,然后設(shè)置handler為我們的清理函數(shù)猴鲫,并設(shè)置data為我們要清理的數(shù)據(jù)对人。這樣在ngx_destroy_pool中會循環(huán)調(diào)用handler清理數(shù)據(jù);

比如:我們可以將一個開打的文件描述符作為資源掛載到內(nèi)存池上拂共,同時提供一個關(guān)閉文件描述的函數(shù)注冊到handler上牺弄,那么內(nèi)存池在釋放的時候,就會調(diào)用我們提供的關(guān)閉文件函數(shù)來處理文件描述符資源了宜狐。

image

2.5势告、內(nèi)存池銷毀

ngx_destroy_pool

ngx_destroy_pool這個函數(shù)用于銷毀一個內(nèi)存池:

//銷毀內(nèi)存池
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;
    
    // 遍歷節(jié)點上的各個節(jié)點
    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);  //釋放節(jié)點占用的內(nèi)存
        }
    }

    //對大塊數(shù)據(jù)內(nèi)存的清理
    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); // 直接ngx_free蛇捌,宏,本質(zhì)是free
    }
}

#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);

        if (n == NULL) {
          break;
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咱台,一起剝皮案震驚了整個濱河市络拌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌回溺,老刑警劉巖春贸,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異遗遵,居然都是意外死亡萍恕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門车要,熙熙樓的掌柜王于貴愁眉苦臉地迎上來允粤,“玉大人,你說我怎么就攤上這事屯蹦∥” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵登澜,是天一觀的道長阔挠。 經(jīng)常有香客問我,道長脑蠕,這世上最難降的妖魔是什么购撼? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮谴仙,結(jié)果婚禮上迂求,老公的妹妹穿的比我還像新娘。我一直安慰自己晃跺,他們只是感情好揩局,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掀虎,像睡著了一般凌盯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烹玉,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天驰怎,我揣著相機與錄音,去河邊找鬼二打。 笑死县忌,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播症杏,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼装获,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸳慈?” 一聲冷哼從身側(cè)響起饱溢,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎走芋,沒想到半個月后绩郎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡翁逞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年肋杖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挖函。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡状植,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怨喘,到底是詐尸還是另有隱情津畸,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布必怜,位于F島的核電站肉拓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏梳庆。R本人自食惡果不足惜暖途,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膏执。 院中可真熱鬧驻售,春花似錦、人聲如沸更米。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽征峦。三九已至纸巷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眶痰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工梯啤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留竖伯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像七婴,于是被迫代替她去往敵國和親祟偷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359