最近nginx的源碼恰好研究到內存池,這兒就看下nginx內存池的相關的東西。html
一,爲何要使用內存池linux
大多數的解釋不外乎提高程序的處理性能及減少內存中的碎片,對於性能優化這點主要體如今:
(1)系統的malloc/free等內存申請函數涉及到較多的處理,如申請時合適空間的查找,釋放時的空間合併。
(2)默認的內存管理函數還會考慮多線程的應用,加鎖操做會增長開銷。
(3)每次申請內存的系統態與用戶態的切換也及爲的消耗性能。
對於因爲應用的頻繁的在堆上分配及釋放空間所帶來的內存碎片化,其實主流的思想是認爲存在的,不過也有人認爲
這種考慮實際上是多餘的,在「內存池到底爲咱們解決了什麼問題」一文中則認爲,大量的內存申請與釋放僅會形成短暫
的內存碎片化的產生,並不會引發大量內存的長久碎片化,從而致使最後申請大內存時的徹底不可用性。文中認爲對於
肯定的應用均是「有限對象需求」,即在任一程序中申請與釋放的對象種類老是有限的,大小也老是有必定重複性的,
這樣在碎片產生一段時間後,會由於一樣的對象申請而消除內存的臨時碎片化。nginx
不過,綜上,內存池有利於提高程序在申請及釋放內存時的處理性能這點是肯定的。內存池的主要優勢有:
(1)特殊狀況的頻繁的較小的內存空間的釋放與申請不須要考慮複雜的分配釋放方法,有較高的性能。
(2)初始申請時一般申請一塊較大的連續空間的內存區域,所以進行管理及內存地址對齊時處理很是方便。
(3)小塊內存的申請一般不用考慮實際的釋放操做。web
二,內存池的原理及實現方法windows
內存池的原理基本是內存的提早申請,重複利用。其中主要須要關注的是內存池的初始化,內存分配及內存釋放。
內存池的實現方法主要分兩種:
一種是固定式,即提早申請的內存空間大小固定,空間劃分紅固定大小的內存單元以供使用以下圖示:性能優化
上圖引用自http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html。多線程
另外一種是動態式,即初始申請固定大小的單元,空間不作明確的大小劃分,而是根據須要提供合適大小的空間以供使用。
不過,這兩種方式在處理大空間的內存申請時的處理方法一般都是採用系統的內存申請調用。socket
三,nginx的內存池實現ide
nginx的內存池設計得很是精妙,它在知足小塊內存申請的同時,也處理大塊內存的申請請求,同時還容許掛載本身的數據區域
及對應的數據清楚操做。
nginx內存池實現主要是在core/ngx_palloc.{h,c}中,一些支持函數位於os/unix/ngx_alloc.{h,c}中,支持函數主要是對原有的
malloc/free/memalign等函數的封裝,對就的函數爲:
》ngx_alloc 完成malloc的封裝
》ngx_calloc 使用malloc分配空間,同時使用memset完成初始化
》ngx_memalign 會根據系統不一樣而調用不同的函數處理,如posix系列使用posix_memalign,windows則不考慮對齊。主要做用
是申請指定的alignment對齊的起始地址的內存空間。
》ngx_free 完成free的封裝函數
nginx內存池中有兩個很是重要的結構,一個是ngx_pool_s,主要是做爲整個內存池的頭部,管理內存池結點鏈表,大內存鏈表,
cleanup鏈表等,具體結構以下:
//該結構維護整個內存池的頭部信息 struct ngx_pool_s { ngx_pool_data_t d; //數據塊 size_t max; //數據塊大小,即小塊內存的最大值 ngx_pool_t *current; //保存當前內存值 ngx_chain_t *chain; //能夠掛一個chain結構 ngx_pool_large_t *large; //分配大塊內存用,即超過max的內存請求 ngx_pool_cleanup_t *cleanup; //掛載一些內存池釋放的時候,同時釋放的資源 ngx_log_t *log; };
另外一重要的結構爲ngx_pool_data_s,這個是用來鏈接具體的內存池結點的,具體以下:
//該結構用來維護內存池的數據塊,供用戶分配之用 typedef struct { u_char *last; //當前內存分配結束位置,即下一段可分配內存的起始位置 u_char *end; //內存池結束位置 ngx_pool_t *next; //連接到下一個內存池 ngx_uint_t failed;//統計該內存池不能知足分配請求的次數 } ngx_pool_data_t;
還有另兩個結構ngx_pool_large_t,ngx_pool_cleanup_t,以下示:
//大內存結構 struct ngx_pool_large_s { ngx_pool_large_t *next; //下一個大塊內存 void *alloc;//nginx分配的大塊內存空間 }; struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; //數據清理的函數句柄 void *data; //要清理的數據 ngx_pool_cleanup_t *next; //鏈接至下一個 };
而後咱們具體看一下nginx內存池的組成結構,以下圖示:
上面的圖中,current指針是指向的首結點,在具體的運行過程當中是會根據failed值進行調整的。還有就是
ngx_pool_cleanup_s與ngx_pool_large_s的結構空間均來自內存池結點。
而後看nginx相關的操做:
1.建立內存池
內存池的建立是在ngx_create_pool函數中完成的,實現以下:
//建立內存池 ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log) { ngx_pool_t *p; //ngx_memalign實際上會依據os不用,分狀況處理,在os不支持memalign狀況的分配時,選擇直接分配內存 p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log); // 分配內存函數,uinx,windows分開走 if (p == NULL) { return NULL; } p->d.last = (u_char *) p + sizeof(ngx_pool_t); //初始指向 ngx_pool_t 結構體後面 p->d.end = (u_char *) p + size; //整個結構的結尾後面 p->d.next = NULL; p->d.failed = 0; size = size - sizeof(ngx_pool_t); //實際上pool數據區的大小與系統頁的大小有關的 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL; //最大不超過 NGX_MAX_ALLOC_FROM_POOL,也就是getpagesize()-1 大小 p->current = p; p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log; return p; }
2.銷燬內存池
內存池的銷燬位於ngx_destroy_pool(ngx_pool_t *pool)中,此函數會清理全部的內存池結點,同時清理large鏈表的內存
而且對於註冊的cleanup鏈表的清理操做也會進行。具體實現以下:
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); } } #if (NGX_DEBUG) /* * we could allocate the pool->log from this pool * so we cannot use this log while free()ing the pool */ 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 (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) { ngx_free(p); if (n == NULL) { break; } } }
3.分配內存
從內存池中分配內存涉及到幾個函數,以下:
void *ngx_palloc(ngx_pool_t *pool, size_t size); //palloc取得的內存是對齊的 void *ngx_pnalloc(ngx_pool_t *pool, size_t size); //pnalloc取得的內存是不對齊的 void *ngx_pcalloc(ngx_pool_t *pool, size_t size); //pcalloc直接調用palloc分配好內存,而後進行一次0初始化操做 void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment); //在分配size大小的內存,並按照alignment對齊,而後掛到large字段下 static void *ngx_palloc_block(ngx_pool_t *pool, size_t size); //申請新的內存池結點 static void *ngx_palloc_large(ngx_pool_t *pool, size_t size); //申請大的內存塊
下面僅對部分函數進行源碼的分析:
首先來看ngx_palloc()函數,其源碼爲:
//有內存對齊的空間申請 void * ngx_palloc(ngx_pool_t *pool, size_t size) { u_char *m; ngx_pool_t *p; if (size <= pool->max) { //從current遍歷到鏈表末尾,不找前面緣由實際上是由於failed的控制機制,保證前面的節點 //基本處於満的狀態。僅剩餘部分小塊區域。 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_palloc_block,ngx_palloc_large先來看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, *current; 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; //這兒有個細節,新的節點能夠用ngx_pool_t指針表示,但具體的數據存儲則是ngx_pool_data_t. m += sizeof(ngx_pool_data_t); m = ngx_align_ptr(m, NGX_ALIGNMENT); new->d.last = m + size; //這兒是調整current指針,每一次空間申請失敗都會致使current至內存池鏈表結尾的 //結點的failed次數加1,這樣在連續分配時,當前current其後的幾個結點,其實也差很少 //處於飽和狀態,而後這時將current一次調至失敗次數較小的結點是合理的,不過判斷跳轉時機 //是依據經驗值的。 current = pool->current; for (p = current; p->d.next; p = p->d.next) { if (p->d.failed++ > 4) { current = p->d.next; } } p->d.next = new; pool->current = current ? current : new; return m; }
而後是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; //ngx_alloc僅是對alloc的簡單封裝 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(pool, sizeof(ngx_pool_large_t)); if (large == NULL) { ngx_free(p); return NULL; } large->alloc = p; large->next = pool->large; pool->large = large; return p; }
4.其他的函數
內存池中還一些其它支持函數,這裏不細說了:
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);
四,下面是一全例子
這個例子主要是演示下nginx內存池的使用,代碼以下:
/* * author:doop-ymc * date:2013-11-11 * version:1.0 */ #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" #define MY_POOL_SIZE 5000 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 echo_pool(ngx_pool_t* pool) { int n_index; ngx_pool_t *p_pool; ngx_pool_large_t *p_pool_large; n_index = 0; p_pool = pool; p_pool_large = pool->large; printf("------------------------------\n"); printf("pool begin at: 0x%x\n", pool); do{ printf("->d :0x%x\n", p_pool); printf(" last = 0x%x\n", p_pool->d.last); printf(" end = 0x%x\n", p_pool->d.end); printf(" next = 0x%x\n", p_pool->d.next); printf(" failed = %d\n", p_pool->d.failed); p_pool = p_pool->d.next; }while(p_pool); printf("->max :%d\n", pool->max); printf("->current :0x%x\n", pool->current); printf("->chain :0x%x\n", pool->chain); if(NULL == p_pool_large){ printf("->large :0x%x\n", p_pool_large); }else{ do{ printf("->large :0x%x\n", p_pool_large); printf(" next = 0x%x\n", p_pool_large->next); printf(" alloc = 0x%x\n", p_pool_large->alloc); p_pool_large = p_pool_large->next; }while(p_pool_large); } printf("->cleanup :0x%x\n", pool->cleanup); printf("->log :0x%x\n\n\n", pool->log); } int main() { ngx_pool_t *my_pool; /*create pool size:5000*/ my_pool = ngx_create_pool(MY_POOL_SIZE, NULL); if(NULL == my_pool){ printf("create nginx pool error,size %d\n.", MY_POOL_SIZE); return 0; } printf("+++++++++++CREATE NEW POOL++++++++++++\n"); echo_pool(my_pool); printf("+++++++++++ALLOC 2500+++++++++++++++++\n"); ngx_palloc(my_pool, 2500); echo_pool(my_pool); printf("+++++++++++ALLOC 2500+++++++++++++++++\n"); ngx_palloc(my_pool, 2500); echo_pool(my_pool); printf("+++++++++++ALLOC LARGE 5000+++++++++++\n"); ngx_palloc(my_pool, 5000); echo_pool(my_pool); printf("+++++++++++ALLOC LARGE 5000+++++++++++\n"); ngx_palloc(my_pool, 5000); echo_pool(my_pool); ngx_destroy_pool(my_pool); return 0; }
Makefile文件:
CC = gcc CFLAGS += -W -Wall -g NGX_ROOT_PATH = /home/doop-ymc/nginx/nginx-1.0.14 TARGETS = pool_t TARGETS_C_FILE = $(TARGETS).c all: $(TARGETS) .PHONY:clean clean: rm -f $(TARGETS) *.o INCLUDE_PATH = -I. \ -I$(NGX_ROOT_PATH)/src/core \ -I$(NGX_ROOT_PATH)/src/event \ -I$(NGX_ROOT_PATH)/src/event/modules \ -I$(NGX_ROOT_PATH)/src/os/unix \ -I$(NGX_ROOT_PATH)/objs \ CORE_DEPS = $(NGX_ROOT_PATH)/src/core/nginx.h \ $(NGX_ROOT_PATH)/src/core/ngx_config.h \ $(NGX_ROOT_PATH)/src/core/ngx_core.h \ $(NGX_ROOT_PATH)/src/core/ngx_log.h \ $(NGX_ROOT_PATH)/src/core/ngx_palloc.h \ $(NGX_ROOT_PATH)/src/core/ngx_array.h \ $(NGX_ROOT_PATH)/src/core/ngx_list.h \ $(NGX_ROOT_PATH)/src/core/ngx_hash.h \ $(NGX_ROOT_PATH)/src/core/ngx_buf.h \ $(NGX_ROOT_PATH)/src/core/ngx_queue.h \ $(NGX_ROOT_PATH)/src/core/ngx_string.h \ $(NGX_ROOT_PATH)/src/core/ngx_parse.h \ $(NGX_ROOT_PATH)/src/core/ngx_inet.h \ $(NGX_ROOT_PATH)/src/core/ngx_file.h \ $(NGX_ROOT_PATH)/src/core/ngx_crc.h \ $(NGX_ROOT_PATH)/src/core/ngx_crc32.h \ $(NGX_ROOT_PATH)/src/core/ngx_murmurhash.h \ $(NGX_ROOT_PATH)/src/core/ngx_md5.h \ $(NGX_ROOT_PATH)/src/core/ngx_sha1.h \ $(NGX_ROOT_PATH)/src/core/ngx_rbtree.h \ $(NGX_ROOT_PATH)/src/core/ngx_radix_tree.h \ $(NGX_ROOT_PATH)/src/core/ngx_slab.h \ $(NGX_ROOT_PATH)/src/core/ngx_times.h \ $(NGX_ROOT_PATH)/src/core/ngx_shmtx.h \ $(NGX_ROOT_PATH)/src/core/ngx_connection.h \ $(NGX_ROOT_PATH)/src/core/ngx_cycle.h \ $(NGX_ROOT_PATH)/src/core/ngx_conf_file.h \ $(NGX_ROOT_PATH)/src/core/ngx_resolver.h \ $(NGX_ROOT_PATH)/src/core/ngx_open_file_cache.h \ $(NGX_ROOT_PATH)/src/core/ngx_crypt.h \ $(NGX_ROOT_PATH)/src/event/ngx_event.h \ $(NGX_ROOT_PATH)/src/event/ngx_event_timer.h \ $(NGX_ROOT_PATH)/src/event/ngx_event_posted.h \ $(NGX_ROOT_PATH)/src/event/ngx_event_busy_lock.h \ $(NGX_ROOT_PATH)/src/event/ngx_event_connect.h \ $(NGX_ROOT_PATH)/src/event/ngx_event_pipe.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_time.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_errno.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_alloc.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_files.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_channel.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_shmem.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_process.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_setproctitle.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_atomic.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_gcc_atomic_x86.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_thread.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_socket.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_os.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_user.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_process_cycle.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_linux_config.h \ $(NGX_ROOT_PATH)/src/os/unix/ngx_linux.h \ $(NGX_ROOT_PATH)/src/core/ngx_regex.h \ $(NGX_ROOT_PATH)/objs/ngx_auto_config.h NGX_PALLOC = $(NGX_ROOT_PATH)/objs/src/core/ngx_palloc.o NGX_STRING = $(NGX_ROOT_PATH)/objs/src/core/ngx_string.o NGX_ALLOC = $(NGX_ROOT_PATH)/objs/src/os/unix/ngx_alloc.o $(TARGETS): $(TARGETS_C_FILE) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $(CC) $(CFLAGS) $(INCLUDE_PATH) $^ -o $@ $(NGX_PALLOC):$(CORE_DEPS) \ $(NGX_ROOT_PATH)/src/core/ngx_palloc.c $(CC) -c -g -o0 $(INCLUDE_PATH) -o $(NGX_PALLOC) $(NGX_ROOT_PATH)/src/core/ngx_palloc.c $(NGX_ALLOC):$(CORE_DEPS) \ $(NGX_ROOT_PATH)/src/os/unix/ngx_alloc.c $(CC) -c -g -o0 $(INCLUDE_PATH) -o $(NGX_ALLOC) $(NGX_ROOT_PATH)/src/os/unix/ngx_alloc.c $(NGX_STRING):$(CORE_DEPS) \ $(NGX_ROOT_PATH)/src/core/ngx_string.c $(CC) -c -g -o0 $(INCLUDE_PATH) -o $(NGX_STRING) $(NGX_ROOT_PATH)/src/core/ngx_string.c
運行結果:
[root@localhost pool]# ./pool_t +++++++++++CREATE NEW POOL++++++++++++ ------------------------------ pool begin at: 0x8f33020 ->d :0x8f33020 last = 0x8f33048 end = 0x8f343a8 next = 0x0 failed = 0 ->max :4960 ->current :0x8f33020 ->chain :0x0 ->large :0x0 ->cleanup :0x0 ->log :0x0 +++++++++++ALLOC 2500+++++++++++++++++ ------------------------------ pool begin at: 0x8f33020 ->d :0x8f33020 last = 0x8f33a0c end = 0x8f343a8 next = 0x0 failed = 0 ->max :4960 ->current :0x8f33020 ->chain :0x0 ->large :0x0 ->cleanup :0x0 ->log :0x0 +++++++++++ALLOC 2500+++++++++++++++++ ------------------------------ pool begin at: 0x8f33020 ->d :0x8f33020 last = 0x8f33a0c end = 0x8f343a8 next = 0x8f343c0 failed = 0 ->d :0x8f343c0 last = 0x8f34d94 end = 0x8f35748 next = 0x0 failed = 0 ->max :4960 ->current :0x8f33020 ->chain :0x0 ->large :0x0 ->cleanup :0x0 ->log :0x0 +++++++++++ALLOC LARGE 5000+++++++++++ ------------------------------ pool begin at: 0x8f33020 ->d :0x8f33020 last = 0x8f33a14 end = 0x8f343a8 next = 0x8f343c0 failed = 0 ->d :0x8f343c0 last = 0x8f34d94 end = 0x8f35748 next = 0x0 failed = 0 ->max :4960 ->current :0x8f33020 ->chain :0x0 ->large :0x8f33a0c next = 0x0 alloc = 0x8f35750 ->cleanup :0x0 ->log :0x0 +++++++++++ALLOC LARGE 5000+++++++++++ ------------------------------ pool begin at: 0x8f33020 ->d :0x8f33020 last = 0x8f33a1c end = 0x8f343a8 next = 0x8f343c0 failed = 0 ->d :0x8f343c0 last = 0x8f34d94 end = 0x8f35748 next = 0x0 failed = 0 ->max :4960 ->current :0x8f33020 ->chain :0x0 ->large :0x8f33a14 next = 0x8f33a0c alloc = 0x8f36ae0 ->large :0x8f33a0c next = 0x0 alloc = 0x8f35750 ->cleanup :0x0 ->log :0x0
總結:從上面的例子初步能看出一些nginx pool使用的輪廓,不過這兒沒有涉及到failed的處理。
五,內存池的釋放
這部分在nginx中主要是利用其本身web server的特性來完成的;web server老是不停的接受鏈接及
請求,nginx中有不一樣等級的內存池,有進程級的,鏈接級的及請求級的,這樣內存總會在對應的進程,
鏈接,或者請求終止時進行內存池的銷燬。