本博客(
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 的內存池結構如下。 ?
[cpp] view plain
copy 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;????? 00061:????ngx_pool_large_t???*large;????? 00062:????ngx_pool_cleanup_t?*cleanup;??? 00063:????ngx_log_t??????????*log;??????? 00064:?};??
其中, sizeof(ngx_pool_data_t) =16B , sizeof(ngx_pool_t) =40B 。 nginx 將幾乎所有的結構體放在 ngx_core.h 文件中重新進行了申明,如下。
[cpp] view plain
copy 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 鏈表,分配的大塊內存鏈表等,如下。
[cpp] view plain
copy 00015:?? ? ? ?? 00019:?#define?NGX_MAX_ALLOC_FROM_POOL?(ngx_pagesize?-?1)???? 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);?????? 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;????? 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
[cpp] view plain
copy 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);?? ?? ?????? ????ngx_pagesize?=?getpagesize();???????? ????ngx_cacheline_size?=?NGX_CPU_CACHE_LINE;?? ????...?? }??
這些數據結構之間的關系,請參考后面的圖。
?
1.3 ngx_pool_t 的邏輯結構
?
這些數據結構邏輯結構圖如下。注:本文采用 UML 的方式畫出該圖。
2. 內存池操作
2.1 創建內存池
創建內存池有 ngx_create_pool() 函數完成,代碼如下。
[cpp] view plain
copy 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);???? 00026:????p->d.end?=?(u_char?*)?p?+?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;???? 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() 代碼如下,分析請參考筆者所加的注釋。
[cpp] view plain
copy 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)?{?? 00122:?? 00123:???????p?=?pool->current;????? 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;???? 00130:?? 00131:?????????????return ?m;?? 00132:??????????}?? 00133:?? 00134:??????????p?=?p->d.next;?? 00135:?? 00136:???????}?while ?(p);?? 00137:?? 00138:???????return ?ngx_palloc_block(pool,?size);??? 00139:????}?? 00140:?? 00141:????return ?ngx_palloc_large(pool,?size);???? 00142:?}??
例如,在 2.1 節中創建的內存池中分配 200B 的內存,調用 ngx_palloc(pool, 200) 后,該內存池物理結構如下圖。
2.4.2 ngx_palloc_block() 函數分析
?
ngx_palloc_block 函數代碼如下,分析請參考筆者所加的注釋。
[cpp] view plain
copy 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);???????? 00183:?? 00184:????m?=?ngx_memalign(NGX_POOL_ALIGNMENT,?psize,?pool->log);?? 00185:????if ?(m?==?NULL)?{?? 00186:???????return ?NULL;?? 00187:????}?? 00188:?? 00189:????new ?=?(ngx_pool_t?*)?m;?? 00190:?? 00191:????new ->d.end?=?m?+?psize;??? 00192:????new ->d.next?=?NULL;?? 00193:????new ->d.failed?=?0;?? 00194:?? 00195:????m?+=?sizeof (ngx_pool_data_t);??? 00196:????m?=?ngx_align_ptr(m,?NGX_ALIGNMENT);??? 00197:????new ->d.last?=?m?+?size;????????? 00198:?? 00199:????current?=?pool->current;?? 00200:?? 00201:????for ?(p?=?current;?p->d.next;?p?=?p->d.next)?{?? 00202:???????if ?(p->d.failed++?>?4)?{????? 00203:??????????current?=?p->d.next;?????? 00204:???????}?? 00205:????}?? 00206:?? 00207:????p->d.next?=?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 代碼
[cpp] view plain
copy ? ? ?? ?? #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 文件如下?!@便是本節的主要目的。
[plain] view plain
copy 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?$@??
3. 3 運行 運行結果
[plain] view plain
copy #?./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.
總結
以上是生活随笔 為你收集整理的nginx源码分析—内存池结构ngx_pool_t及内存管理 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。