palloc
是nginx自身實現的一個內存池模塊,其遍佈整個nginx的源碼之中,也是nginx能簡潔高效處理各個請求的基礎所在。本文先從ngx_alloc
和ngx_palloc
2個文件來解讀內存模塊。html
ngx_alloc
文件整個ngx_alloc
包含了3個函數:ngx_alloc
、ngx_calloc
和ngx_memalign
。
其中ngx_alloc
和ngx_calloc
方法都是利用malloc
方法來分配內存,不一樣的是ngx_calloc
方法會在分配後進行初始化工做。
而ngx_memalign
方法,則是利用memalign
或posix_memalign
方法申請一個內存對齊的內存塊。nginx
內存對齊的用處首先是能夠提升cpu效率,由於不對齊會致使cpu訪問內存時候須要拆份內存塊;第二是方便平臺的移植。函數
ngx_palloc
模塊結構體上節的ngx_alloc
文件是對c語言內存的封裝,此後的內存分配都是經過調取其中的三個方法進行的。那麼咱們先來了解一下ngx_palloc
包含的結構體。源碼分析
ngx_pool_s
結構體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; };
ngx_pool_s
結構體是整個內存池的核心結構體。它自己是一個記錄表,其中記錄了整個內內存池的內存分配信息鏈的頭指針。其中主要的屬性分別是d
、large
和cleanup
三個屬性,這也是咱們接下來要了解的三個結構體的指針。學習
ngx_pool_data_t
結構體typedef struct { u_char *last; u_char *end; ngx_pool_t *next; ngx_uint_t failed; } ngx_pool_data_t;
ngx_pool_data_t
結構體其實就像是ngx_pool_s
結構體的一個詳細描述,其中描述了一個內存池的信息,包括當前分配完的內存地址、內存池最後的內存地址、下一個內存池指針以及分配內存失敗次數。ui
ngx_pool_large_s
結構體struct ngx_pool_large_s { ngx_pool_large_t *next; void *alloc; };
這個結構體就比較簡單,就算一個鏈表,幷包含一個指針指向當前分配的內存塊。debug
ngx_pool_cleanup_s
結構體struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next; };
ngx_pool_cleanup_s
結構體的功能主要是用來在銷燬內存池時,須要處理一下其餘的操做來保證內存的正常銷燬,避免內存的泄露。所以,在銷燬內存期間,會觸發這個ngx_pool_cleanup_s
的鏈表,並以此執行銷燬函數。指針
ngx_pool_cleanup_file_t
結構體typedef struct { ngx_fd_t fd; u_char *name; ngx_log_t *log; } ngx_pool_cleanup_file_t;
這個結構體,主要用途就是爲了在銷燬內存塊的時候,能對文件描述符進行關閉等操做。(感受是這樣)code
ngx_palloc
模塊函數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
方法來分配內存塊,而後計算出d.last
和d.end
的2個屬性,其餘屬性都比較容易理解。htm
ngx_palloc
方法以及ngx_pnalloc
方法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_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); }
在分配小塊內存時,就算不斷的尋找是否存在符合條件的內存大小,若存在,則將內存塊地址返回,並將d.last
日後移動分配的內存大小,即完成了內存分配。若不存在,則利用ngx_palloc_block
方法去生成一個新的內存塊。
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; }
該函數其實用途在於從新生成一個新的內存池,同時內存池的大小和最初的內存池是相同大小。關鍵在於,他會對失敗大於4次的內存池的當前指針進行移動,這樣能夠提升以後的內存查找的效率。
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; }
nginx內存池有趣的地方就在於,他們直接可能會互相調用來實現本身的功能,例如當前的方法,首先它回去直接申請一個須要的內存塊,以後它須要去查找ngx_pool_large_t
的鏈表,看看有沒有某個ngx_pool_large_t
的alloc
是爲空的,這樣就能夠將分配好的地址掛載上去。
若不存在,那麼就利用small方法申請一個ngx_pool_large_t
的節點,而後將其加入ngx_pool_large_t
的鏈表中。
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_palloc_large
簡單暴力版,直接申請ngx_pool_large_t
並加入鏈表中。
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) ... ... #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; } } }
ngx_destroy_pool
方法的執行流程主要以下:先進行cleanup
操做,觸發銷燬方法、再進行大塊內存的銷燬、最後銷燬銷燬內存。銷燬方法都是使用ngx_free
,其實就算free
方法。
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_pool_cleanup_file_t
,而後設置handle
屬性,用於之後內存池銷燬。
nginx的內存池功能相對stl的內存池更好理解,也許是代碼風格問題致使閱讀難度的增長。不過學習了nginx的內存分配後,就能夠開始其餘的模塊的閱讀。