日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

Nginx 內(nèi)存池 ngx_pool_t

nginx 是自己實(shí)現(xiàn)了內(nèi)存池的,所以在nginx ngx_pool_t 這個(gè)結(jié)構(gòu)也隨處可見,這里主要分析一下內(nèi)存池的分配邏輯。

內(nèi)存池實(shí)現(xiàn)了包括小塊內(nèi)存、大塊內(nèi)存和清理資源幾種資源的處理,應(yīng)該來(lái)說(shuō)覆蓋了絕大數(shù)的使用場(chǎng)景了。

文章相關(guān)視頻講解:

高性能服務(wù)器為什么需要內(nèi)存池??jī)?nèi)存如何分配? 如何設(shè)計(jì)內(nèi)存 ?看視頻講解:「鏈接」

Nginx源碼分析之內(nèi)存池與線程池:「鏈接」

相關(guān)結(jié)構(gòu)定義

// 大塊內(nèi)存
typedef struct ngx_pool_large_s  ngx_pool_large_t;
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;         // 下一個(gè)大塊內(nèi)存池
    void                 *alloc;        // 實(shí)際分配內(nèi)存
};

// 小塊內(nèi)存池
typedef struct {
    u_char               *last;         // 可分配內(nèi)存起始地址
    u_char               *end;          // 可分配內(nèi)存結(jié)束地址
    ngx_pool_t           *next;         // 指向內(nèi)存管理結(jié)構(gòu)
    ngx_uint_t            failed;       // 內(nèi)存分配失敗次數(shù)
} ngx_pool_data_t;

// 內(nèi)存池管理結(jié)構(gòu)
typedef struct ngx_pool_s            ngx_pool_t;
struct ngx_pool_s {
    ngx_pool_data_t       d;            // 小塊內(nèi)存池
    size_t                max;          // 小塊內(nèi)存最大的分配內(nèi)存,評(píng)估大內(nèi)存還是小塊內(nèi)存
    ngx_pool_t           *current;      // 當(dāng)前開始分配的小塊內(nèi)存池
    ngx_chain_t          *chain;        // chain
    ngx_pool_large_t     *large;        // 大塊內(nèi)存
    ngx_pool_cleanup_t   *cleanup;      // 待清理資源
    ngx_log_t            *log;          // 日志對(duì)象
};

ngx_pool_t 是整個(gè)內(nèi)存池的管理結(jié)構(gòu),這種結(jié)構(gòu)對(duì)于個(gè)內(nèi)存池對(duì)象來(lái)說(shuō)可能存在多個(gè),但是對(duì)于用戶而言,第一下訪問(wèn)的始終是創(chuàng)建時(shí)返回的那個(gè)。多個(gè) ngx_pool_t 通過(guò) d.next 來(lái)進(jìn)行連接,current 指向 當(dāng)前開始分配的小塊內(nèi)存池,注意 ngx_pool_data_t 在內(nèi)存池結(jié)構(gòu)的起始處,可以進(jìn)行類型轉(zhuǎn)換訪問(wèn)到不同的成員。

實(shí)現(xiàn)

內(nèi)存對(duì)齊

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

參考 ngx_align 值對(duì)齊宏 分析,ngx_align_ptr 同理

創(chuàng)建內(nèi)存池

max 的最大值為 4095,當(dāng)從內(nèi)存池中申請(qǐng)的內(nèi)存大小大于 max 時(shí),不會(huì)從小塊內(nèi)存中進(jìn)行分配。

ngx_uint_t  ngx_pagesize = getpagesize();  // linux 上是 4096
#define NGX_POOL_ALIGNMENT 16
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)  // 4095

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);  // 16 字節(jié)對(duì)齊申請(qǐng) size 大小的內(nèi)存
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  // 設(shè)置可分配內(nèi)存的起始處
    p->d.end = (u_char *) p + size;                 // 設(shè)置可分配內(nèi)存的終止處
    p->d.next = NULL;
    p->d.failed = 0;                                // 內(nèi)存分配失敗次數(shù)

    size = size - sizeof(ngx_pool_t);               // 設(shè)置小塊內(nèi)存可分配的最大值(小于 4095)
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;                                 // 設(shè)置起始分配內(nèi)存池
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

內(nèi)存池創(chuàng)建后的結(jié)構(gòu)邏輯如圖所示:

Nginx 內(nèi)存池似懂非懂?一文帶你看清高性能服務(wù)器內(nèi)存池

 

內(nèi)存申請(qǐng)

申請(qǐng)的內(nèi)存塊以 max 作為區(qū)分

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

小塊內(nèi)存申請(qǐng)

current 指向每次申請(qǐng)內(nèi)存時(shí)開始檢索分配的小塊內(nèi)存池,而 ngx_palloc_small 的參數(shù) pool 在內(nèi)存池沒有回收時(shí),是固定不變的。

static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    p = pool->current;  // 從 current 處開始分配合適的內(nèi)存

    do {
        m = p->d.last;

        if (align) {  // 是否需要內(nèi)存對(duì)齊
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        // 當(dāng)前小塊內(nèi)存池的剩余容量滿足申請(qǐng)的內(nèi)存
        if ((size_t) (p->d.end - m) >= size) {
            p->d.last = m + size;

            return m;  // 一旦滿足分配直接退出
        }

        p = p->d.next;  // 不滿足的情況下尋找下一個(gè)小塊內(nèi)存池

    } while (p);

    return ngx_palloc_block(pool, size); // 沒有滿足分配的內(nèi)存池,再申請(qǐng)一個(gè)小塊內(nèi)存池
}

當(dāng)在小塊內(nèi)存池中找到了合適的內(nèi)存后的結(jié)構(gòu)如下:

Nginx 內(nèi)存池似懂非懂?一文帶你看清高性能服務(wù)器內(nèi)存池

 

當(dāng)沒有小塊內(nèi)存池滿足申請(qǐng)時(shí),會(huì)再申請(qǐng)一個(gè)小塊內(nèi)存池來(lái)滿足分配,在設(shè)置完 last 和 end 兩個(gè)內(nèi)存指示器后,對(duì)從 current 開始的內(nèi)存池成員 failed 進(jìn)行自增操作,并且當(dāng)這個(gè)內(nèi)存池的 failed 分配次數(shù)大于 4 時(shí),表面這個(gè)內(nèi)存分配失敗的次數(shù)太多,根據(jù)經(jīng)驗(yàn)應(yīng)該下一次分配可能還是失敗,所以直接跳過(guò)這個(gè)內(nèi)存池,移動(dòng) current。


新的內(nèi)存塊插入至內(nèi)存池鏈表的尾端。

#define NGX_ALIGNMENT   sizeof(unsigned long)  // 8

static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    psize = (size_t) (pool->d.end - (u_char *) pool);  // 每一個(gè)內(nèi)存池的大小都相同

    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);  // 16 字節(jié)對(duì)齊申請(qǐng)
    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;

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

    p->d.next = new;  // 尾插法插入至鏈表末端

    return m;
}

分配一塊內(nèi)存池后邏輯結(jié)構(gòu)如下:

Nginx 內(nèi)存池似懂非懂?一文帶你看清高性能服務(wù)器內(nèi)存池

 

大塊內(nèi)存申請(qǐng)

大塊內(nèi)存是通過(guò) large 連接的,并且都屬于 ngx_create_pool 返回的 ngx_pool_t 結(jié)構(gòu)。malloc 分配的內(nèi)存由一個(gè) ngx_pool_large_t 節(jié)點(diǎn)來(lái)掛載,而這個(gè) ngx_pool_large_t 節(jié)點(diǎn)又是從小塊內(nèi)存池中分配的。

  • 為避免large鏈表長(zhǎng)度過(guò)大導(dǎo)致在遍歷尋找空閑掛載節(jié)點(diǎn)耗時(shí)過(guò)長(zhǎng),限制了遍歷的節(jié)點(diǎn)為3,如果沒有滿足要求則直接分配
  • 頭插法 插入至large鏈表中,新的節(jié)點(diǎn)后面也是最先被訪問(wèn)
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);  // 調(diào)用 malloc
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {  // 從large 中鏈表中找到 alloc 為 NULL 的節(jié)點(diǎn),將分配的內(nèi)存掛在該節(jié)點(diǎn)上
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

        if (n++ > 3) {  // 為了避免過(guò)多的遍歷,限制次數(shù)為 0
            break;
        }
    }

    // 當(dāng)遍歷的 ngx_pool_large_t 節(jié)點(diǎn)中 alloc 都有指向的內(nèi)存時(shí),從小塊內(nèi)存中分配一個(gè) ngx_pool_large_t 節(jié)點(diǎn)用于掛載新分配的大內(nèi)存
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;  // 頭插法 插入至大塊內(nèi)存鏈表中
    pool->large = large;

    return p;
}

第一次大塊內(nèi)存分配后的結(jié)構(gòu)如下:

Nginx 內(nèi)存池似懂非懂?一文帶你看清高性能服務(wù)器內(nèi)存池

 

完整內(nèi)存池結(jié)構(gòu)邏輯

  • 所有的內(nèi)存池結(jié)構(gòu)都通過(guò) d.next 連接
  • 前兩個(gè)內(nèi)存池結(jié)構(gòu)的 current 都指向第三個(gè)內(nèi)存池結(jié)構(gòu)
  • 所有的 ngx_pool_large_t 節(jié)點(diǎn)都是從小內(nèi)存池中分配的
  • 所有的 ngx_pool_large_t 節(jié)點(diǎn)都是連接在首個(gè)內(nèi)存池結(jié)構(gòu)上的
  • ngx_pool_large_t 節(jié)點(diǎn)的 alloc 被釋放但 ngx_pool_large_t 節(jié)點(diǎn)不回收
Nginx 內(nèi)存池似懂非懂?一文帶你看清高性能服務(wù)器內(nèi)存池

 

總結(jié)

ngx_pool_t 內(nèi)存分配方面

  • 通過(guò) current 和 d.next 來(lái)訪問(wèn)其他的內(nèi)存池結(jié)構(gòu)
  • 插入方式
    • 小塊內(nèi)存池通過(guò)尾插法插入至內(nèi)存池鏈表的尾端
    • 大塊內(nèi)存通過(guò)頭插法插入至large鏈表的首部
  • 限制次數(shù)
    • 小內(nèi)存分配失敗(failed)次數(shù)大于4次后就不再作為分配內(nèi)存的池子了
    • 大內(nèi)存只尋找 large 鏈表中前三節(jié)點(diǎn)是否可以掛載新分配的內(nèi)存
  • 內(nèi)存對(duì)齊,多處內(nèi)存對(duì)齊減少內(nèi)存跨 cache 的數(shù)量

其實(shí)總體而言這是一個(gè)比較簡(jiǎn)單的內(nèi)存池了,還是有一些內(nèi)存浪費(fèi)的地方,限制次數(shù) 可以說(shuō)明這個(gè)情況,不過(guò)這也是在簡(jiǎn)單、高效和內(nèi)存分配上的一個(gè)平衡了

分享到:
標(biāo)簽:內(nèi)存
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定