Nginx內存管理詳解

Nginx內存管理詳解

目錄:linux

1.Nginx內存管理介紹nginx

2.Nginx內存池的邏輯結構程序員

3.Nginx內存池的基本數據結構web

4.內存池基本操做介紹服務器

5.內存池管理源碼詳解數據結構

6.內存池使用源碼詳解tcp

7.小結ide

 

 

 

1.Nginx內存管理介紹

  在C/C++語言程序設計中,一般由程序員本身管理內存的分配和釋放,其方式一般是malloc(free)和new(delete)等API。這樣作的缺點在於:因爲所申請內存塊的大小不定,當頻繁使用時會形成大量的內存碎片從下降性能。一般咱們所使用的解決辦法就是內存池函數

  什麼是內存池呢?內存池就是在真正使用內存以前,先申請分配必定數量的、大小相等(通常狀況下)的內存塊留做備用。當有新的內存需求時,就從內存池中分出一部份內存塊,若內存塊不夠再繼續申請新的內存。而不是每次須要了就調用分配內存的系統API(如malloc)進行申請,每次不須要了就調用系統釋放內存的API(如free)進行釋放。這樣作的一個顯著優勢是,使得內存分配效率獲得提高。所以使用內存池的方式對程序所使用的內存進行統一的分配和回收,是當前最流行且高效的內存管理方法,可以在很大程度上下降內存管理的難度,減小程序的缺陷,提升整個程序的穩定性。性能

  經過減小頻繁的內存申請和釋放能夠提高效率很容易理解,那麼內存池到底是怎麼提升程序的穩定性的呢?咱們知道在C/C++語言中,並無提供直接可用的垃圾回收機制,所以在程序編寫中, 一個特別容易發生的錯誤就是內存泄露,對於運行時間短,內存需求小的程序來講,泄露一點內存除了影響程序運行效率以外可能並不會形成多大的問題。可是相似於Ngnix這樣須要長期運行的web服務器程序來講,內存泄露是一件很是嚴重的災難,這會使得程序因爲內存耗盡而崩潰,重啓以前再也不可以提供相應的web服務。還有一種狀況就是當內存分配與釋放的邏輯在程序中相隔較遠時,很容易發生內存被釋放兩次乃至屢次的狀況。使用內存池使得咱們在開發程序時,只用關心內存的分配,而釋放就交給內存池來完成。

  那麼內存池在Nginx中到底是怎麼使用的呢?一般咱們對於每一個請求或者鏈接都會創建相應的內存池,創建好內存池以後,咱們能夠直接從內存池中申請所須要的內存,而不用去管內存的釋放,惟一須要注意的就是當內存池使用完成以後須要記得銷燬內存池。此時,內存池會調用相應的數據清理函數(若是有的話),以後會釋放在內存池中管理的內存。

  你們可能會問,既然申請的內存在內存池銷燬的時候纔會被釋放,這不會存在內存的浪費麼?畢竟使用完了再也不須要的內存爲何不當即釋放而非要等到銷燬內存池時才釋放呢?確實存在這個問題,不過你們不用擔憂。在Nginx中,對於大塊內存可使用ngx_pfree()函數提早釋放。而且因爲Nginx是一個純粹的web服務器,而web服務器一般使用的協議是Http協議,而且在傳輸層使用的是Tcp協議,咱們知道每個tcp鏈接都是由生命週期的,所以基於tcp的http請求都會有一個很短暫的生命週期。對於這種擁有很短暫生命週期的請求,咱們所創建的內存池的生命週期也相應會很短暫,所以其所佔用的內存資源很快就能夠獲得釋放,不會出現太多的資源浪費的問題。畢竟工程就是一種折中嘛,咱們須要在內存資源浪費和減低程序內存管理難度、提高效率之間選擇一個合適的權衡。

  說了這麼多,如今就讓咱們開始研究和學習Nginx內存管理的機制和源碼吧。注:本文的講解都是基於nginx-1.10.3版本。

 

 

 

2.Nginx內存池的邏輯結構

  前面提到Nginx內存管理機制其實就是內存池,其底層實現就是一個鏈表結構。咱們須要對內存池進行管理和分配,依賴的就是ngx_pool_t結構體,能夠認爲該結構就是內存池的分配管理模塊。那麼內存池的邏輯結構到底是什麼樣呢?其實就是一個ngx_pool_t結構體,在這個結構體中包含了三個部分:小塊內存造成的單鏈表,大塊內存造成的單鏈表和數據清理函數造成的單鏈表。先給出一張整個內存池內部實現的結構圖,方便你們理解。具體如圖2.1所示:

 

 

圖2.1 Nginx內存池示意圖

 

   圖2.1完整的展現了ngx_pool_t內存池中小塊內存、大塊內存和資源清理函數鏈表間的關係。圖中,內存池預先分配的剩餘空閒內存不足以知足用戶申請的內存需求,致使又分配了兩個小內存池。其中原內存池的failed成員已經大於4,因此current指向了第2塊小塊內存池,這樣當用戶再次從小塊內存池中請求分配內存空間時,將會直接忽略第1塊小內存池,從第2塊小塊內存池開始遍歷。從這裏能夠看到,咱們使用的內存池確實存在當failed成員大於4以後不能利用其空閒內存的資源浪費現象(因爲current指針後移)。值得注意的是:咱們的第二、3塊小塊內存池中只包含了ngx_pool_t結構體和數據區,並不包含max、current、...、log。這是因爲後續第1塊小內存池已經包含了這些信息,後續的小塊內存池沒必要在浪費空間存儲這些信息。咱們在第6小節:內存池的使用中將會有所介紹。圖中共分配了3個大塊內存,其中第二塊的alloc爲NULL(提早調用了ngx_pfree())。圖中還掛在了兩個資源清理方法。提醒一下的是:若是在這裏沒有弄清楚,沒有關係,看完了後面的部分再回過頭來理解這個示意圖就可以很好的理解了。這裏只是先給出一個歸納性的Nginx內存池邏輯結構的介紹,先給你們留下一個大概的印象。

 

 

 

3.Nginx內存池的基本數據結構

本部分主要介紹內存池中重要的數據結構,主要是ngx_pool_t,而後介紹ngx_pool_t中三個重要數據結構:ngx_pool_data_t,ngx_pool_large_t和ngx_pool_cleanup_t。

 

(1)ngx_pool_t

  咱們能夠在Nginx的源碼的src/core/目錄下的nax_palloc.h頭文件中看到:

 

1 struct ngx_pool_s { 2  ngx_pool_data_t d; 3  size_t max; 4     ngx_pool_t           *current; 5     ngx_chain_t          *chain; 6     ngx_pool_large_t     *large; 7     ngx_pool_cleanup_t   *cleanup; 8     ngx_log_t            *log; 9 };

 

   而且在src/core/ngx_core.h中:

typedef struct ngx_pool_s        ngx_pool_t;

 

下面將具體講解ngx_pool_t結構體中每一個成員的含義和用途:

 d:ngx_pool_data_t結構體,描述內存池中的小塊內存。當小塊內存不足時,會再分配一個ngx_pool_t(裏面含有一個新分配且未使用的小塊內存空間和用於管理這塊內存空間的ngx_pool_data_t結構體)。這些小塊內存塊之間經過d中的next成員連接造成的單鏈表。掛在d成員上。

 

max:評估申請內存屬於小塊仍是大塊的標準,在x86上默認是4095字節。

 

current:多個小塊內存構成單鏈表時,指向分配內存時遍歷的第一個小塊內存。

 

chain:與內存池關係不大,略過。

 

large:ngx_pool_large_t結構體當用戶申請的內存空間大於max時,就會分配大塊內存。而多個大塊內存之間是經過ngx_pool_large_t中的next成員連接造成的單鏈表。掛在large成員上。

 

cleanup:ngx_pool_cleanup_t結構體,全部待清理的資源(例如須要關閉或者刪除的文件)以ngx_pool_cleanup_t對象中的next成員連接造成單鏈表。掛在cleanup成員上。

 

log:內存池中執行時輸出日誌的地方。

 

 

(a).ngx_pool_data_t

  咱們能夠在Nginx的源碼的src/core/目錄下的nax_palloc.h頭文件中看到:

 

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

 

下面將具體講解ngx_pool_data_t結構體中每一個成員的含義和用途: 

last:指向小塊內存中未分配的空閒內存的首地址。

 

end:指向當前小塊內存的尾部。

 

next:同屬於一個內存池的多個小塊內存之間,經過next成員連接造成單鏈表。

 

failed: 每當當前的小塊內存因爲空閒部分較少而不能知足用戶提出的內存申請請求時,failed成員就會加1。當failed成員大於4後,ngx_pool_t的current成員就會移向下一個小塊內存,在之後分配內存時,將從下一個小塊內存開始遍歷。

 

 

(b).ngx_pool_large_t

  咱們能夠在Nginx的源碼的src/core/nax_palloc.h頭文件中看到:

 

typedef struct ngx_pool_large_s  ngx_pool_large_t;

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

 

下面將具體講解ngx_pool_large_t結構體中每一個成員的含義和用途:

next:全部大塊內存經過next指針連接在一塊兒造成單鏈表。

 

alloc:指向分配的大塊內存,後面咱們將會看到大塊內存底層是經過ngx_alloc分配,ngx_free釋放。釋放完了以後賦值爲NULL。

 

 

(c).ngx_pool_cleanup_t

   咱們能夠在Nginx的源碼的src/core/nax_palloc.h頭文件中看到:

 

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

 

 下面將具體講解ngx_pool_cleanup_t結構體中每一個成員的含義和用途:

handler:初始化爲NULL,須要設置的清理函數。

 

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

 

根據上面的聲明,能夠看出,ngx_pool_clean_pt是一個函數指針,有一個通用型的參數data,返回類型爲void。後面咱們會看到當銷燬內存池的時候,底層會遍歷掛在cleanup成員上的單鏈表上的各個節點,調用各節點的數據清理函數完成相應的清理操做。這是經過回調函數實現的。

 

data:用於向數據清理函數傳遞的參數,指向待清理的數據的地址,若沒有則爲NULL。咱們能夠經過ngx_pool_cleanup_add函數添加數據清理函數,當其中的參數size>0時,data不爲NULL。

 

next:用於連接全部的數據清理函數造成單鏈表。由ngx_pool_cleanup_add函數設置next成員,用於將當前ngx_pool_cleanup_t(由ngx_pool_cleanup_add函數返回)添加到cleanup鏈表中。

 

 

 

4.內存池基本操做介紹

  這一部分主要簡單講解與內存池管理有關的基本操做(共15個)。主要包括四個部分:(a).內存池操做 (b).基於內存池的分配、釋放操做 (3).隨着內存池釋放同步釋放資源的操做 (4).與內存池無關的分配、釋放操做。在第5和第6節中,咱們會對部分經常使用內存池的操做進行代碼上的詳細介紹。

 

(a).內存池操做:

 

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

 

  ngx_create_pool

  建立內存池,其參數size爲整個內存的大小,包括結構管理(ngx_pool_t)和後續可分配的空閒內存。這意味着,size必須大於等於sizeof(ngx_pool_t),一般在32位的系統是是40字節,後面咱們介紹源碼時會詳細的介紹。一般size的默認大小爲NGX_DEFAULT_POOL_SIZE(#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)),能夠看到爲16k。不用擔憂其不夠用,由於當不夠用時,Nginx會對內存池進行內存空間的擴展,也就是申請一個新的內存池(鏈表)節點(程序中成爲一個block),而後掛在內存池的最後面。

 

  ngx_destory_pool

  銷燬內存池,它會執行經過ngx_pool_cleanup_add函數添加的各類資源清理方法,而後釋放大塊內存,最後把整個pool分配的內存釋放掉。

 

  ngx_reset_pool

  重置內存池,即將在內存池中原有的內存釋放後繼續使用。後面咱們會看到,這個方法是把大塊的內存釋放給操做系統,而小塊的內存則在不釋放的狀況下複用。

 

 

(b).基於內存池的分配、釋放操做

 

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);
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);

  

      ngx_palloc

  分配地址對齊的內存。內存對齊能夠減小cpu讀取內存的次數,代價是存在一些內存浪費。

 

  ngx_pnalloc

  同ngx_palloc,區別是分配內存時不考慮對齊。

 

  ngx_pcalloc

  同ngx_palloc,區別是分配完對齊的內存後,再調用memset所有初始化爲0。

 

  ngx_pmemalign

  按參數alignment進行地址對齊來分配內存。注意,這樣分配的內存無論申請的size有多小,都不會使用小塊內存,它們直接從進程的堆中分配,並掛在大塊內存組成的large單鏈表中。

 

  ngx_pfree

  提早釋放大塊內存。因爲其實現是遍歷large單鏈表,尋找ngx_pool_large_t對應的alloc成員後調用ngx_free(alloc),其實是直接調用free(alloc),釋放內存給操做系統,將ngx_pool_large_t移出鏈表並刪除。效率不高。

 

 

(c).隨着內存池釋放同步釋放資源的操做

 

ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
void ngx_pool_cleanup_file(void *data);
void ngx_pool_delete_file(void *data);

    

    ngx_pool_cleanup_add

  添加一個須要在內存釋放時同步釋放的資源。該方法會返回一個ngx_pool_cleanup_t結構體,而咱們獲得該結構體後須要設置ngx_pool_cleanup_t的handler成員爲釋放資源時執行的方法。ngx_pool_clean_add的參數size,當它不爲0時,會分配size大小的內存,並將ngx_pool_cleanup_t的data成員指向該內存,這樣能夠利用這段內存傳遞參數,供資源清理函數使用。當size爲0時,data將爲NULL。

 

  ngx_pool_run_cleanup_file

  在內存釋放前,若是須要提早關閉文件(調用ngx_pool_cleanup_add添加的文件,同時ngx_pool_cleanup_t的handler成員被設置爲ngx_pool_cleanup_file),則調用該方法。

 

  ngx_pool_cleanup_file

  以關閉文件來釋放資源的方法,能夠設置到ngx_pool_cleanup_t的handler成員。

 

  ngx_pool_delete_file 

  以刪除文件來釋放資源的方法,能夠設置到ngx_pool_cleanup_t的handler成員。

 

 

(d).與內存池無關的分配、釋放操做

 

void *ngx_alloc(size_t size, ngx_log_t *log);
void *ngx_calloc(size_t size, ngx_log_t *log);

#define ngx_free          free

  

  這部分的聲明和定義實際上並不在src/core/ngx_palloc.h中,而是在/src/os/unix/ngx_alloc.h中。

 

  ngx_alloc

  從操做系統中分配內存,經過調用malloc實現。

 

  ngx_calloc

  從操做系統中分配內存並所有初始化爲0,經過調用malloc和memset實現。

 

  ngx_free

  從上面的宏定義能夠看到,其就是free函數,釋放內存到操做系統。

 

 

 

5.內存池管理源碼詳解

   本部分的源碼能夠在src/core/ngx_palloc.h、src/core/ngx_palloc.c、src/os/unix/ngx_alloc.h和src/os/unix/ngx_alloc.c中找到。內存池的管理主要包括內存池的建立、銷燬以及重置操做。咱們經過對源碼的分析來研究和學習Nginx的內存管理技術。

 

(a).內存池的建立

  建立內存池的操做主要由ngx_create_pool()函數完成,代碼以下:

 

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_memalign()函數申請對齊的內存,其大小爲size個字節。若是內存申請失敗,則返回NULL,不然對ngx_pool_t結構體中的成員進行初始化。在進行初始化以前,讓咱們先討論如下什麼是小塊內存?

 

 

/*
 * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)

 

 

   這是ngx_palloc.h中的一個註釋及宏定義,從中咱們能夠看到在x86系統4095字節是一個標準。由於ngx_pagesize中存放的是當前Nginx服務器運行的系統中一頁內存頁的大小,而在x86的系統上就是4KB。因爲存在減1的關係,這意味着在x86系統上,小於等於4095字節的內存被稱爲小塊內存,而大於4095字節的內存被稱爲大塊內存。固然這並非絕對的,在上述源碼中,咱們看到若是傳遞的參數size知足:size - sizeof(ngx_pool_t) < NGX_MAX_ALLOC_FROM_POOL時,其max的值爲size(小於NGX_MAX_ALLOC_FROM_POOL),而當size不知足上述不等式時,其值爲NGX_MAX_ALLOC_FROM_POOL。也就是說NGX_MAX_ALLOC_FROM_POOL是一個最大的門限,申請的小塊內存的大小應該不超過其大小。在初始化max以後,咱們將last指向分配好的空閒內存空間的首地址,end指向內存池的尾部。並將next初始化爲NULL,failed的值初始化爲0。而後再將current指向這塊內存池的首地址,large和cleanup也被初始化爲NULL,最後返回指向分配好的內存空間的首地址。爲了更加清晰地展現內存池的建立過程,下面將會舉一個例子來講明。可是在這以前,咱們先來分析如下ngx_memalign()函數的實現源碼。

 

  關於ngx_memalign()的細節咱們能夠在src/os/unix/ngx_alloc.c中看到其源碼,前面部分是聲明,後面是定義。以下所示:

 

/*
 * Linux has memalign() or posix_memalign()
 * Solaris has memalign()
 * FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
 * aligns allocations bigger than page size at the page boundary
 */

#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)

void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);

#else

#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)

#endif

 

 

#if (NGX_HAVE_POSIX_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;
    int    err;

    err = posix_memalign(&p, alignment, size);

    if (err) {
        ngx_log_error(NGX_LOG_EMERG, log, err,
                      "posix_memalign(%uz, %uz) failed", alignment, size);
        p = NULL;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "posix_memalign: %p:%uz @%uz", p, size, alignment);

    return p;
}

#elif (NGX_HAVE_MEMALIGN)

void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
    void  *p;

    p = memalign(alignment, size);
    if (p == NULL) {
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                      "memalign(%uz, %uz) failed", alignment, size);
    }

    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
                   "memalign: %p:%uz @%uz", p, size, alignment);

    return p;
}

#endif

 

  咱們還須要知道的就是在linux系統下,分配內存有三個系統調用,若是不考慮內存對齊,則有malloc();若是考慮內存對齊,則有:memalign()和posix_memalign();從ngx_memalign()的具體聲明和實現中,咱們能夠看出這其實一個條件編譯。若是系統定義了NGX_HAVE_POSIX_MEMALIGN,則調用posix_memalign()申請對齊的內存;若是系統定義了NGX_HAVE_MEMALIGN,則調用memalign()申請對齊的內存;而且這兩種內存對齊默認都是基於16字節的。不然直接調用ngx_alloc(),而ngx_alloc()直接調用malloc()申請不對齊的內存。講完了內存池中三種申請內存的方式以後,咱們能夠開始講解建立內存池的實例了。

  好比說咱們須要建立一個大小爲1024字節的內存池做爲一個分配模塊:

 

ngx_pool_t *pool = ngx_create_pool (1024,  log);

 

  爲了方便,咱們不妨假設申請的這塊內存的起始地址爲10。執行完建立內存池的操做後,內存中的分佈狀況如圖5.1所示:

 

圖5.1 建立內存池內存片斷圖

 

  從執行結果能夠看出:建立的內存池總共佔用了1024個字節,起始地址爲10,結束地址爲1034。指向內存池的指針爲pool。last指針爲50(10+40),由於起始地址是10,而ngx_pool_t結構體所佔用的內存空間爲40字節,怎麼計算獲得的呢?其實很簡單,只須要考慮結構體在內存中的對齊問題便可。在x86中(x64中指針在內存中佔用8字節而不是4字節)以下所示:

 

 

typedef struct {
    u_char               *last;//4字節
    u_char               *end;//4字節
    ngx_pool_t           *next;//4字節
    ngx_uint_t            failed;//4字節
} ngx_pool_data_t;


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

 

   

  咱們能夠計算獲得,在x86的系統中ngx_pool_t結構體各個成員變量佔用的空間爲40字節。所以last的值爲50。end的值爲10+1024=1034。max的值爲1024-40=984。current=10。能夠看到:

在物理內存中,申請到的內存空間被分爲了兩部分,前面一部分是ngx_pool_t內存管理結構各個成員變量所佔用的空間,此處爲40字節。後面部分的984字節的空閒空間纔是咱們能夠在後續的程序中真正能夠利用的,用來存放數據的。以上就是Nging內存池建立的主要原理和具體實現。

 

 

(b).內存池的銷燬

  銷燬內存池的工做主要由ngx_destroy_pool()函數完成。代碼以下:

 

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

#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

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

    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 (l = pool->large; l; l = l->next) {
        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;
        }
    }
}

 

  咱們能夠看到,銷燬內存池的主要步驟爲:先經過遍歷掛在cleanup上數據清理函數鏈表,經過回調函數handler作相應的數據清理;中間輸出部分只與調試程序相關,可忽略。而後遍歷掛在large上的大塊內存鏈表,調用ngx_free()函數釋放節點所佔的大塊內存空間;最後,遍歷掛在d->next上的小塊內存池鏈表,釋放小塊內存池(包括管理結構和數據區)佔用的空間,在這一步中,咱們首先清理了第一塊ngx_pool_t(包括了large、cleanup等成員)表明的小塊內存池,而後再清理剩下的其餘小塊內存池。通過以上三個過程,就能夠完成數據清理、釋放整個內存池佔用的內存空間,並銷燬內存池。須要注意的是:因爲內存池的結構,咱們必須最後清理管理結構ngx_pool_t(第一塊小塊內存池),由於若是先清理第一塊ngx_pool_t表明的內存池的話,咱們就找不到掛在large和cleanup上的單鏈表了,由於咱們清理了其單鏈表的第一個節點。

 

 

(c).內存池的重置

  重置內存池,就是將內存池分配到初始分配的狀態。這是由ngx_reset_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;
}

   

  咱們能夠看到,重置內存池十分簡單。首先將掛在large上的大塊內存鏈表上的各個節點釋放掉,並將pool->large賦值爲NULL。以後,將全部小塊內存池構成的單鏈表中的全部節點結尾的last指針重置到剛分配時的位置。小塊內存中存儲的數據並無被釋放,其在之後的內存池使用的過程當中將會被覆蓋更新。這能夠減小內存分配的次數,提高內存重用率。但會浪費一些內存空間。

 

 

 

6.內存池使用源碼詳解

   內存池建立好以後,如何進行使用呢?這些內存使用完了以後是如何進行回收利用的呢?下面的部分將會詳細的介紹內存池的使用。

 

(a).從內存池中申請內存

  在Nginx中,基於內存池的申請方法主要有ngx_palloc、ngx_pnalloc、ngx_pcalloc和ngx_pmemalign共4種方法。而不基於內存池,直接從操做系統中申請內存的主要有ngx_alloc和ngx_calloc共兩種方法。在這一小節中,咱們只講述從內存池中申請內存相關的4中方法。而其餘的部分將會在後面的小節進行講解。

  基於內存池的4中內存申請方法的區別在第4章:內存池API介紹中已經詳細闡述了。此處再也不贅述。

 

(1).ngx_palloc

  下面給出源碼:

 

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

 

  從其實現中,咱們能夠看出,ngx_palloc()總共有兩個參數,第一個是在那個內存池上申請內存(以前咱們曾經提到過一般爲每一個Http請求或者鏈接建立一個內存池,此處須要傳遞的參數就是這些內存池對應的指針),另外一個參數是size,表示申請內存的大小。進入函數後,首先是判斷申請的內存大小和max(小塊內存標準)的關係,若是size<max,就調用ngx_palloc_small()函數申請內存。不然調用ngx_palloc_large()函數申請內存。下面讓咱們先來看ngx_palloc_small()函數的源碼,以下所示:

 

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;

    do {
        m = p->d.last;

        if (align) {
            m = ngx_align_ptr(m, 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);
}

 

  從上述源碼中,咱們能夠看到,該函數從current指向的內存池(小塊內存池鏈表)中開始循環遍歷。在每一次遍歷中,咱們首先得到目前內存池中未分配的空閒內存的首地址last,並賦值給m,而後因爲從ngx_palloc()函數中傳遞過來的align=1,所以調用ngx_align_ptr(),這是個什麼呢?僅今後咱們不能判斷其是函數仍是宏,下面咱們給出其源碼,在src/core/ngx_config.h中,以下所示:

 

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

 

  能夠看出,這是一個宏定義,該操做比較巧妙,用於計算以參數a對齊後的偏移指針p。實際上,咱們最後分配的內存空間就是從對齊後的偏移指針開始的,這可能會浪費少數幾個字節,但卻能提升讀取效率。接着分析ngx_palloc-small()函數中的源碼,在調用完宏ngx_align_ptr(m, NGX_ALIGNMENT)後咱們獲得了以默認參數16對齊的偏移指針m。此時,咱們已經擁有了對齊後的空閒內存地址空間的首地址m和尾部地址end,咱們就能夠計算出該塊內存池(一個block)剩餘的空閒內存空間大小:p->d.end - m。那麼這個剩餘的空閒內存空間是否必定能知足用戶的內存申請請求(size個字節)呢?答案是否認的。所以咱們須要將從current開始的每個小塊內存池的剩餘空閒內存空間和size進行比較,遍歷鏈表直到找到知足申請大小(size個字節)的小塊內存池。若是小塊內存池鏈表上的某塊小塊內存可以知足需求,那麼咱們就將從Nginx的內存池中劃分出內存空間,並更新last的值(將last的值後移size個字節),而後返回m。

  若是遍歷完整個小塊內存池都沒有找到知足申請大小的內存,則程序調用ngx_palloc_block()函數。其源碼以下所示:

 

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

    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;

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

 

  既然當前整個內存池都不能知足用戶內存的申請,而咱們的操做系統明明還有內存可用(資源耗盡的狀況除外),那咱們總不能拒絕用戶的合理請求吧。ngx_palloc_block()函數就是應對這種狀況而出現的。該函數實現了對內存池的擴容。

  須要注意的是,因爲咱們遍歷完了整個鏈表,所以此時的pool指針指向的是內存池鏈表的最後一個節點。因此說在ngx_palloc_block()中計算的是當前內存池最後一個節點的大小psize。該大小爲須要擴展的空間大小。而後,咱們調用前面提到過的ngx_memalgin()函數申請新的內存空間,大小爲psize,做爲新的小塊內存池節點。以後,咱們將這個節點掛在內存池的最後面。具體怎麼實現的呢?咱們來詳細的看一看。

  首先將這個新節點進行初始化,包括d->end、d->next、d->failed。而後將指向這塊內存的首地址m後移sizeof(ngx_pool_data_t),你們可能還記得咱們在建立內存池ngx_pool_create()時,內存池中空閒地址的首地址是在整個內存池的首地址的基礎上後移了sizeof(ngx_pool_t),那麼爲何此處建立新的內存池節點只須要後移sizeof(ngx_pool_data_t)呢?在x86系統上,sizeof(ngx_pool_data_t)對應16個字節,而sizeof(ngx_pool_t)對應40個字節。其實你們仔細想想,咱們建立的內存池是小塊內存池鏈表的第一個節點,這個節點中除了包含ngx_pool_data_t結構體以外,還須要包含large指針、cleanup指針等。而小塊內存池後面的節點均沒有必要包含這些成員,由於咱們的large鏈表和cleanup鏈表是直接且僅僅掛在小塊內存池鏈表的第一個節點上的。不須要再掛到後續的其餘小塊內存池鏈表的結構上。這麼想是否是以爲比較合理呢?答案就是這樣的。可是咱們以前的重置內存池操做中,並無把後續的從第二個節點開始的小塊內存池鏈表上的空閒內存的起始地址初始化爲(u_char *)p + sizeof (ngx_pool_data_t),而是將全部節點(包括第一個)的空閒內存地址初始化爲(u_char *)p + sizeof (ngx_pool_t)。這樣作會浪費一些內存空間,可是整個重置內存池操做會簡單一點點。由於不用區分第一個節點和其餘節點。若是區分的話,咱們須要讓第一個節點的空閒內存的起始地址初始化爲(u_char *)p + sizeof (ngx_pool_t),將其餘節點的空閒內存的起始地址初始化爲(u_char *)p + sizeof (ngx_pool_data_t)。咱們的Nginx源碼就是這麼實現的。你們知道就好了。由於這並不會影響內存池的使用。

  在完成對新的內存池節點的初始化以後。咱們須要將這個節點加入到小塊內存池鏈表的尾部。具體怎麼實現的呢?

  首先咱們找到current指針,並根據這個指針遍歷小塊內存池鏈表,在每個遍歷中,咱們將每一個節點的failed成員加1(這是由於大家這些節點不能給我分配內存啊,否則也不會調用我,所以對大家的failed成員通通加1)。而且加1以後,進行判斷,若是某個節點的failed成員的值大於4,那麼就將current指向下一個節點(下次再分配內存時將會自動忽略這個節點)。

  在遍歷完小塊內存池的鏈表後,咱們的pool指針已經指向了鏈表的最後一個節點,所以在鏈表的尾部插入一個節點很是簡單,p->d.next = new這個語句就能完成。以後返回這個指向這個新節點的空閒內存空間的首地址。

  上述就是ngx_palloc_small()函數完成的功能,內容比較多你們可能都忘了,咱們尚未講解ngx_palloc()函數的另一個部分:ngx_palloc_large(),這個函數是用於當用戶申請的內存大小大於咱們的小塊內存標準max的狀況。下面咱們將會看到,這種狀況下,申請的內存將被看成是大數據塊,將會被掛在large鏈表上。先給出ngx_palloc_large()的源碼:

 

 

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);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            large->alloc = p;
            return p;
        }

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

    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;
    pool->large = large;

    return p;
}

 

  從上面的代碼中咱們能夠看出咱們首先調用ngx_alloc()函數申請一塊大小爲size的內存空間,ngx_alloc()函數實際上就是簡單的封裝瞭如下malloc()函數,後面咱們會詳細的講解。這裏知道它是由malloc實現的就行了。申請完內存以後,開始遍歷large鏈表,找到鏈表中alloc爲NULL的節點,用alloc指向剛申請到的內存空間並返回。注意這段循環代碼至多執行3次,若是在3次後都沒有找到alloc爲NULL的節點,就會退出循環,繼續執行後面的代碼。限制代碼執行的次數是爲了提高內存分配的效率,由於large鏈表可能會很大。

  以後,咱們調用ngx_palloc_small()從新申請一塊大小爲sizeof(ngx_pool_large_t)結構體大小的內存,創建一個新節點。最後咱們把新創建的節點插入到large鏈表的頭部,返回申請的內存空間的起始地址。爲何是插入頭部而不是插入尾部呢?這裏面實際上是有依據的,由於咱們以前爲了防止large過大將遍歷large鏈表的次數設置爲3,若是插在尾部,那麼遍歷鏈表前面的三個節點就沒有意義了,由於每次均可能會遍歷不到後面的空閒節點,而致使每次都須要從新創建新節點。而且插入頭部,從頭部開始遍歷也會使得效率比較高。由於這樣遍歷到空閒的大塊內存節點的機率會高不少。

 

 

(2).ngx_pnalloc

  先給出其源碼:

 

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

    return ngx_palloc_large(pool, size);
}

 

  咱們能夠看到,ngx_pnalloc()和ngx_palloc()很是類似,惟一的區別就是ngx_pnalloc()中調用的是ngx_palloc_small(pool, size, 0),而ngx_palloc()中調用的是ngx_palloc_small(pool, size, 1)。那麼實際上的含義有什麼區別呢?ngx_pnalloc()分配內存時不考慮內存數據對齊,而ngx_palloc()分配內存時考慮內存數據對齊。

 

 

(3).ngx_pcalloc

  咱們先給出其源碼,以下所示:

 

void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

 

  從其實現能夠看出,ngx_pcalloc()和ngx_palloc()很是的類似,惟一的區別就是ngx_pcalloc()函數將剛申請到的內存空間所有初始化爲0。

 

 

(4).ngx_pmemalign

  咱們給出其源碼,以下所示:

 

void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
    void              *p;
    ngx_pool_large_t  *large;

    p = ngx_memalign(alignment, size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    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;
    pool->large = large;

    return p;
}

 

  從其源碼實現中,咱們能夠看出ngx_pmemalign()函數首先調用ngx_memalign()函數來申請對齊的內存地址空間。而後ngx_palloc_small()函數來創建一個新的大數據塊節點。並將ngx_pmemalign()函數申請的內存空間直接掛在新建的大塊數據節點的alloc成員上。最後再將新建的大數據塊節點掛在大塊內存組成的單鏈表中。

  上面就是整個基於內存池申請內存的4種方法的源碼實現及其分析。下面咱們會繼續講解釋放內存和回收內存。

  ngx_pfree()函數用於提早釋放大塊內存。

 

 

(b).釋放內存

  此處咱們將介紹基於內存池的內存釋放操做函數ngx_pfree(),與內存池無關的內存釋放操做ngx_free()將在後面被講解。

  在Nginx中,小塊內存並不存在提早釋放這麼一說,由於其佔用的內存較少,不太須要被提早釋放。可是對於很是大的內存,若是它的生命週期遠遠短於所屬的內存池,那麼在內存池銷燬以前提早釋放它就變得有意義了。下面先給出其源碼:

 

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

 

  從其實現中能夠看出,ngx_pfree()函數的實現十分簡單。經過遍歷large單鏈表,找到待釋放的內存空間(alloc所指向的內存空間),而後調用ngx_free()函數釋放內存。後面咱們會看到ngx_free()函數是free()函數的一個簡單封裝。釋放alloc所佔用的空間後,將alloc設置爲NULL。咱們須要注意的是:ngx_pfree()函數僅僅釋放了large鏈表上每一個節點的alloc成員所佔用的空間,並無釋放ngx_pool_large_t結構所佔用的內存空間。如此實現的意義在於:下次分配大塊內存時,會指望複用這個ngx_pool_large_t結構體。從這裏能夠想到,若是large鏈表中的元素不少,那麼ngx_pfree()的遍歷耗損的性能是不小的,若是不能肯定內存確實很是大,最好不要調用ngx_pfree。

 

 

(c).隨着內存池釋放同步釋放資源的操做

  在Nginx服務器程序中,有些數據類型在回收其所佔的資源時不能直接經過釋放內存空間的方式進行,而須要在釋放以前對數據進行指定的數據清理操做。ngx_pool_cleanup_t結構體的函數指針handler就是這麼一個數據清理函數,其data成員就指向要清理的數據的內存地址。咱們將要清理的方法和數據存放到ngx_pool_cleanup_t結構體中,經過next成員組成內存回收鏈表,就能夠實如今釋放內存前對數據進行指定的數據清理操做。而與這些操做相關的方法有:ngx_pool_cleanup_add()、ngx_pool_run_cleanup_file()、ngx_pool_cleanup_file()和ngx_pool_delete_file()共4種。下面咱們將分別講解這些操做。

 

(1).ngx_pool_cleanup_add()

  這個方法的目的是爲了添加一個須要在內存池釋放時同步釋放的資源。咱們依照慣例仍是先給出其源碼,而後對源碼進行分析和學習。其源碼以下所示:

 

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

 

  從其實現中咱們能夠看出,咱們首先調用ngx_palloc()函數申請cleanup單鏈表中的一個新節點(指向ngx_pool_cleanup_t結構體的指針),而後根據參數size是否爲0決定是否須要申請存放目標數據的內存空間。當size>0時,調用ngx_palloc()函數申請大小爲size個字節的用於存放待清理的數據的內存空間。這些要清理的數據存儲在ngx_pool_cleanup_t結構體的data成員指向的內存空間中。這樣能夠利用這段內存傳遞參數,供清理資源的方法使用。當size=0時,data爲NULL。最後將新生成的ngx_pool_cleanup_t結構體掛在cleanup單鏈表的頭部。返回一個指向ngx_pool_cleanup_t結構體的指針。而咱們獲得後須要設置ngx_pool_cleanup_t的handler成員爲釋放資源時執行的方法。

返回的指向ngx_pool_cleanup_t結構體的指針具體怎麼使用呢?咱們對ngx_pool_cleanup_t結構體的data成員指向的內存空間填充目標數據時,將會爲handler成員指定相應的函數。

 

 

(2).ngx_pool_run_cleanup_file()

  在內存池釋放前,若是須要提早關閉文件,則調用該方法。下面給出其源碼,以下所示:

 

void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
    ngx_pool_cleanup_t       *c;
    ngx_pool_cleanup_file_t  *cf;

    for (c = p->cleanup; c; c = c->next) {
        if (c->handler == ngx_pool_cleanup_file) {

            cf = c->data;

            if (cf->fd == fd) {
                c->handler(cf);
                c->handler = NULL;
                return;
            }
        }
    }
}

 

  再給出ngx_pool_cleanup_file結構體的聲明和定義(在src/core/ngx_palloc.h頭文件中),以下所示:

 

typedef struct {
    ngx_fd_t              fd;
    u_char               *name;
    ngx_log_t            *log;
} ngx_pool_cleanup_file_t;

 

  從上述源碼中,咱們能夠看出,ngx_pool_run_cleanup_file()經過遍歷cleanup單鏈表,尋找單鏈表上的一個節點,這個節點知足handler(函數指針)等於ngx_pool_cleanup_file(在與函數名相關的表達式中,函數名會被編譯器隱式轉換成函數指針)。因爲ngx_pool_cleanup_t結構體的data成員常常會指向ngx_pool_cleanup_file_t(在後面的ngx_pool_cleanup_file()函數中咱們能夠看到),咱們將這個節點data指針賦值給cf(ngx_pool_cleanup_t結構指針)。以後若是傳遞過來的參數fd與cf->fd相同的話(表明咱們找到了須要提早關閉的文件描述符fd),就提早執行ngx_pool_cleanup_file(fd),進行文件的關閉操做。

 

 

(3).ngx_pool_cleanup_file()

  該方法以關閉文件的方式來釋放資源,能夠被設置爲ngx_pool_cleanup_t的handler成員(函數指針)。咱們給出其源碼實現,以下所示:

 

void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

 

  能夠看出,ngx_pool_cleanup_t結構的data成員指向ngx_pool_cleanup_file_t結構體(前面講解ngx_pool_run_cleanup_file()提到過)。以後直接調用ngx_close_file()函數關閉對應的文件。而ngx_close_file()底層是是經過close()函數實現的。

 

 

(4).ngx_pool_delete_file()

  以刪除文件來釋放資源的方法,能夠設置到ngx_pool_cleanup_t的handler成員。咱們先給出其源碼,以下所示:

 

void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

 

  能夠看出,ngx_pool_cleanup_t結構的data成員指向ngx_pool_cleanup_file_t結構體,在程序中咱們先將傳遞過來的參數data(待清理的目標數據)賦值給c,而後對c的成員name(文件名稱)調用ngx_delete_file()函數,完成對文件的刪除操做,以後調用ngx_close_file()函數關閉相應的文件流(關閉這個文件流能夠阻止刪除的文件再次被訪問,而且釋放FILE結構使得它能夠被作用於其餘的文件),這就是咱們爲何在刪除對應的文件後還須要關閉打開的文件流的緣由。

  補充一下:ngx_close_file和ngx_delete_file實際上是一個宏定義,咱們能夠在src/os/unix/ngx_files.h中看到其具體實現,以下所示:

 

#define ngx_close_file           close
#define ngx_close_file_n         "close()"


#define ngx_delete_file(name)    unlink((const char *) name)
#define ngx_delete_file_n        "unlink()"

 

  能夠看到,ngx_close_file其實就是close,在Nginx服務器程序編譯階段僅僅作一個簡單的替換。ngx_delete_file(name)也是一個宏定義,本質上爲unlink((const char *) name),該函數會刪除參數name指定的文件。

  

 

(d).與內存池無關的資源分配、釋放操做

   與內存池無關的內存分配和釋放操做主要有ngx_alloc()、ngx_calloc()和ngx_free()共3中操做方法。下面咱們將繼續講解它們的具體實現。

 

(1).ngx_alloc()

  ngx_alloc()函數直接從操做系統中申請內存,其實現是對malloc()函數的一個簡單封裝。咱們能夠在src/os/unix/ngx_alloc.c中找到其源碼。以下所示:

 

void *
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;

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

 

  能夠看到,其實現很是簡單。僅僅是封裝了malloc()函數,並作了一些日誌和調試方面的處理。

 

 

(2).ngx_calloc()

  ngx_calloc()和ngx_alloc()很是類似,惟一的區別是在調用malloc()函數申請完內存以後,會調用ngx_memzero()函數將內存所有初始化爲0。ngx_memzero()就是memset()函數。

 

 

(3).ngx_free()

  咱們能夠在src/os/unix/ngx_alloc.h中看到其源碼,以下所示:

 

#define ngx_free          free

 

  能夠看到Nginx程序釋放內存的函數很是簡單,和銷燬內存池中用的是同一個(free)。這裏須要再次說明的是:對於在不一樣場合下從內存池中申請的內存空間的釋放時機是不同的。通常只有大數據塊才直接調用ngx_free()函數進行釋放,其餘數據空間的釋放都是在內存池銷燬的時機完成的,不須要提早完成。

  至此,Nginx與內存相關的操做的源碼實現已基本講完了。你們若是想進一步研究和學習Nginx內存管理機制,能夠從官方下載Nginx源碼,從源碼中去發現Nginx下降系統內存開銷的方法。

 

 

 

7.小結

  全部的講解都講述完了,咱們來進行總結一下。在第1節中,咱們介紹了Nginx的內存管理機制-內存池的基本原理和使用內存池管理Nginx服務器程序帶來的好處。爲了方便你們對內存池結構的理解,咱們在第2節中特地給出了ngx_pool_t內存池的示意圖2.1,並簡單的闡述了這個圖的具體含義。在此基礎上,咱們繼續在第3節中講述了與內存池相關的重要的數據結構,主要包括ngx_pool_t、ngx_pool_data_t、ngx_pool_large_t和ngx_pool_cleanup_t。而後爲了給你們一個內存池操做方法的宏觀介紹,咱們在第4節講述了內存的主要操做方法(共15個分紅4類)。以後在第5節中咱們詳細介紹了內存池的管理,主要包括內存池的建立、銷燬和重置。在第6節中咱們詳細介紹了內存池的使用,主要包括從內存池中如何申請內存、釋放內存和回收內存。這兩個小結是整個Nginx內存管理的精華部分,咱們在這部分中詳細的分析Nginx的源碼實現,從源碼的角度去講解Nginx內存管理用到的技術,方便咱們在之後的程序設計中能夠借鑑和學習。最後,但願這篇文章能真正幫助到你們學習Nginx。

相關文章
相關標籤/搜索