1. 程式人生 > >Nginx記憶體池的初步分析

Nginx記憶體池的初步分析

最近對Nginx進行了一些簡單的剖析,要是剖析的不好希望大家見諒,本人水平有限哦

我們先看這麼一副圖
nginx記憶體池資料結構邏輯圖
在呼叫這個圖片之前我想先首先宣告一下這幅圖的作者啊,我可不想打著盜版的聲音去用這幅圖哦= =!!!感謝這幅圖的作者rainx!

好,下面我們來通過這個圖來對整個記憶體池進行分析吧。
我們通過程式碼的對比來分析這幅圖

//記憶體池頭部結構 
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; };

其實我覺得我們先應該通過這個結構體和這幅圖來了解一下nginx記憶體池,其實所謂的一個記憶體池是通過一個連結串列來進行管理的,而ngx_pool_t就是對一個節點進行管理的結構體。

ngx_pool_data_t d //這是對這一塊ngx_pool_t的資料塊的一個管理
size_t max //資料塊的大小,也就是你所分配的這一塊節點資料區域的大小
ngx_pool_t *current //當前可分配的ngx_pool_t
ngx_pool_large_t *large //大於max的用大塊記憶體區域儲存
ngx_pool_cleanup_t *cleanup //用於清除某些檔案描述符或者其它的特殊結構

typedef struct {
    u_char               *last;//當前記憶體池分配到此處,即下一次分配從此處開始
    u_char               *end;//記憶體池結束位置
    ngx_pool_t           *next;//記憶體池裡面有很多塊記憶體,這些記憶體   
                               //塊就是通過該指標連成連結串列的 
    ngx_uint_t            failed;//記憶體池分配失敗次數
} ngx_pool_data_t;

u_char *last //指向可以分配記憶體空間的地方,last實際上是作為已經分配的記憶體空間的末尾,而下一次資料分配就要從last開始到end,當然要是在分配的空間小於max的情況下,假如下一次所要求分配的空間大於max,那麼則開闢大塊記憶體
u_char *end //資料域最末尾的指標
ngx_pool_t *next //指向下一塊ngx_pool_t,這是在記憶體分配不夠,開闢了下一個ngx_pool_t的前提下
ngx_uint_t failed //假如我們的分配的記憶體空間過於的小,超過4次在此塊ngx_pool_t分配不成功,那麼我們的current就會指向下一個ngx_pool_t,這個就是記錄失敗次數的一個記錄器

//管理大塊記憶體
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;//下一塊
    void                 *alloc;
};

我們在這裡強調一下,大塊記憶體實際上是指在ngx_pool_t中大於max的需要分配的節點,當然每次分配都是在current節點中,ngx_pool_t中的large指標負責將這些節點串聯起來
void *alloc //當然指的就是大塊記憶體的分配區域

struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;//處理特殊資料的回撥函式
    void                 *data;//指向要清除的資料
    ngx_pool_cleanup_t   *next;//下一個cleanup callback  
};

當然我想說這個結構體沒啥好介紹的,當我們在記憶體池中加入打開了些檔案描述符在釋放前需要關閉這些檔案描述符或者一些特殊結構需要釋放的時候,我們會呼叫ngx_pool_cleanup_s中的handler函式,當然這也是一個節點,我們需要在處理這些特殊的結構或者檔案描述符的時候用特定的回撥函式handler,我們需要把這些handler串聯起來,其實跟ngx_pool_large_s差不多,也是在當前的current下,而串聯起來這些節點的是cleanup指標

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
分割線
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
好啦,我能說nginx中記憶體池大致的結構我已經剖析完了,現在我們來分析一些對它的操作的函式:

void *ngx_alloc(size_t size, ngx_log_t *log);
void *ngx_calloc(size_t size, ngx_log_t *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);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

咱們來一個個分析好吧:

void *
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;
//可以看出來底層呼叫了malloc
    p = malloc(size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "malloc(%uz) failed", size);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);

    return p;
}

ngx_alloc我們可以把它看成一個底層呼叫函式,直接呼叫的malloc,當然這裡是看起來跟記憶體池沒有任何關係了,但是它應該算是記憶體池的底層呼叫函式

/******************************************************/

void *
ngx_calloc(size_t size, ngx_log_t *log)
{
    void  *p;
//帶清零作用的
    p = ngx_alloc(size, log);

    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

這裡當然也是相當於一個底層呼叫函式,它只不過相當於從堆上分配了一塊記憶體附帶清零的作用,僅此而已
/******************************************************/

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);
    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;
    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_alloc()函式啊,不過沒關係,我只能說它其實並沒有呼叫ngx_alloc()這個介面,而它呼叫了另一個介面:
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
我們可以看到這個介面和上面的介面都不一樣,那麼可以進這個接口裡面去看一下:

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;
    int    err;
//呼叫posix_memalign( )成功時會返回size位元組的動態記憶體
//並且這塊記憶體的地址是alignment的倍數
//引數alignment必須是2的冪,還是void指標的大小的倍數
    err = posix_memalign(&p, alignment, size);
    //...
return p;
}

它裡面涵蓋了一個叫做posix_memalign(&p, alignment, size)的函式,那麼這個函式的作用是幹嘛的呢?我們知道POSIX標明瞭通過malloc(), calloc(), 和 realloc()返回的地址對於任何的C型別來說都是對齊的。在Linux中,這些函式返回的地址在32位系統是以8位元組為邊界對齊,在64位系統是以16位元組為邊界對齊的,而posix_memalign(&p, alignment, size)的作用就是動態對齊。而在這裡它則是按alignment大小來進行對齊的,其實也可以看一下這個玩意的大小吧:
#define NGX_POOL_ALIGNMENT 16

嘿嘿,看到沒有,它的大小是16哦!!!也就是說它是按16來進行對齊的,至於具體的posix_memalign(&p, alignment, size)咋用,希望大家自己回去查一下吧。

當然我們也看過了ngx_memalign(…)函式,我們繼續來看一下ngx_create_pool(…)函式,我們可以看到它對max進行了一個初始化:
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

其實我一直很好奇這個地方啊,這個地方的max是經過傳進來的值決定的,而假如,傳進來的小於NGX_MAX_ALLOC_FROM_POOL,那麼按size的大小來計算哦,這裡我們再來順帶看一下NGX_MAX_ALLOC_FROM_POOL是什麼:
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)

對哦,這裡是一個巨集定義,定義的大小是4K-1個大小,那麼我們姑且算在小於4K的情況下它按size大小來定義吧,其實我認為這裡的唯一的好處就是它按分頁機制去處理了一個最大記憶體,假如我不到4K我能節約性的從記憶體中拿合適的記憶體,當大於4K的時候我們就可以不考慮頁面大小的問題了,直接就大塊記憶體走起,當然,考不考慮4K不是我們說了算,而是剛開始申請的size大小說了算哦。

我一直覺得這個地方有個BUG,那就是假如傳進來的size < sizeof(ngx_pool_t)怎麼辦?不就成負數了?那麼這個程序會不會因此掛掉?這個我就是不是很清楚了!!
/******************************************************/
接下來看看ngx_destroy_pool(ngx_pool_t *pool)

void ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

//先考慮cleanup函式,但是貌似我並沒有找見有關的回撥
    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);
        }
    }
    //....
        for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

我們可以從這裡看到它先呼叫了ngx_pool_t中的clean中的handler函式來清理一些它可能開啟的檔案描述符或者特殊結構,此時再free掉那些大塊結構。當然,最後是把自己本身的節點給free掉了
/******************************************************/
void ngx_reset_pool(ngx_pool_t *pool)

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

    for (p = pool; p; p = p->d.next) {
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

這個函式呢是記憶體池重置函式,它會將大塊記憶體刪掉並且把裡面的東西給初始化哦!!
/**************************************************/
下面說的就是void *ngx_palloc(ngx_pool_t *pool, size_t size)了吧

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

我們可以看到這裡是從記憶體池中分配記憶體的一個函式,當我們前面建立好記憶體池之後,我們可以通過這裡在一個ngx_pool_t中的資料段中分配節點,我們可以看到,它就只用了last和end兩個標記位去標記剩餘空間的大小,當我們空間大於max的時候直接用ngx_palloc_large(pool, size)呼叫大塊記憶體,而假如小於max卻在後面的ngx_pool_t中不夠用的話則會呼叫ngx_palloc_block(pool, size)函式,其實內部就是重新申請了一塊ngx_pool_t記憶體,呼叫的還是ngx_memalign(…)函式,有興趣的朋友自己看看了。

當然後面還有一個
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
當然,這個函式和void *ngx_palloc(ngx_pool_t *pool, size_t size)最大的區別當然就是:
void *ngx_palloc(ngx_pool_t *pool, size_t size)是對齊的
void *ngx_palloc(ngx_pool_t *pool, size_t size)是不對齊的

/******************************************************/
好了總結了那麼多,覺得心好累啊!!!

相關推薦

Nginx記憶體初步分析

最近對Nginx進行了一些簡單的剖析,要是剖析的不好希望大家見諒,本人水平有限哦 我們先看這麼一副圖 在呼叫這個圖片之前我想先首先宣告一下這幅圖的作者啊,我可不想打著盜版的聲音去用這幅圖哦= =!!!感謝這幅圖的作者rainx! 好,下面我們來通過

nginx 記憶體分析

# nginx 記憶體池 ngx_pool_t nginx 是自己實現了記憶體池的,所以在nginx ngx_pool_t 這個結構也隨處可見,這裡主要分析一下記憶體池的分配邏輯。 記憶體池實現了包括小塊記憶體、大塊記憶體和清理資源幾種資源的處理,應該來說覆蓋了絕大數的使用場景了。 ## 相關結構定義

[nginx] nginx記憶體ngx_pool_t的介紹

本文詳細介紹nginx中記憶體池的設計和實現。   nginx pool 由 小記憶體拉鍊、大記憶體拉鍊、回撥函式拉鍊組成。建立的第一個ngx_pool_s頭部,會作為整個pool的head,儲存一些拉鍊資訊。 1 建立一個ngx_pool: struct ngx_po

Netty記憶體原理分析

為了避免頻繁的記憶體分配給系統帶來負擔以及GC對系統性能帶來波動,Netty4提出了全新的記憶體管理,使用了全新的記憶體池來管理記憶體的分配和回收。記憶體池這塊的程式碼複雜難懂,而且幾乎沒有註釋閱讀起來比較費力,特別是以前沒有接觸過記憶體分配演算法的閱讀起來更為蛋疼,好在經

[nginx] 記憶體與基本容器

一、nginx記憶體池 與STL的空間配置器類似,有兩級配置,僅提供了釋放大記憶體塊的介面,小記憶體只分配不釋放,要麼釋放整個記憶體池(nginx程序中有茫茫多記憶體池) 記憶體池由三部分組成:小塊記憶體形成的連結串列(並不是ngx_list_t

淺談nginx記憶體(四)

1.Nginx的buf緩衝區資料結構,主要用來儲存非常大塊的記憶體,ngx_buf_s資料結構也貫穿了整個nginx。 Nginx的緩衝區設計也是非常靈活的: (1)可以自定義管理業務層面的緩衝區連結串列。 (2)可以將空閒的緩衝區連結串列交還給記憶體池pool->c

Windows記憶體詳解(四)OD記憶體斷點初步分析

記憶體斷點原理:        記憶體斷點原理,通過將記憶體斷點所在記憶體頁的屬性修改為記憶體斷點屬性(non-access or non-writable),程式執行時,對目標記憶體頁中所有資料的訪問或寫,都會丟擲異常,OD通過截獲此異常,然後對比,儲存在某一記憶體的

nginx原始碼分析記憶體結構ngx_pool_t及記憶體管理

本部落格(http://blog.csdn.net/livelylittlefish)貼出作者(阿波)相關研究、學習內容所做的筆記,歡迎廣大朋友指正!Content0. 序1. 記憶體池結構1.1 ngx_pool_t結構1.2 其他相關結構1.3 ngx_pool_t的邏輯

nginx源代碼分析之內存實現原理

delete align 業務 -s 首部 ges hand 重置 mar 建議看本文檔時結合nginx源代碼。1.1 什麽是內存池?為什麽要引入內存池?內存池實質上是接替OS進行內存管理。應用程序申請內存時不再與OS打交道。而是從內存池中申請內存或者釋放內存到內存

swoole之memoryGlobal記憶體分析

記憶體池的作用: 直接使用系統呼叫malloc會有如下弊端: 頻繁分配記憶體時會產生大量記憶體碎片 頻繁分配記憶體增加系統呼叫開銷 容易造成記憶體洩漏 記憶體池是預先申請一定數量的,大小相等的記憶體塊作為預備使用;當需要時向記憶體池分出一部分記憶體,若記憶體塊不夠使用時再向系統申請

STL原始碼分析記憶體

前言 上一節只分析了第二級配置器是由多個連結串列來存放相同記憶體大小, 當沒有空間的時候就向記憶體池索取就行了, 卻沒有具體分析記憶體池是怎麼儲存空間的, 是不是記憶體池真的有用不完的記憶體, 本節我們就具體來分析一下 記憶體池 static data template的初始

菜鳥學習Nginx記憶體

從今天開始深入介紹Nginx框架。 首先來談談我對《深入理解Nginx模組開發與架構解析》看法,這本書應該是到目前為止,市面寫的最詳細,最充實的書籍(沒有之一),值得擁有。然而此書對於一個小白來說,並不太適合,此書適合有相關使用經驗或者開發經驗,適合於進一步深造的同學。如果是小白,建議先瀏覽

Nginx學習之路(七)NginX中的記憶體管理之---Nginx中的記憶體

上一篇文章說到了Nginx中的記憶體對齊機制和記憶體分頁機制,今天就來說下Nginx中的記憶體池,記憶體池是一個使用非常廣泛的技術,在web伺服器的高併發情況下可能存在平凡的malloc()和free()過程,通過記憶體池的方式可以將這一過程的開銷極大程度的減少,Nginx的

nginx原始碼學習(二) 記憶體結構 ngx_pool_t

1,nginx的記憶體池介紹     為了方便系統模組對記憶體的使用,方便記憶體的管理,nginx自己實現了程序池的機制來進行記憶體的分配和釋放, 首先nginx會在特定的生命週期幫你    統一建立記憶體池,當需要進行記憶體分配的時候統一通過記憶體池中的記憶體進行分配,最

nginx記憶體設計

記憶體池結構體 struct ngx_pool_s { ngx_pool_data_t d; /* 記憶體池連結串列頭指標 */ size_t max; /* 一個閾值,決定是用malloc分配記憶體

nginx原始碼剖析--記憶體

作者:July、dreamice、阿波、yixiao。 出處:http://blog.csdn.net/v_JULY_v/。 引言         Nginx(發音同 engine x)是一款輕量級的Web 伺服器/反向代理伺服器及電子郵件(IMAP/P

記憶體,程序,和執行緒的比較分析

池的概念 由於伺服器的硬體資源“充裕”,那麼提高伺服器效能的一個很直接的方法就是以空間換時間,即“浪費”伺服器的硬體資源,以換取其執行效率。這就是池的概念。池是一組資源的集合,這組資源在伺服器啟動之初就完全被建立並初始化,這稱為靜態資源分配。當伺服器進入正式執行階段,即開始

nginx原始碼學習----記憶體

1、基本結構 先來學習一下nginx記憶體池的幾個主要資料結構:[見:./src/core/ngx_palloc.h/.c]     ngx_pool_data_t(記憶體池資料塊結構) 1: typedef struct { 2: u_char *las

ThreadPoolExecutor線程分析和使用

pro rep interrupt rup bsp roc 停止 可用 method 1. 引言 合理利用線程池能夠帶來三個好處。 第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。 第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立

Nginx Log日誌統計分析常用命令

php baidu netstat 顯示 acc 嗅探 性能分析 dump class IP相關統計 統計IP訪問量(獨立ip訪問數量) awk ‘{print $1}‘ access.log | sort -n | uniq | wc -l 查看某一時間段的IP訪問量