摘要:源代碼路徑版本主要作用分析提供了一種機制,幫助進行資源管理內(nèi)存文件。用來標記該使用時分配失敗次數(shù)。根據(jù)以上思路,可以很容易明白源碼里關于創(chuàng)建鏈表的代碼函數(shù)聲明說明輸入要分配的節(jié)點大小,返回一個的指針。
源代碼路徑
版本:1.8.0
srccoreNgx_palloc.h srccoreNgx_palloc.c主要作用分析
提供了一種機制,幫助進行資源管理(內(nèi)存、文件)??梢灶惐?b>C++中的RAII機制。
以內(nèi)存管理為例,通常是手工進行malloc/free,這種做法的優(yōu)點是靈活、高效,缺點是容易出錯,造成內(nèi)存泄漏以及各種莫名其妙的BUG。
因此,在C/C++中正確的管理內(nèi)存一直是最棘手的問題。
C++中提供了RAII機制和智能指針來解決這個問題。Nginx采用C語言開發(fā),實現(xiàn)了一套用來管理資源的機制。這就是nginx pool。
數(shù)據(jù)結(jié)構(gòu)ngx_pool_data_t
typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t;
首先看一個示意圖:
last指針表示ngx_pool_data_t所管理的內(nèi)存block中已使用的內(nèi)存的末尾地址,這也是下次分配的起始地址。
end指針表示ngx_pool_data_t所管理的內(nèi)存block的尾地址,該指針在分配此block時確定,用來標記last的邊界值。
next用來指向下一個ngx_pool_data_t的內(nèi)存block地址,用于形成block鏈表。
failed用來標記該block使用時分配失敗次數(shù)。
圍繞核心數(shù)據(jù)結(jié)構(gòu)ngx_pool_data_t, Nginx是如何管理和使用的呢?
本篇主要從以下幾個方面說明:
1) 如何初始化ngx_pool_data_t鏈表;
2) 如何向鏈表增加ngx_pool_data_t節(jié)點。
3) 如何回收/銷毀ngx_pool_data_t節(jié)點及鏈表
基本思路:
所謂初始化ngx_pool_data_t鏈表就是申請一段內(nèi)存block,然后將該block指針void* p轉(zhuǎn)成ngx_pool_data_t*類型的指針,這樣,就可以利用ngx_pool_data_t*指針來管理這段block,而這個block就是ngx_pool_data_t鏈表的節(jié)點。
當然要能夠正確管理block還需要初始化ngx_pool_data_t的成員變量last、end、next、failed。這樣,通過ngx_pool_data_t指針,可以獲取管理ngx_pool_data_t鏈表節(jié)點的能力
但是現(xiàn)在我們還沒有管理整個ngx_pool_data_t鏈表的能力,那么如何做呢?通過在內(nèi)存block的起始部分添加last、end、next、failed等信息可以管理一段內(nèi)存。相應地,通過在ngx_pool_data_t鏈表第一個節(jié)點添加管理鏈表的信息,就可以管理整個鏈表。
同時,由于鏈表第一個節(jié)點只是一個特殊的節(jié)點所以,負責管理節(jié)點的結(jié)構(gòu)體ngx_pool_data_t應該是的負責管理鏈表結(jié)構(gòu)體節(jié)點的子集
在Nginx源碼中這個管理鏈表的結(jié)構(gòu)體就是:ngx_pool_s,其結(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; };
其中,d是ngx_pool_data_t用來管理節(jié)點本身,而其他變量則用來管理鏈表。
current用來指示鏈表中當前正在使用的節(jié)點;
chain用來在節(jié)點上掛接filter鏈表;
cleanup用來注冊資源回收handler等;
large是與節(jié)點大內(nèi)存、小內(nèi)存有關,這里暫不分析。
通過以上分析,可知,ngx_pool_s是管理整個ngx_pool_data_t鏈表,同時也能夠管理所在的鏈表節(jié)點。
在Nginx源碼里,申請的block指針為void*統(tǒng)一轉(zhuǎn)成ngx_pool_s*,這樣,在鏈表第一個節(jié)點,通過ngx_pool_s*管理整個鏈表及節(jié)點本身,在其他節(jié)點,通過ngx_pool_s*管理節(jié)點本身。
根據(jù)以上思路,可以很容易明白Nginx源碼里關于創(chuàng)建ngx_pool_data_t鏈表的代碼
函數(shù)聲明: Ngx_palloc.h
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
說明:
輸入要分配的ngx_pool_t節(jié)點block大小size,返回一個ngx_pool_t*的指針。該指針既用來管理鏈表,也用來管理節(jié)點block本身。
因此,后續(xù)鏈表的插入,刪除都是通過操作該指針來完成,而使用鏈表第一個節(jié)點的block也是通過該指針來完成。
函數(shù)定義: Ngx_palloc.c
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log) { ngx_pool_t *p; // 申請內(nèi)存block,為提高效率,進行了對齊 // 轉(zhuǎn)成ngx_pool_t*指針 p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); if (p == NULL) { return NULL; } // 初始化ngx_pool_data_t用來管理節(jié)點block本身 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; // 為提高效率能使用的block最大值為NGX_MAX_ALLOC_FROM_POOL // 輸入size最小值為sizeof(ngx_pool_t),輸入小于該值會造成nginx崩潰 size = size - sizeof(ngx_pool_t); p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; // 初始時,鏈表current指向自身 p->current = p; p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; return p; }向鏈表增加ngx_pool_data_t節(jié)點
基本思路:
上一小節(jié)中關于如何初始化ngx_pool_data_t鏈表的思路,可以基本套用到增加ngx_pool_data_t節(jié)點中來,只是在結(jié)構(gòu)體ngx_pool_t成員變量初始化上有所區(qū)別。
直接上源碼:
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size) { // 輸入變量pool用來表示整個鏈表 u_char *m; size_t psize; ngx_pool_t *p, *new; // 計算鏈表第一個節(jié)點的block大小 psize = (size_t) (pool->d.end - (u_char *) pool); // 申請與鏈表第一個節(jié)點大小相同的內(nèi)存block m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log); if (m == NULL) { return NULL; } // 轉(zhuǎn)為ngx_pool_t*類型 new = (ngx_pool_t *) m; // 初始化節(jié)點管理結(jié)構(gòu)體ngx_pool_data_t new->d.end = m + psize; new->d.next = NULL; new->d.failed = 0; // 為了效率,對ngx_pool_data_t的last變量進行對齊操作 m += sizeof(ngx_pool_data_t); m = ngx_align_ptr(m, NGX_ALIGNMENT); new->d.last = m + size; // 將第一個鏈表節(jié)點用于操作鏈表的pool指針的current變量指向當前節(jié)點 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; }
當然,在使用時,Nginx并不是直接使用ngx_palloc_block來操作鏈表,而是使用ngx_palloc
原因在于,使用ngx_create_pool和ngx_palloc_block構(gòu)造鏈表之后,我們得到的是多個連續(xù)的內(nèi)存塊組成的列表,這些內(nèi)存塊大小相同,其大小為調(diào)用ngx_create_pool傳入的size值或NGX_MAX_ALLOC_FROM_POOL。
而在實際使用時malloc的大小不一定等于這些內(nèi)存塊的大小,所以,需要提供一個接口,能夠從ngx_pool_t鏈表中獲取需要大小的內(nèi)存。
這個函數(shù)就是ngx_palloc
基本思路:
如果要獲取的內(nèi)存大小大于ngx_pool_t節(jié)點的大小,那么屬于創(chuàng)建大內(nèi)存,調(diào)用ngx_palloc_large(pool, size)。這里我們暫不分析大內(nèi)存的問題。
如果要獲取的內(nèi)存小于ngx_pool_t節(jié)點的大小,那么嘗試從當前節(jié)點分配。
這里分兩種情況:
當前節(jié)點剩余空間足夠的情況,直接分配;
當前節(jié)點剩余空間不足的情況,創(chuàng)建一個新節(jié)點,并將新節(jié)點分配
P.S 這里的分配就是返回指針
函數(shù)聲明:
void *ngx_palloc(ngx_pool_t *pool, size_t size);
函數(shù)定義:
void * ngx_palloc(ngx_pool_t *pool, size_t size) { u_char *m; ngx_pool_t *p; // 獲取內(nèi)存小于鏈表節(jié)點內(nèi)存的情況 if (size <= pool->max) { p = pool->current; // 嘗試從當前節(jié)點分配內(nèi)存 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); // 新建一個節(jié)點并分配 return ngx_palloc_block(pool, size); } // 獲取內(nèi)存小于鏈表節(jié)點內(nèi)存的情況 return ngx_palloc_large(pool, size); }
今天先到這里,后續(xù)再補充或另起一篇寫。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/39133.html
摘要:源碼路徑版本主要作用分析是對通常的這種數(shù)據(jù)結(jié)構(gòu)重復的造輪子。鏈表使用的內(nèi)存池。在堆上創(chuàng)建調(diào)用函數(shù)與的分析類似,調(diào)用該函數(shù)會自動向申請內(nèi)存空間。 源碼路徑 版本:1.8.0 srccoreNgx_list.h srccoreNgx_list.c 主要作用分析 ngx_list_t是Nginx對通常的list這種數(shù)據(jù)結(jié)構(gòu)重復的造輪子。 在本篇中,我們先來分析Nginx是如何造這...
摘要:源文件路徑版本主要作用分析是內(nèi)部使用的數(shù)組型數(shù)據(jù)結(jié)構(gòu),與語言內(nèi)置的數(shù)組概念上類似,但是有兩點主要區(qū)別使用內(nèi)存池來管理內(nèi)存雖然有預設數(shù)組大小的概念,但是在數(shù)組元素超出預設值大小時,會在內(nèi)存池中發(fā)生重分配。 源文件路徑 版本:1.8.0 srccoreNgx_array.h srccoreNgx_array.c 主要作用分析 ngx_array_t是Nginx內(nèi)部使用的數(shù)組型數(shù)據(jù)...
摘要:而對于堆內(nèi)存,通常需要程序員進行管理。我們通常說的內(nèi)存管理亦是只堆空間內(nèi)存管理。內(nèi)存管理整體可以分為個部分,第一部分是常規(guī)的內(nèi)存池,用于進程平時所需的內(nèi)存管理第二部分是共享內(nèi)存的管理。將內(nèi)存塊按照的整數(shù)次冪進行劃分最小為最大為。 施洪寶 一. 概述 應用程序的內(nèi)存可以簡單分為堆內(nèi)存,棧內(nèi)存。對于棧內(nèi)存而言,在函數(shù)編譯時,編譯器會插入移動棧當前指針位置的代碼,實現(xiàn)??臻g的自管理。而對于...
閱讀 1352·2019-08-30 15:44
閱讀 1414·2019-08-29 18:42
閱讀 464·2019-08-29 13:59
閱讀 803·2019-08-28 17:58
閱讀 2848·2019-08-26 12:02
閱讀 2448·2019-08-23 18:40
閱讀 2438·2019-08-23 18:13
閱讀 3138·2019-08-23 16:27