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