摘要:數據塊指針指向內存塊底部,表示可用空間的邊界。內存池銷毀函數。
這里只介紹nginx_pool主要的大小內存申請、回收及其高效的內存分配機制具體的實現。
1.nginx_create_pool(size_t size, ngx_log_t *log)這個函數是內存池的創建函數。 第一個參數是內存池的大小(一次最大可申請的小塊空間大小),其實實際的小塊空間單次最大可申請大小還需要用size減去sizeof(ngx_pool_t)(內存池頭部結構體的大小):
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; //日志信息 }; /*內存池數據塊信息*/ typedef struct { u_char *last; //這一塊內存塊中可以分配出去的內存地址 u_char *end; //指向這一塊內存最后 ngx_pool_t *next; //下一個內存塊 ngx_uint_t failed; //在這一塊內存塊上分配失敗的次數 } ngx_pool_data_t;
所以size最小應該為sizeof(ngx_pool_t),其最大不能超過NGX_MAX_ALLOC_FROM_POOL:
#define NGX_MAX_ALLOC_FROM_POOL (ngx_pagesize - 1)
也就是最大不可以超過ngx_pagesize - 1。第二個參數是日志信息參數。
(1)p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);ngx_memalign()是為內存池的創建申請內存的函數,它實際調用的是ngx_alloc()函數:
#define ngx_memalign(alignment, size, log) ngx_alloc(size, log)
ngx_alloc()函數的實現:
void *ngx_alloc(size_t size, ngx_log_t *log) { void *p; p = malloc(size); //具體申請內存是通過malloc if (p == NULL) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "malloc(%uz) failed", size); } ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size); return p; }
與ngx_alloc()函數相似的一個函數ngx_calloc(),比ngx_alloc多了一步初始化的操作,這一點和malloc和calloc比較相似。
(2)內存池信息初始化(這里直接看代碼比較容易):p->d.last = (u_char *) p + sizeof(ngx_pool_t);// 1 p->d.end = (u_char *) p + size;// 2 p->d.next = NULL;// 3 p->d.failed = 0;// 4 size = size - sizeof(ngx_pool_t);// 5 p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;// 6 p->current = p;// 7 p->chain = NULL; p->large = NULL; p->cleanup = NULL; p->log = log;
1:數據塊last指針指向ngx_pool_s結構體下方,表示可用空間的開頭。nginx_create_pool其實就做了兩件事,申請空間和數據初始化,也就是把內存池的頭部做好,為以后的操作做鋪墊。 2.ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align) 小塊內存分配函數2:數據塊end指針指向內存塊底部,表示可用空間的邊界。
3:下一個可用內存塊指針置NULL,因為內存池是增長的,剛開始只有一塊小內存,內存塊鏈是一個不夠再申請一個,這樣一個個增加上去的。所以這里就是NULL。
4:failed這一塊內存塊上分配失敗的次數置零,當次數超過一定值時,這一塊的內存上的current就會指向一個下一個失敗次數低于限制的可用的內存塊。下一次直接可以從可用的內存塊上分配,減少遍歷內存塊鏈的時間,我認為這是ngx_pool在內存分配上采取的一個高效的措施。
5:這個size是表示當前內存塊上實際可用的空間大小。減去ngx_pool_s的大小就是剩下的實際可以用來分配的大小。
6:max用來區分申請的大小是小塊內存還是大塊內存。這里對它的大小做了一個限制,就是最大為ngx_pagesize - 1。
7:current表示下一個可以分配的內存塊,初始化為當前的內存塊,因為當前內存塊是可用的。
第一個參數是內存池頭部結構體指針,第二個是要申請的大小,第三個參數是一個內存對齊的標志,1就是需要按內存對齊申請,0就是不內存對齊。
(1)ngx_align_ptr(p, a)這是內存對齊函數,實際上是一個宏:
#define ngx_align_ptr(p, a) (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
其中a是sizeof(unsigned long),也就是說32位系統按照四字節對齊,64位系統按照8字節對齊。這個對齊方式是這樣的,拿32位系統,按照四字節對齊方式:p + 3(0000 0000 0000 0011),如果p的最低兩位不是0(沒有對齊),便會向上進位,然后再與上~(0000 0000 0000 0011) = (1111 1111 1111 1100),這樣最低位的全為0且會大于原來p,不會越界。如果最低兩位是0,那么加上3再與上~(3),就還是原來的值。這樣就實現了內存對齊。那么為什么要內存對齊呢?因為內存對齊,可以大大增加cpu讀取內存的效率,減少cpu不必要的I/O次數。
(2)再內存塊上循環找一個可以分配的內存塊,從current開始。如果沒有合適的,重新申請一塊和之前的一樣大小的內存塊用于這一次的分配。內存池的增長就是這一步: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; //分配申請的小內存 /*這里的循環就是把current移到failed次數低于四次的小內存塊上*/ 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; }3.static void ngx_palloc_large(ngx_pool_t pool, size_t size)
大塊內存分配函數。第一個參數是內存池頭pool,第二個參數是大小。
(1)第一步申請大塊內存,通過ngx_alloc(),其實底層是通過malloc申請的。p = ngx_alloc(size, pool->log);(2)第二步是將申請的大塊內存地址放到ngx_pool_large_t結構體中。
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; //當前結構體插入到大塊內存鏈
這一步是在大塊內存鏈上找一個空的大塊內存結構體把當前的大塊內存地址放到里面進行管理,這里的n,就是又一個高效的地方,為了減少遍歷鏈表的時間,只要遍歷超過三次,便不再查找,會在小塊內存上申請一個小空間來管理這塊大內存,然后把大塊內存結構體插入到當前的鏈表上。4.ngx_int_t ngx_pfree(ngx_pool_t pool, void p)
大塊內存釋放函數。第一個參數是內存池pool,第二個是要釋放的內存地址。
這里的大塊內存是用malloc申請的,所以釋放要用free。釋放之后,大塊內存的結構體不用釋放,因為是從小塊內存上申請的空間,所以是沒辦法釋放的,這樣也有一個好處就是以后的大塊內存可以節省申請空間的時間,更加高效。5.void ngx_destroy_pool(ngx_pool_t *pool)
內存池銷毀函數。如果說內存池是一個對象,那么銷毀函數就是一個析構函數,把所有資源釋放,但是還是需要把小塊內存上的所有對象都析構。然后再free大塊的內存,最后就是釋放所有小塊內存。下面是源碼:
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); } } /*debugger模式下對內存池的跟蹤*/ #if (NGX_DEBUG) /* * we could allocate the pool->log from this pool * so we cannot use this log while free()ing the pool */ for (l = pool->large; l; l = l->next) { ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc); } 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 (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; } } }6.void ngx_reset_pool(ngx_pool_t *pool)
內存池重置函數。參數只有一個要重置的內存池。
reset和destory的區別在于,reset不用釋放所有小塊內存,重置之后可以繼續使用;destory之后,這個內存池的所有資源都釋放,內存池已經不存在了。所以reset需要釋放所有大塊內存釋放,然后把所有的小塊重置為可分配狀態。源碼:
void ngx_reset_pool(ngx_pool_t *pool) { ngx_pool_t *p; ngx_pool_large_t *l; /*釋放所有大塊空間*/ for (l = pool->large; l; l = l->next) { if (l->alloc) { ngx_free(l->alloc); } } /*把所有小塊內存*/ for (p = pool; p; p = p->d.next) { p->d.last = (u_char *) p + sizeof(ngx_pool_t); p->d.failed = 0; } pool->current = pool; pool->chain = NULL; pool->large = NULL; }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/39858.html
摘要:解析第題第題為什么的和的中不能做異步操作解析第題第題京東下面代碼中在什么情況下會打印解析第題第題介紹下及其應用。盡量減少操作次數。解析第題第題京東快手周一算法題之兩數之和給定一個整數數組和一個目標值,找出數組中和為目標值的兩個數。 引言 半年時間,幾千人參與,精選大廠前端面試高頻 100 題,這就是「壹題」。 在 2019 年 1 月 21 日這天,「壹題」項目正式開始,在這之后每個工...
摘要:事實是只是部分語言的不同表示法。基于這些,解析器會進行立即或者懶解析。然而,解析器做了完全不相關的額外無用功即解析函數。這里不解析函數,該函數聲明了卻沒有指出其用途。所以之前的例子,解析器實際上 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十四章。 概...
摘要:事實是只是部分語言的不同表示法。基于這些,解析器會進行立即或者懶解析。然而,解析器做了完全不相關的額外無用功即解析函數。這里不解析函數,該函數聲明了卻沒有指出其用途。所以之前的例子,解析器實際上 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十四章。 概...
摘要:事實是只是部分語言的不同表示法。基于這些,解析器會進行立即或者懶解析。然而,解析器做了完全不相關的額外無用功即解析函數。這里不解析函數,該函數聲明了卻沒有指出其用途。所以之前的例子,解析器實際上 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第十四章。 概...
閱讀 3824·2021-10-12 10:11
閱讀 3641·2021-09-13 10:27
閱讀 2548·2019-08-30 15:53
閱讀 1978·2019-08-29 18:33
閱讀 2197·2019-08-29 14:03
閱讀 1001·2019-08-29 13:27
閱讀 3322·2019-08-28 18:07
閱讀 775·2019-08-26 13:23