本部落格( http://blog.csdn.net/livelylittlefish)貼出作者(阿波)相關研究、學習内容所做的筆記,歡迎廣大朋友指正!
Content
0. 序
1. 記憶體池結構
1.1 ngx_pool_t結構
1.2 其他相關結構
1.3 ngx_pool_t的邏輯結構
2. 記憶體池操作
2.1 建立記憶體池
2.2 銷毀記憶體池
2.3 重置記憶體池
2.4 配置設定記憶體
2.4.1 ngx_palloc()函數分析
2.4.2 ngx_palloc_block()函數分析
2.5 釋放記憶體
2.6 注冊cleanup
2.7 記憶體池的實體結構
3. 一個例子
3.1 代碼
3.2 如何編譯
3.3 運作結果
4. 小結
5. 緻謝
0. 序
nginx對記憶體的管理由其自己實作的記憶體池結構ngx_pool_t來完成,本文重點叙述nginx的記憶體管理。
nginx記憶體管理相關檔案:
(1) ./src/os/unix/ngx_alloc.h/.c
- 記憶體相關的操作,封裝了最基本的記憶體配置設定函數
- 如free/malloc/memalign/posix_memalign,分别被封裝為ngx_free,ngx_alloc/ngx_calloc, ngx_memalign
-
- ngx_alloc:封裝malloc配置設定記憶體
- ngx_calloc:封裝malloc配置設定記憶體,并初始化空間内容為0
- ngx_memalign:傳回基于一個指定alignment的大小為size的記憶體空間,且其位址為alignment的整數倍,alignment為2的幂。
(2) ./src/core/ngx_palloc.h/.c
- 封裝建立/銷毀記憶體池,從記憶體池配置設定空間等函數
.表示nginx-1.0.4代碼目錄,本文為/usr/src/nginx-1.0.4。
1. 記憶體池結構
nginx對記憶體的管理均統一完成,例如,在特定的生命周期統一建立記憶體池(如main函數系統啟動初期即配置設定1024B大小的記憶體池),需要記憶體時統一配置設定記憶體池中的記憶體,在适當的時候釋放記憶體池的記憶體(如關閉http連結時調用ngx_destroy_pool進行銷毀)。
是以,開發者隻需在需要記憶體時進行申請即可,不用過多考慮記憶體的釋放等問題,大大提高了開發的效率。先看一下記憶體池結構。
1.1 ngx_pool_t結構
此處統一一下概念,記憶體池的資料塊:即配置設定記憶體在這些資料塊中進行,一個記憶體池可以有多一個記憶體池資料塊。nginx的記憶體池結構如下。
00048: typedef struct {
00049: u_char *last; //目前記憶體池配置設定到此處,即下一次配置設定從此處開始
00050: u_char *end; //記憶體池結束位置
00051: ngx_pool_t *next; //記憶體池裡面有很多塊記憶體,這些記憶體塊就是通過該指針連成連結清單的
00052: ngx_uint_t failed; //記憶體池配置設定失敗次數
00053: } ngx_pool_data_t; //記憶體池的資料塊位置資訊
00054:
00055:
00056: struct ngx_pool_s{ //記憶體池頭部結構
00057: ngx_pool_data_t d; //記憶體池的資料塊
00058: size_t max; //記憶體池資料塊的最大值
00059: ngx_pool_t *current; //指向目前記憶體池
00060: ngx_chain_t *chain; //該指針挂接一個ngx_chain_t結構
00061: ngx_pool_large_t *large; //大塊記憶體連結清單,即配置設定空間超過max的記憶體
00062: ngx_pool_cleanup_t *cleanup; //釋放記憶體池的callback
00063: ngx_log_t *log; //日志資訊
00064: };
其中,sizeof(ngx_pool_data_t)=16B,sizeof(ngx_pool_t)=40B。
nginx将幾乎所有的結構體放在ngx_core.h檔案中重新進行了申明,如下。
typedef struct ngx_module_s ngx_module_t;
typedef struct ngx_conf_s ngx_conf_t;
typedef struct ngx_cycle_s ngx_cycle_t;
typedef struct ngx_pool_s ngx_pool_t;
typedef struct ngx_chain_s ngx_chain_t;
typedef struct ngx_log_s ngx_log_t;
typedef struct ngx_array_s ngx_array_t;
typedef struct ngx_open_file_s ngx_open_file_t;
typedef struct ngx_command_s ngx_command_t;
typedef struct ngx_file_s ngx_file_t;
typedef struct ngx_event_s ngx_event_t;
typedef struct ngx_event_aio_s ngx_event_aio_t;
typedef struct ngx_connection_s ngx_connection_t;
1.2 其他相關結構
其他與記憶體池相幹的資料結構,如清除資源的cleanup連結清單,配置設定的大塊記憶體連結清單等,如下。
00015: /*
00016: * NGX_MAX_ALLOC_FROM_POOL should be (ngx_pagesize - 1), i.e. 4095 on x86.
00017: * On Windows NT it decreases a number of locked pages in a kernel.
00018: */
00019: #define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1) //在x86體系結構下,該值一般為4096B,即4K
00020:
00021: #define NGX_DEFAULT_POOL_SIZE (16* 1024)
00022:
00023: #define NGX_POOL_ALIGNMENT 16
00024: #define NGX_MIN_POOL_SIZE \
00025: ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \
00026: NGX_POOL_ALIGNMENT)
00027:
00028:
00029: typedef void (*ngx_pool_cleanup_pt)(void *data); //cleanup的callback類型
00030:
00031: typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
00032:
00033: struct ngx_pool_cleanup_s{
00034: ngx_pool_cleanup_pt handler;
00035: void *data; //指向要清除的資料
00036: ngx_pool_cleanup_t *next; //下一個cleanup callback
00037: };
00038:
00039:
00040: typedef struct ngx_pool_large_s ngx_pool_large_t;
00041:
00042: struct ngx_pool_large_s{
00043: ngx_pool_large_t *next; //指向下一塊大塊記憶體
00044: void *alloc; //指向配置設定的大塊記憶體
00045: };
...
...
00067: typedef struct {
00068: ngx_fd_t fd;
00069: u_char *name;
00070: ngx_log_t *log;
00071: } ngx_pool_cleanup_file_t;
00072:
(gdb) p getpagesize()
$18 = 4096
全局變量ngx_pagesize的初始化是在如下函數中完成的。./src/os/unix/ngx_posix_init.c
ngx_int_t
ngx_os_init(ngx_log_t *log)
{
ngx_uint_t n;
#if (NGX_HAVE_OS_SPECIFIC_INIT)
if (ngx_os_specific_init(log) != NGX_OK) {
return NGX_ERROR;
}
#endif
ngx_init_setproctitle(log);
/** 該函數為glibc的庫函數,由系統調用實作,傳回核心中的PAGE_SIZE,該值依賴體系結構*/
ngx_pagesize = getpagesize();
ngx_cacheline_size = NGX_CPU_CACHE_LINE;
...
}
這些資料結構之間的關系,請參考後面的圖。
1.3 ngx_pool_t的邏輯結構
這些資料結構邏輯結構圖如下。注:本文采用UML的方式畫出該圖。
2. 記憶體池操作
2.1 建立記憶體池
建立記憶體池有ngx_create_pool()函數完成,代碼如下。
00015: ngx_pool_t *
00016: ngx_create_pool(size_t size, ngx_log_t *log)
00017: {
00018: ngx_pool_t *p;
00019:
00020: p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
00021: if (p == NULL) {
00022: return NULL;
00023: }
00024:
00025: p->d.last = (u_char *) p + sizeof(ngx_pool_t); //last指向ngx_pool_t結構體之後資料取起始位置
00026: p->d.end = (u_char *) p + size; //end指向配置設定的整個size大小的記憶體的末尾
00027: p->d.next = NULL;
00028: p->d.failed = 0;
00029:
00030: size = size - sizeof(ngx_pool_t);
00031: p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //最大不超過4095B
00032:
00033: p->current = p;
00034: p->chain = NULL;
00035: p->large = NULL;
00036: p->cleanup = NULL;
00037: p->log = log;
00038:
00039: return p;
00040: }
例如,調用ngx_create_pool(1024, 0x80d1c4c)後,建立的記憶體池實體結構如下圖。
2.2 銷毀記憶體池
銷毀記憶體池由如下函數完成。
void ngx_destroy_pool(ngx_pool_t *pool)
該函數将周遊記憶體池連結清單,所有釋放記憶體,如果注冊了clenup(也是一個連結清單結構),亦将周遊該cleanup連結清單結構依次調用clenup的handler清理。同時,還将周遊large連結清單,釋放大塊記憶體。
2.3 重置記憶體池
重置記憶體池由下面的函數完成。
void ngx_reset_pool(ngx_pool_t *pool);
該函數将釋放所有large記憶體,并且将d->last指針重新指向ngx_pool_t結構之後資料區的開始位置,同剛建立後的位置相同。
2.4 配置設定記憶體
記憶體配置設定的函數如下。
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);
傳回值為配置設定的記憶體起始位址。選擇其中的兩個函數進行分析,其他的也很好了解,省略。
2.4.1 ngx_palloc()函數分析
ngx_palloc()代碼如下,分析請參考筆者所加的注釋。
00115: void *
00116: ngx_palloc(ngx_pool_t *pool, size_t size)
00117: {
00118: u_char *m;
00119: ngx_pool_t *p;
00120:
00121: if (size <= pool->max) {//判斷待配置設定記憶體與max值
00122:
00123: p = pool->current; //小于max值,則從current節點開始周遊pool連結清單
00124:
00125: do {
00126: m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
00127:
00128: if ((size_t) (p->d.end - m) >= size) {
00129: p->d.last = m + size; //在該節點指向的記憶體塊中配置設定size大小的記憶體
00130:
00131: return m;
00132: }
00133:
00134: p = p->d.next;
00135:
00136: } while (p);
00137:
00138: return ngx_palloc_block(pool, size); //連結清單裡沒有能配置設定size大小記憶體的節點,則生成一個新的節點并在其中配置設定記憶體
00139: }
00140:
00141: return ngx_palloc_large(pool, size); //大于max值,則在large連結清單裡配置設定記憶體
00142: }
例如,在2.1節中建立的記憶體池中配置設定200B的記憶體,調用ngx_palloc(pool, 200)後,該記憶體池實體結構如下圖。
2.4.2 ngx_palloc_block()函數分析
ngx_palloc_block函數代碼如下,分析請參考筆者所加的注釋。
00175: static void *
00176: ngx_palloc_block(ngx_pool_t *pool, size_t size)
00177: {
00178: u_char *m;
00179: size_t psize;
00180: ngx_pool_t *p, *new, *current;
00181:
00182: psize = (size_t) (pool->d.end - (u_char *) pool); //計算pool的大小
00183:
00184: m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);//配置設定一塊與pool大小相同的記憶體
00185: if (m == NULL) {
00186: return NULL;
00187: }
00188:
00189: new = (ngx_pool_t *) m;
00190:
00191: new->d.end = m + psize; //設定end指針
00192: new->d.next = NULL;
00193: new->d.failed = 0;
00194:
00195: m += sizeof(ngx_pool_data_t); //讓m指向該塊記憶體ngx_pool_data_t結構體之後資料區起始位置
00196: m = ngx_align_ptr(m, NGX_ALIGNMENT); //按4位元組對齊
00197: new->d.last = m + size; //在資料區配置設定size大小的記憶體并設定last指針
00198:
00199: current = pool->current;
00200:
00201: for (p = current; p->d.next; p = p->d.next) {
00202: if (p->d.failed++ > 4) { //failed的值隻在此處被修改
00203: current = p->d.next; //失敗4次以上移動current指針
00204: }
00205: }
00206:
00207: p->d.next = new; //将這次配置設定的記憶體塊new加入該記憶體池
00208:
00209: pool->current = current ? current : new;
00210:
00211: return m;
00212: }
注意:該函數配置設定一塊記憶體後,last指針指向的是ngx_pool_data_t結構體(大小16B)之後資料區的起始位置。而建立記憶體池時時,last指針指向的是ngx_pool_t結構體(大小40B)之後資料區的起始位置。
結合2.7節的記憶體池的實體結構,更容易了解。
2.5 釋放記憶體
請參考如下函數,不再贅述。
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
需要注意的是該函數隻釋放large連結清單中注冊的記憶體,普通記憶體在ngx_destroy_pool中統一釋放。
2.6 注冊cleanup
請參考如下函數,該函數實作也很簡單,此處不再贅述。
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
2.7 記憶體池的實體結構
針對本文第3節的例子,畫出的記憶體池的實體結構如下圖。
從該圖也能看出2.4節的結論,即記憶體池第一塊記憶體前40位元組為ngx_pool_t結構,後續加入的記憶體塊前16個位元組為ngx_pool_data_t結構,這兩個結構之後便是真正可以配置設定記憶體區域。
是以,本文Reference中的記憶體配置設定相關中的圖是有一點點小問題的,并不是每一個節點的前面都是ngx_pool_t結構。
3. 一個例子
了解并掌握開源軟體的最好方式莫過于自己寫一些測試代碼,或者改寫軟體本身,并進行調試來進一步了解開源軟體的原理和設計方法。本節給出一個建立記憶體池并從中配置設定記憶體的簡單例子。
3.1 代碼
/**
* ngx_pool_t test, to test ngx_palloc, ngx_palloc_block, ngx_palloc_large
*/
#include <stdio.h>
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"
volatile ngx_cycle_t *ngx_cycle;
void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
const char *fmt, ...)
{
}
void dump_pool(ngx_pool_t* pool)
{
while (pool)
{
printf("pool = 0x%x\n", pool);
printf(" .d\n");
printf(" .last = 0x%x\n", pool->d.last);
printf(" .end = 0x%x\n", pool->d.end);
printf(" .next = 0x%x\n", pool->d.next);
printf(" .failed = %d\n", pool->d.failed);
printf(" .max = %d\n", pool->max);
printf(" .current = 0x%x\n", pool->current);
printf(" .chain = 0x%x\n", pool->chain);
printf(" .large = 0x%x\n", pool->large);
printf(" .cleanup = 0x%x\n", pool->cleanup);
printf(" .log = 0x%x\n", pool->log);
printf("available pool memory = %d\n\n", pool->d.end - pool->d.last);
pool = pool->d.next;
}
}
int main()
{
ngx_pool_t *pool;
printf("--------------------------------\n");
printf("create a new pool:\n");
printf("--------------------------------\n");
pool = ngx_create_pool(1024, NULL);
dump_pool(pool);
printf("--------------------------------\n");
printf("alloc block 1 from the pool:\n");
printf("--------------------------------\n");
ngx_palloc(pool, 512);
dump_pool(pool);
printf("--------------------------------\n");
printf("alloc block 2 from the pool:\n");
printf("--------------------------------\n");
ngx_palloc(pool, 512);
dump_pool(pool);
printf("--------------------------------\n");
printf("alloc block 3 from the pool :\n");
printf("--------------------------------\n");
ngx_palloc(pool, 512);
dump_pool(pool);
ngx_destroy_pool(pool);
return 0;
}
3.2 如何編譯
這個問題是編寫測試代碼或者改寫軟體本身最迫切需要解決的問題,否則,編寫的代碼無從編譯或運作,那也無從進行調試并了解軟體了。
如何對自己編寫的測試代碼進行編譯,可參考Linux平台代碼覆寫率測試-編譯過程自動化及對連結的解釋、Linux平台如何編譯使用Google test寫的單元測試?。我們要做的是學習這種編譯工程的方法,針對該例子,筆者編寫的makefile檔案如下。——這便是本節的主要目的。
CXX = gcc
CXXFLAGS += -g -Wall -Wextra
NGX_ROOT = /usr/src/nginx-1.0.4
TARGETS = ngx_pool_t_test
TARGETS_C_FILE = $(TARGETS).c
CLEANUP = rm -f $(TARGETS) *.o
all: $(TARGETS)
clean:
$(CLEANUP)
CORE_INCS = -I. \
-I$(NGX_ROOT)/src/core \
-I$(NGX_ROOT)/src/event \
-I$(NGX_ROOT)/src/event/modules \
-I$(NGX_ROOT)/src/os/unix \
-I$(NGX_ROOT)/objs \
NGX_PALLOC = $(NGX_ROOT)/objs/src/core/ngx_palloc.o
NGX_STRING = $(NGX_ROOT)/objs/src/core/ngx_string.o
NGX_ALLOC = $(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o
$(TARGETS): $(TARGETS_C_FILE)
$(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $^ -o [email protected]
3.3 運作運作結果
# ./ngx_pool_t_test
--------------------------------
create a new pool:
--------------------------------
pool = 0x8922020
.d
.last = 0x8922048
.end = 0x8922420
.next = 0x0
.failed = 0
.max = 984
.current = 0x8922020
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 984
--------------------------------
alloc block 1 from the pool:
--------------------------------
pool = 0x8922020
.d
.last = 0x8922248
.end = 0x8922420
.next = 0x0
.failed = 0
.max = 984
.current = 0x8922020
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 472
--------------------------------
alloc block 2 from the pool:
--------------------------------
pool = 0x8922020
.d
.last = 0x8922248
.end = 0x8922420
.next = 0x8922450
.failed = 0
.max = 984
.current = 0x8922020
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 472
pool = 0x8922450
.d
.last = 0x8922660
.end = 0x8922850
.next = 0x0
.failed = 0
.max = 0
.current = 0x0
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 496
--------------------------------
alloc block 3 from the pool :
--------------------------------
pool = 0x8922020
.d
.last = 0x8922248
.end = 0x8922420
.next = 0x8922450
.failed = 1
.max = 984
.current = 0x8922020
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 472
pool = 0x8922450
.d
.last = 0x8922660
.end = 0x8922850
.next = 0x8922880
.failed = 0
.max = 0
.current = 0x0
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 496
pool = 0x8922880
.d
.last = 0x8922a90
.end = 0x8922c80
.next = 0x0
.failed = 0
.max = 0
.current = 0x0
.chain = 0x0
.large = 0x0
.cleanup = 0x0
.log = 0x0
available pool memory = 496
4. 小結
本文針對nginx-1.0.4的記憶體管理進行了較為全面的分析,包括相關記憶體池資料結構,記憶體池的建立、銷毀,以及從記憶體池中配置設定記憶體等。最後通過一個簡單例子向讀者展示nginx記憶體池的建立和配置設定操作,同時借此向讀者展示編譯測試代碼的方法。
分析完nginx的記憶體管理,你一定驚歎于nginx作者的聰明才智。這種記憶體管理的設計方法小巧、快捷,值得借鑒!
5. 緻謝
寫作本文,筆者參考了Reference裡yixiao的Nginx源碼分析-記憶體池和RainX1982的Nginx代碼研究計劃。在此給予他們誠摯的感謝!
Reference
man posix_memalign (manual頁)(Allocate aligned memory)
man getpagesize (manual頁)(Get memory page size)
Nginx源碼分析-記憶體池 (yixiao)
Nginx代碼研究計劃 (RainX1982)
Appendix: posix_memalign
The function posix_memalign() allocates size bytes and places theaddress of the allocated memory in*memptr. The address of the allocated memory will be a multiple of alignment, which must be a power oftwo and a multiple of sizeof(void *).
the memory is not zeroed.
posix_memalign() returns zero on success, or one of the error values listed in the next section on failure. Note that errno is not set.