寫在前面
本文的內(nèi)存池代碼是改編自Nginx的內(nèi)存池源碼,思路幾乎一樣。由于Nginx源碼的變量命名我不喜歡,又沒有注釋,看得我很難受。想自己寫一版容易理解的代碼。
應(yīng)用場景
寫內(nèi)存池的原理之前,按照慣例先說內(nèi)存池的應(yīng)用場景。
為什么我們需要內(nèi)存池?
- 因?yàn)閙alloc等分配內(nèi)存的方式,需要涉及到系統(tǒng)調(diào)用sbrk,頻繁的malloc和free會消耗系統(tǒng)資源。
既然如此,我們就預(yù)先在用戶態(tài)創(chuàng)建一個緩存空間,作為內(nèi)存池。
每次malloc的時候,從用戶態(tài)的內(nèi)存池中獲得分配的內(nèi)存,不走系統(tǒng)調(diào)用,就能像賽車加氮?dú)庖粯映壖铀賰?nèi)存管理。
2.如果頻繁地malloc和free,由于malloc的地址是不確定的,因?yàn)槊看蝝alloc的時候,會先在freelist中找一個適合其大小的塊,如果找不到,才會調(diào)用sbrk直接拓展堆的內(nèi)存邊界。(freelist是之前free掉的內(nèi)存,內(nèi)核會將其組織成一個鏈表,留待下次malloc的時候查找使用。)
因?yàn)椴淮_定,所以容易產(chǎn)生內(nèi)存碎片。
如果我們需要4個字節(jié)的空間,卻因?yàn)閙alloc的位置隨機(jī)分配在這個滑稽的位置,就導(dǎo)致雖然我們有2+2的空間但是只能望洋興嘆的尷尬處境。
Nginx內(nèi)存池的特點(diǎn)
nginx中線程池與內(nèi)存池都是池,核心思想都是對系統(tǒng)的資源調(diào)度起一個緩沖的作用,但是多少還是有點(diǎn)區(qū)別。
對于線程池來說,兩隊(duì)列+中樞的架構(gòu),在不同公司的框架實(shí)現(xiàn)都大同小異。
但是內(nèi)存池卻是在不同的框架中,實(shí)現(xiàn)不盡相同。
為什么會有區(qū)別,根本原因是面向的實(shí)際業(yè)務(wù)不同,從而導(dǎo)致不同公司的內(nèi)存池會靈活變通,有各自鮮明的特點(diǎn)。
在Nginx的服務(wù)器中,每當(dāng)有一個客戶端connect進(jìn)來后,就會為其單獨(dú)創(chuàng)建一個內(nèi)存池,用于recv和send的緩沖區(qū)buffer。
所以Nginx的內(nèi)存池的特點(diǎn)是,其包含了兩種內(nèi)存分配方式,大內(nèi)存和小內(nèi)存使用不同的數(shù)據(jù)結(jié)構(gòu)來存儲。從而適應(yīng)客戶端不同的請求,如果只是一些簡單的表單,就用小內(nèi)存,如果是上傳下載大文件,就用大內(nèi)存。
同時Nginx的內(nèi)存池還有一個重要的特點(diǎn):不像線程池會回收利用所有線程,Nginx的內(nèi)存池不回收小內(nèi)存的buffer,只回收大內(nèi)存的buffer。
同樣是出于實(shí)際業(yè)務(wù)的考慮,每個內(nèi)存池都對應(yīng)一個客戶,那么一個客戶端產(chǎn)生的小內(nèi)存碎片自然不會太多,即使不回收,也不會有太大代價。
同時tcp本身就有keep-alive機(jī)制,超過一定時間就斷開,Nginx是典型的將不同客戶端分發(fā)到多進(jìn)程的網(wǎng)絡(luò)模型,連接斷開,進(jìn)程結(jié)束,從而對應(yīng)的內(nèi)存池會釋放,相當(dāng)于一次性回收所有的大小內(nèi)存。
但是在連接中大內(nèi)存因?yàn)檎加每臻g大,Nginx覺得還是有必要回收,所以只做了回收大內(nèi)存這個接口。
數(shù)據(jù)結(jié)構(gòu)
我們先講核心的,小內(nèi)存的實(shí)現(xiàn)。
首先整個內(nèi)存池pool中有兩條鏈,一條是big block鏈,一條是small block鏈。
一個small block我們可以看做是一個小緩沖區(qū),而整條鏈的小緩沖區(qū)串起來組成一個大緩沖區(qū),就是內(nèi)存池了。
small block數(shù)據(jù)結(jié)構(gòu)如下:
public:
char * cur_usable_buffer;
char * buffer_end;
small_block * next_block;
int no_enough_times;
};
- cur_usable_buffer:指向該block的可用buffer的首地址
- buffer_end:指向該block的buffer的結(jié)尾地址
- next_block: 指向block鏈的下一個small block
- no_enough_times:每次分配內(nèi)存,都要順著small block鏈,找鏈中的每個小緩沖區(qū)看是否有足夠分配的內(nèi)存,如果在該block沒找到,就會將該值+1,代表沒有足夠空間命中的次數(shù)。
對于small block,我們看它的格局要更上層一點(diǎn),達(dá)到看山不是山,看水不是水的境界,因?yàn)樗粌H僅是單獨(dú)的block對象,還代表了后面跟著的buffer,當(dāng)鏈中所有小緩沖區(qū)都不夠位置分配新的空間時,就會創(chuàng)建新的small block,而創(chuàng)建的時候,會一次性創(chuàng)建small_block+buffer_capacity大小的空間。
為什么要這么設(shè)置,而不是先malloc一個small_block,再malloc一個small_buffer,別問,問就是不優(yōu)雅,像這樣分配內(nèi)存,相當(dāng)于兩個連在一起形成一個整塊,很舒適,適合強(qiáng)迫癥,而不是東一塊隨機(jī)地址,西一塊隨機(jī)地址,那還叫池嗎?干脆叫地下水管道吧,一堆支流。
我們不需要在small_block的數(shù)據(jù)結(jié)構(gòu)中存buffer的首地址指針,因?yàn)楹茏匀坏氖牵覀兡玫絪mall_block的指針后自然也就拿到了buffer的首地址指針
即 buffer_head_ptr = (char*)small_block + sizeof(small_block);
small_block中各指針的指向:
然后是整個內(nèi)存池pool的數(shù)據(jù)結(jié)構(gòu)
public:
size_t small_buffer_capacity;
small_block * cur_usable_small_block;
big_block * big_block_start;
small_block small_block_start[0];
//-----------------------------上面是成員,下面是api--------------------------------------------
static memory_pool * createPool(size_t capacity);
static void destroyPool(memory_pool * pool);
static char* createNewSmallBlock(memory_pool * pool,size_t size);
static char* mallocBigBlock(memory_pool * pool,size_t size);
static void* poolMalloc(memory_pool * pool,size_t size);
static void freeBigBlock(memory_pool * pool, char *buffer_ptr);
};
- small_buffer_capacity:對于Nginx的內(nèi)存池,每個small buffer的大小都是一樣的,所以該值代表了small buffer的容量,在創(chuàng)建內(nèi)存池的時候作為參數(shù)確定。
- cur_usable_small_block:每次要分配小內(nèi)存的時候,并不會從頭開始找合適的空間,而是從這個指針指向的small_block開始找。
- big_block_start:big block鏈的鏈頭
- small_block_start:small block的鏈頭。
這里要提一點(diǎn)的是,該類的最后一個成員small_block_start,其為一個長度為0的數(shù)組,這在C99中是一種柔性數(shù)組的寫法,所以比較吃編譯器,我是在linux環(huán)境下編譯,沒什么問題,但是vs環(huán)境可能會報錯,如果報錯就設(shè)置長度為1。
為什么要這么設(shè)置,后面講第一個api createPool 的時候會說。
還有全員使用靜態(tài)成員方法也是與此有關(guān),因?yàn)镹ginx是純C寫的,我使用靜態(tài)成員方法也是一種兼容C的面向過程函數(shù)的變種吧(笑~)。
最后是big block的數(shù)據(jù)結(jié)構(gòu)
public:
char * big_buffer;
big_block * next_block;
};
- big_buffer:大內(nèi)存buffer的首地址
- next_block:因?yàn)閎ig block也是鏈?zhǔn)浇Y(jié)構(gòu),指向下一個big block
big block最簡單,因?yàn)镹ginx似乎不在乎big block優(yōu)不優(yōu)雅了,其big_block和big_buffer的地址就是分開的,不會連在一起。
接口實(shí)現(xiàn)
創(chuàng)建內(nèi)存池 createPool:
這里需要再次提到剛剛的柔性數(shù)組,small_block_start
Nginx希望創(chuàng)建內(nèi)存池pool的時候,不是單獨(dú)一個孤零零的pool對象,而是創(chuàng)建pool的同時,就創(chuàng)建第一個small_block,而創(chuàng)建一個新的small_block又需要同步建立一個small_buffer,而Nginx希望這三個對象的內(nèi)存是連起來的,如圖所示,于是優(yōu)雅再次出現(xiàn)。
柔性數(shù)組的意義在于,我們希望在memory_pool保留一個指針錨點(diǎn)來指向第一個small_block而不是通過剛剛的指針加法來找到small_block的首地址,那便使用一個0長度的數(shù)組作為這個錨點(diǎn)。
這樣malloc整段內(nèi)存,small_block就會接在memory_pool的后面,且以small_block_start的形式成為pool的成員,實(shí)際上small_block_start長度為0是不占pool的內(nèi)存空間的。
而為什么使用靜態(tài)成員函數(shù)也是這個原因,使用柔性數(shù)組必須保證其位置定義在整個類的內(nèi)存空間的末尾,靜態(tài)函數(shù)雖然在類中聲明,但是實(shí)際會存放在靜態(tài)區(qū)中保存,不占用類的內(nèi)存。
//-創(chuàng)建內(nèi)存池并初始化,api以靜態(tài)成員(工廠)的方式模擬C風(fēng)格函數(shù)實(shí)現(xiàn)
//-capacity是buffer的容量,在初始化的時候確定,后續(xù)所有小塊的buffer都是這個大小
memory_pool * memory_pool::createPool(size_t capacity){//-我們先分配一大段連續(xù)內(nèi)存,該內(nèi)存可以想象成這段內(nèi)存由pool+small_block+small_block_buffers三個部分組成.
//-為什么要把三個部分(可以理解為三個對象)用連續(xù)內(nèi)存來存,因?yàn)檫@樣整個池看起來比較優(yōu)雅.各部分地址不會天女散花地落在內(nèi)存的各個角落.
size_t total_size = sizeof(memory_pool)+sizeof(small_block)+capacity;
void *temp = malloc(total_size);
memset(temp,0,total_size);
memory_pool * pool = (memory_pool*)temp;
fprintf(stdout,"pool address:%pn",pool);
//-此時temp是pool的指針,先來初始化pool對象
pool ->small_buffer_capacity = capacity;
pool ->big_block_start = nullptr;
pool ->cur_usable_small_block = (small_block*)(pool->small_block_start);
//-pool+1的1是整個memory_pool的步長,別弄錯了。此時sbp是small_block的指針
small_block * sbp = (small_block*)(pool+1);
fprintf(stdout,"first small block address:%pn",sbp);
//-初始化small_block對象
sbp -> cur_usable_buffer = (char*)(sbp+1);
fprintf(stdout,"first small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = sbp->cur_usable_buffer+capacity;//-第一個可用的buffer就是開頭,所以end=開頭+capacity
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;
return pool;
};
代替malloc的分配內(nèi)存的接口:poolMalloc
第一步,我們判斷poolMalloc的size是一個大內(nèi)存還是小內(nèi)存。
如果是大內(nèi)存就走mallocBigBlock這個api。
如果是小內(nèi)存,就從cur_usable_small_block這個small block開始找足夠的空間去分配內(nèi)存,注意并不是從small block鏈的開頭開始尋找。因?yàn)榇蟾怕蔯ur_usable_small_block之前的所有small block都已經(jīng)分配完了,所以為了提高命中效率,需要這樣一個指針指向?qū)ふ业拈_始。
對于每個small block,我們直接用buffer_end 和 cur_usable_buffer相減就可以得到一個small buffer的剩余容量去判斷是否能分配。
如果空間足夠,就從cur_usable_buffer開始分配size大小的空間,并返回這段空間的首地址,同時更新cur_usable_buffer指向新的剩余空間。
如果直到鏈的末尾都沒有足夠的size大小的空間,那就需要創(chuàng)建新的small block,走createNewSmallBlock這個api。
void* memory_pool::poolMalloc(memory_pool * pool,size_t size){
//-先判斷要malloc的是大內(nèi)存還是小內(nèi)存
if(sizesmall_buffer_capacity){//-如果是小內(nèi)存
//-從cur small block開始尋找
small_block * temp = pool -> cur_usable_small_block;
do{
//-判斷當(dāng)前small block的buffer夠不夠分配
//-如果夠分配,直接返回
if(temp->buffer_end-temp->cur_usable_buffer>size){
char * res = temp->cur_usable_buffer;
temp -> cur_usable_buffer = temp->cur_usable_buffer+size;
return res;
}
temp = temp->next_block;
}while (temp);
//-如果最后一個small block都不夠分配,則創(chuàng)建新的small block;
//-該small block在創(chuàng)建后,直接預(yù)先分配size大小的空間,所以返回即可.
return createNewSmallBlock(pool,size);
}
//-分配大內(nèi)存
return mallocBigBlock(pool,size);
}
創(chuàng)建新的小內(nèi)存塊:createNewSmallBlock
首先創(chuàng)建一個smallblock和連帶的buffer,還是如這張圖所示:
因?yàn)槲覀儎?chuàng)建的目的是為了分配size空間,所以初始化后,便預(yù)留size大小的buffer,對cur_usable_buffer進(jìn)行更新。
值得提的是,每次到了創(chuàng)建新的small block的環(huán)節(jié),就意味著目前鏈上的small buffer空間已經(jīng)都分配得差不多了,可能需要更新cur_usable_small_block,這就需要用到small block的no_enough_times成員,將cur_usable_small_block開始的每個small block的該值++,Nginx設(shè)置的經(jīng)驗(yàn)值閾值是4,超過4,意味著該block不適合再成為尋找的開始了,需要往后繼續(xù)嘗試。
char* memory_pool::createNewSmallBlock(memory_pool * pool,size_t size){
//-先創(chuàng)建新的small block,注意還有buffer
size_t malloc_size = sizeof(small_block)+pool->small_buffer_capacity;
void * temp = malloc(malloc_size);
memset(temp,0,malloc_size);
//-初始化新的small block
small_block * sbp = (small_block *)temp;
fprintf(stdout,"new small block address:%pn",sbp);
sbp -> cur_usable_buffer = (char*)(sbp+1);//-跨越一個small_block的步長
fprintf(stdout,"new small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = (char*)temp+malloc_size;
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;
//-預(yù)留size空間給新分配的內(nèi)存
char* res = sbp -> cur_usable_buffer;//-存?zhèn)€副本作為返回值
sbp -> cur_usable_buffer = res+size;
//-因?yàn)槟壳暗乃衧mall_block都沒有足夠的空間了。
//-意味著可能需要更新線程池的cur_usable_small_block,也就是尋找的起點(diǎn)
small_block * p = pool -> cur_usable_small_block;
while(p->next_block){
if(p->no_enough_times>4){
pool -> cur_usable_small_block = p->next_block;
}
++(p->no_enough_times);
p = p->next_block;
}
//-此時p正好指向當(dāng)前pool中最后一個small_block,將新節(jié)點(diǎn)接上去。
p->next_block = sbp;
//-因?yàn)樽詈笠粋€block有可能no_enough_times>4導(dǎo)致cur_usable_small_block更新成nullptr
//-所以還要判斷一下
if(pool -> cur_usable_small_block == nullptr){
pool -> cur_usable_small_block = sbp;
}
return res;//-返回新分配內(nèi)存的首地址
}
分配大內(nèi)存空間:mallocBigBlock
如果size超過了預(yù)設(shè)的capacity,那就會走這個api。
其同樣也是一個鏈?zhǔn)讲檎业倪^程,只不過比查找small block更快更粗暴。
big block鏈沒有類似cur_usable_small_block這樣的節(jié)點(diǎn),只要從頭開始遍歷,如果有空buffer就返回該block,如果超過3個還沒找到(同樣是Nginx的經(jīng)驗(yàn)值)就直接不找了,創(chuàng)建新的big block。
還有一點(diǎn)值得注意的是,big_buffer是個大內(nèi)存,所以其是個malloc的隨機(jī)地址,
但是big_block本身是一個小內(nèi)存,那就不應(yīng)該還是用隨機(jī)地址,應(yīng)該保存在內(nèi)存池內(nèi)部的空間。
所以這里有個套娃的內(nèi)存池poolMalloc操作,用來分配big_block的空間。
char* memory_pool::mallocBigBlock(memory_pool * pool,size_t size){
//-先分配size大小的空間
void*temp = malloc(size);
memset(temp, 0, size);
//-從big_block_start開始尋找,注意big block是一個棧式鏈,插入新元素是插入到頭結(jié)點(diǎn)的位置。
big_block * bbp = pool->big_block_start;
int i = 0;
while(bbp){
if(bbp->big_buffer == nullptr){
bbp->big_buffer = (char*)temp;
return bbp->big_buffer;
}
if(i>3){
break;//-為了保證效率,如果找三輪還沒找到有空buffer的big_block,就直接建立新的big_block
}
bbp = bbp->next_block;
++i;
}
//-創(chuàng)建新的big_block,這里比較難懂的點(diǎn),就是Nginx覺得big_block的buffer雖然是一個隨機(jī)地址的大內(nèi)存
//-但是big_block本身算一個小內(nèi)存,那就不應(yīng)該還是用隨機(jī)地址,應(yīng)該保存在內(nèi)存池內(nèi)部的空間。
//-所以這里有個套娃的內(nèi)存池malloc操作
big_block* new_bbp = (big_block*)memory_pool::poolMalloc(pool,sizeof(big_block));
//-初始化
new_bbp -> big_buffer = (char*)temp;
new_bbp ->next_block = pool->big_block_start;
pool -> big_block_start = new_bbp;
//-返回分配內(nèi)存的首地址
return new_bbp->big_buffer;
}
釋放大內(nèi)存:freeBigBlock:
由于big block是一個鏈?zhǔn)浇Y(jié)構(gòu),所以要找到對應(yīng)的buffer并free掉,就需要從這個鏈的開頭開始遍歷,一直到找到位置。
void memory_pool::freeBigBlock(memory_pool * pool, char *buffer_ptr){
big_block* bbp = pool -> big_block_start;
while(bbp){
if(bbp->big_buffer == buffer_ptr){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
return;
}
bbp = bbp->next_block;
}
}
銷毀線程池:destroyPool:
這個思路也很簡單,pool中有兩條鏈分別指向大內(nèi)存和小內(nèi)存,那么分別沿著這兩條鏈去free掉內(nèi)存即可,由于大內(nèi)存的buffer和big block不是一起malloc的,所以只需要free掉buffer,而big block是分配在小內(nèi)存池中的,所以,之后free掉小內(nèi)存的時候會順帶一起free掉。
比較值得注意的一點(diǎn)是,small鏈的free不是從第一個small block開始的,而是第二個small block。如圖所示,第一個small block的空間是和pool一起malloc出來的,不需要free,只要最后的時候free pool就會一起釋放掉。
void memory_pool::destroyPool(memory_pool * pool){
//-銷毀大內(nèi)存
big_block * bbp = pool->big_block_start;while(bbp){
if(bbp->big_buffer){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
}
bbp = bbp->next_block;
}
//-為什么不刪除big_block節(jié)點(diǎn)?因?yàn)閎ig_block在小內(nèi)存池中,等會就和小內(nèi)存池一起銷毀了
//-銷毀小內(nèi)存
small_block * temp = pool -> small_block_start->next_block;
while(temp){
small_block * next = temp -> next_block;
free(temp);
temp = next;
}
free(pool);
}
測試代碼
測試一下用內(nèi)存池分配的地址是否如我們所設(shè)計的那樣。
memory_pool * pool = memory_pool::createPool(1024);
//-分配小內(nèi)存
char*p1 = (char*)memory_pool::poolMalloc(pool,2);
fprintf(stdout,"little malloc1:%pn",p1);
char*p2 = (char*)memory_pool::poolMalloc(pool,4);
fprintf(stdout,"little malloc2:%pn",p2);
char*p3 = (char*)memory_pool::poolMalloc(pool,8);
fprintf(stdout,"little malloc3:%pn",p3);
char*p4 = (char*)memory_pool::poolMalloc(pool,256);
fprintf(stdout,"little malloc4:%pn",p4);
char*p5 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc5:%pn",p5);
//-測試分配不足開辟新的small block
char*p6 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc6:%pn",p6);
//-測試分配大內(nèi)存
char*p7 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc1:%pn",p7);
char*p8 = (char*)memory_pool::poolMalloc(pool,4096);
fprintf(stdout,"big malloc2:%pn",p8);
//-測試free大內(nèi)存
memory_pool::freeBigBlock(pool, p8);
//-測試再次分配大內(nèi)存(我這里測試結(jié)果和p8一樣)
char*p9 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc3:%pn",p9);
//-銷毀內(nèi)存池
memory_pool::destroyPool(pool);
exit(EXIT_SUCCESS);
}
完整代碼
// * 修改了很多nginx中晦澀的變量名,比較容易理解
#include
#include
#include
using namespace std;
class small_block{
public:
char * cur_usable_buffer;
char * buffer_end;
small_block * next_block;
int no_enough_times;
};
class big_block{
public:
char * big_buffer;
big_block * next_block;
};
class memory_pool{
public:
size_t small_buffer_capacity;
small_block * cur_usable_small_block;
big_block * big_block_start;
small_block small_block_start[0];
static memory_pool * createPool(size_t capacity);
static void destroyPool(memory_pool * pool);
static char* createNewSmallBlock(memory_pool * pool,size_t size);
static char* mallocBigBlock(memory_pool * pool,size_t size);
static void* poolMalloc(memory_pool * pool,size_t size);
static void freeBigBlock(memory_pool * pool, char *buffer_ptr);
};
//-創(chuàng)建內(nèi)存池并初始化,api以靜態(tài)成員(工廠)的方式模擬C風(fēng)格函數(shù)實(shí)現(xiàn)
//-capacity是buffer的容量,在初始化的時候確定,后續(xù)所有小塊的buffer都是這個大小
memory_pool * memory_pool::createPool(size_t capacity){
//-我們先分配一大段連續(xù)內(nèi)存,該內(nèi)存可以想象成這段內(nèi)存由pool+small_block+small_block_buffers三個部分組成.
//-為什么要把三個部分(可以理解為三個對象)用連續(xù)內(nèi)存來存,因?yàn)檫@樣整個池看起來比較優(yōu)雅.各部分地址不會天女散花地落在內(nèi)存的各個角落.
size_t total_size = sizeof(memory_pool)+sizeof(small_block)+capacity;
void *temp = malloc(total_size);
memset(temp,0,total_size);
memory_pool * pool = (memory_pool*)temp;
fprintf(stdout,"pool address:%pn",pool);
//-此時temp是pool的指針,先來初始化pool對象
pool ->small_buffer_capacity = capacity;
pool ->big_block_start = nullptr;
pool ->cur_usable_small_block = (small_block*)(pool->small_block_start);
//-pool+1的1是整個memory_pool的步長,別弄錯了。此時sbp是small_block的指針
small_block * sbp = (small_block*)(pool+1);
fprintf(stdout,"first small block address:%pn",sbp);
//-初始化small_block對象
sbp -> cur_usable_buffer = (char*)(sbp+1);
fprintf(stdout,"first small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = sbp->cur_usable_buffer+capacity;//-第一個可用的buffer就是開頭,所以end=開頭+capacity
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;
return pool;
};
//-銷毀內(nèi)存池
void memory_pool::destroyPool(memory_pool * pool){
//-銷毀大內(nèi)存
big_block * bbp = pool->big_block_start;
while(bbp){
if(bbp->big_buffer){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
}
bbp = bbp->next_block;
}
//-為什么不刪除big_block節(jié)點(diǎn)?因?yàn)閎ig_block在小內(nèi)存池中,等會就和小內(nèi)存池一起銷毀了
//-銷毀小內(nèi)存
small_block * temp = pool -> small_block_start->next_block;
while(temp){
small_block * next = temp -> next_block;
free(temp);
temp = next;
}
free(pool);
}
//-當(dāng)所有small block都沒有足夠空間分配,則創(chuàng)建新的small block并分配size空間,返回分配空間的首指針
char* memory_pool::createNewSmallBlock(memory_pool * pool,size_t size){
//-先創(chuàng)建新的small block,注意還有buffer
size_t malloc_size = sizeof(small_block)+pool->small_buffer_capacity;
void * temp = malloc(malloc_size);
memset(temp,0,malloc_size);
//-初始化新的small block
small_block * sbp = (small_block *)temp;
fprintf(stdout,"new small block address:%pn",sbp);
sbp -> cur_usable_buffer = (char*)(sbp+1);//-跨越一個small_block的步長
fprintf(stdout,"new small block buffer address:%pn",sbp->cur_usable_buffer);
sbp -> buffer_end = (char*)temp+malloc_size;
sbp -> next_block = nullptr;
sbp -> no_enough_times = 0;
//-預(yù)留size空間給新分配的內(nèi)存
char* res = sbp -> cur_usable_buffer;//-存?zhèn)€副本作為返回值
sbp -> cur_usable_buffer = res+size;
//-因?yàn)槟壳暗乃衧mall_block都沒有足夠的空間了。
//-意味著可能需要更新線程池的cur_usable_small_block,也就是尋找的起點(diǎn)
small_block * p = pool -> cur_usable_small_block;
while(p->next_block){
if(p->no_enough_times>4){
pool -> cur_usable_small_block = p->next_block;
}
++(p->no_enough_times);
p = p->next_block;
}
//-此時p正好指向當(dāng)前pool中最后一個small_block,將新節(jié)點(diǎn)接上去。
p->next_block = sbp;
//-因?yàn)樽詈笠粋€block有可能no_enough_times>4導(dǎo)致cur_usable_small_block更新成nullptr
//-所以還要判斷一下
if(pool -> cur_usable_small_block == nullptr){
pool -> cur_usable_small_block = sbp;
}
return res;//-返回新分配內(nèi)存的首地址
}
//-分配大塊的內(nèi)存
char* memory_pool::mallocBigBlock(memory_pool * pool,size_t size){
//-先分配size大小的空間
void*temp = malloc(size);
memset(temp, 0, size);
//-從big_block_start開始尋找,注意big block是一個棧式鏈,插入新元素是插入到頭結(jié)點(diǎn)的位置。
big_block * bbp = pool->big_block_start;
int i = 0;
while(bbp){
if(bbp->big_buffer == nullptr){
bbp->big_buffer = (char*)temp;
return bbp->big_buffer;
}
if(i>3){
break;//-為了保證效率,如果找三輪還沒找到有空buffer的big_block,就直接建立新的big_block
}
bbp = bbp->next_block;
++i;
}
//-創(chuàng)建新的big_block,這里比較難懂的點(diǎn),就是Nginx覺得big_block的buffer雖然是一個隨機(jī)地址的大內(nèi)存
//-但是big_block本身算一個小內(nèi)存,那就不應(yīng)該還是用隨機(jī)地址,應(yīng)該保存在內(nèi)存池內(nèi)部的空間。
//-所以這里有個套娃的內(nèi)存池malloc操作
big_block* new_bbp = (big_block*)memory_pool::poolMalloc(pool,sizeof(big_block));
//-初始化
new_bbp -> big_buffer = (char*)temp;
new_bbp ->next_block = pool->big_block_start;
pool -> big_block_start = new_bbp;
//-返回分配內(nèi)存的首地址
return new_bbp->big_buffer;
}
//-分配內(nèi)存
void* memory_pool::poolMalloc(memory_pool * pool,size_t size){
//-先判斷要malloc的是大內(nèi)存還是小內(nèi)存
if(sizesmall_buffer_capacity){//-如果是小內(nèi)存
//-從cur small block開始尋找
small_block * temp = pool -> cur_usable_small_block;
do{
//-判斷當(dāng)前small block的buffer夠不夠分配
//-如果夠分配,直接返回
if(temp->buffer_end-temp->cur_usable_buffer>size){
char * res = temp->cur_usable_buffer;
temp -> cur_usable_buffer = temp->cur_usable_buffer+size;
return res;
}
temp = temp->next_block;
}while (temp);
//-如果最后一個small block都不夠分配,則創(chuàng)建新的small block;
//-該small block在創(chuàng)建后,直接預(yù)先分配size大小的空間,所以返回即可.
return createNewSmallBlock(pool,size);
}
//-分配大內(nèi)存
return mallocBigBlock(pool,size);
}
//-釋放大內(nèi)存的buffer,由于是一個鏈表,所以,確實(shí),這是效率最低的一個api了
void memory_pool::freeBigBlock(memory_pool * pool, char *buffer_ptr){
big_block* bbp = pool -> big_block_start;
while(bbp){
if(bbp->big_buffer == buffer_ptr){
free(bbp->big_buffer);
bbp->big_buffer = nullptr;
return;
}
bbp = bbp->next_block;
}
}
int main(){
memory_pool * pool = memory_pool::createPool(1024);
//-分配小內(nèi)存
char*p1 = (char*)memory_pool::poolMalloc(pool,2);
fprintf(stdout,"little malloc1:%pn",p1);
char*p2 = (char*)memory_pool::poolMalloc(pool,4);
fprintf(stdout,"little malloc2:%pn",p2);
char*p3 = (char*)memory_pool::poolMalloc(pool,8);
fprintf(stdout,"little malloc3:%pn",p3);
char*p4 = (char*)memory_pool::poolMalloc(pool,256);
fprintf(stdout,"little malloc4:%pn",p4);
char*p5 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc5:%pn",p5);
//-測試分配不足開辟新的small block
char*p6 = (char*)memory_pool::poolMalloc(pool,512);
fprintf(stdout,"little malloc6:%pn",p6);
//-測試分配大內(nèi)存
char*p7 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc1:%pn",p7);
char*p8 = (char*)memory_pool::poolMalloc(pool,4096);
fprintf(stdout,"big malloc2:%pn",p8);
//-測試free大內(nèi)存
memory_pool::freeBigBlock(pool, p8);
//-測試再次分配大內(nèi)存(我這里測試結(jié)果和p8一樣)
char*p9 = (char*)memory_pool::poolMalloc(pool,2048);
fprintf(stdout,"big malloc3:%pn",p9);
//-銷毀內(nèi)存池
memory_pool::destroyPool(pool);
exit(EXIT_SUCCESS);
}
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
3020瀏覽量
74009 -
緩存
+關(guān)注
關(guān)注
1文章
239瀏覽量
26673 -
源碼
+關(guān)注
關(guān)注
8文章
639瀏覽量
29185 -
nginx
+關(guān)注
關(guān)注
0文章
149瀏覽量
12171
發(fā)布評論請先 登錄
相關(guān)推薦
評論