ptmalloc - malloc 終章
是不是覺得我的文章名起得很不好,我也覺得,不過,反正我是程序員,只要變量名、函數名、類名和文件名起得好就行了,其他的,不管。
不過,實話實說,這是關於malloc函數的終章了,malloc其實就是包含了幾個重要函數,__libc_malloc -> _int_malloc -> sysmalloc,__libc_malloc和sysmalloc已經全部說過了,_int_malloc也提到部分。這一章講解的就是_int_malloc裏面最後的一部分,也是最長的一部分,是不是感覺看這麼多代碼很累,是的,反正我複製粘貼我都覺得累。
目錄
- _int_malloc的結構
- unknown
- 總結
_int_malloc的結構
_int_malloc 裏面的代碼按照功能,其實可以分割爲以下部分
- 從fast中獲取內存塊 (ptmalloc - 小小內存的分配和申請)
- 從regular bin (medium size) 中獲取內存塊 (ptmalloc - 小塊內存管理初探)
- code = unknown
- 所有地方都匹配不到適合的,就sysmalloc (ptmalloc - 第一次申請小內存)
unknown就是我們這章要討論的
誠實預告片,接近要看300+行的代碼,沒耐心請點關閉。
unknown
先看一下最外層的代碼吧
static void *
_int_malloc(mstate av, size_t bytes)
{
/* ... */
/* unknown start */
for (; ; ) {
/* ... */
}
}
別激動!正戲開始了。
unknown part1 - unsort
在釋放一塊內存的時候,free是不會內存塊放進去regular bin裏面的,而是會把內存塊進入一個叫unsort的雙鏈表裏面,關於free的詳解,以後會說到,現在先說說unsort。
/* ... */
/* 如果unsort裏面的back指針不等於自身,說明有未排序的內存塊在 */
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) {
bck = victim->bk;
size = chunksize (victim);
/* 如果申請的內存是小內存 */
if (in_smallbin_range (nb) &&
/* 並且unsort裏面也只剩下一塊內存了 */
bck == unsorted_chunks (av) &&
/* 當前塊在分配小內存時被分割過,具體設置下面有 */
victim == av->last_remainder &&
/* 從sort裏面拿出來的那塊內存大於申請的 */
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
/* 將unsort中獲取的那塊分割成兩半 */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
/* 將分隔剩下的那塊取代就有的那塊鏈回去unsort */
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
/* 與大塊內存相關的指針設置 */
if (!in_smallbin_range (remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
/* 設置獲取到的內存塊的頭部參數 */
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
/* 設置分割剩下的內存塊的頭部參數 */
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
return chunk2mem (victim);
}
/* ... */
}
/* ... */
能碰到unsort裏面只剩下一塊內存的情況就是,要麼是隻放了一塊,要麼是跑了那麼多個循環沒碰到適合的,也只是剩下一塊內存了,這兩種情況下,好好的使用這塊內存,分割下,另外的存起來也是無可厚非,你也可以當成一種垂死掙扎,快點脫離循環的一種渴望。
/* 把victim從unsort中除名 */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
/* 如果剛剛好victim的size等於申請的 */
if (size == nb) {
set_inuse_bit_at_offset (victim, size);
return chunk2mem (victim);
}
上上面的看看是否能夠分割剩下的那塊內存是垂死掙扎,那麼到了這裏纔是步入正規
if (in_smallbin_range (size)) {
/* 小塊內存就好辦了,直接鏈上對應的bin */
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
} else {
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
/* 當前鏈表是否爲空 */
if (fwd != bck) {
size |= PREV_INUSE;
/* large bin的是按照大小排序的,恰好,bck->bk就是size最小那一塊 */
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) {
fwd = bck;
bck = bck->bk;
/* 鏈上size鏈 */
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
} else {
/* 找最小 */
while ((unsigned long) size < chunksize_nomask (fwd)) {
fwd = fwd->fd_nextsize;
}
/*
如果恰好相等,那我就不放上size鏈了,從當前代碼來看
size只是大小的排序,有一個就好了
*/
if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd)) {
/* Always insert in the second position. */
fwd = fwd->fd;
} else {
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
} else {
/* 初始化當前large bin的size雙鏈 */
victim->fd_nextsize = victim->bk_nextsize = victim;
}
}
/* 標記當前bin已使用,具體下面章節會說到 */
mark_bin (av, victim_index);
/* 鏈上鍊表 */
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
#define MAX_ITERS 10000
/* 如果處理了10000塊unsort還是沒有心儀的,那就走吧,不挽留了 */
if (++iters >= MAX_ITERS)
break;
}
看了上面的代碼,是不是覺得,好像還是挺容易理解的,心想,很快就可以完結malloc的代碼了
hlt,想太多了
看過我之前文章的其實應該還記得,其實每個bin之間是16 byte差距,那麼申請的時候,11 byte和12 byte是沒差距的,它們最終都會被磨成32 byte。那其實是沒差距啊,那爲何上面還要排序呢?
unsort第一部分代碼記不記得,分割。這就就造成了大塊有可能被割裂。這當然是一種節省內存的做法,但另一方面卻也增加了代碼複雜度。借用以前寫英文作文的套路句子。Every coin has two side。
另外,從size鏈和bin鏈的關係其實能想到一種數據結構,跳錶,一個雙層跳錶。
unknown part2 - large request
if (!in_smallbin_range (nb)) {
bin = bin_at (av, idx);
#define first(bin) (bin)->fd
/* bin->bk是size最小,fd就是size最大 */
if ((victim = first (bin)) != bin &&
(unsigned long) chunksize_nomask (victim) >= (unsigned long) (nb)) {
victim = victim->bk_nextsize;
/* 用size查找最適合 - best fit */
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
/* 不挪走當前size的第一塊是爲了避免重新構造skip list */
if (victim != last (bin) &&
chunksize_nomask (victim) == chunksize_nomask (victim->fd))
victim = victim->fd;
remainder_size = size - nb;
/* 從size鏈和bin鏈上取下來,並重新構建skip list */
unlink (av, victim, bck, fwd);
/* 不能再分割的情況 */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset (victim, size);
}
/* 大塊分割 */
else {
/* 以下這段代碼,估計你看到過很多次了,我就懶得寫註釋了 */
remainder = chunk_at_offset (victim, nb);
bck = unsorted_chunks (av);
fwd = bck->fd;
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size)) {
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
return chunk2mem (victim);
}
}
unknown part3 - bit map
/* 當前索引找不到,+1 */
++idx;
bin = bin_at (av, idx);
/* 具體這個bit map就是,每一個bin如果有內存塊就把對應位設爲1 */
unsigned int block = idx2block (idx);
unsigned int map = av->binmap[block];
unsigned int bit = idx2bit (idx);
for (;; ) {
/* 如果當前bit的數值已經大於map,或者bit爲0 */
if (bit > map || bit == 0) {
/* 一直搜索,直到某個map裏面存在非空bin */
do {
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} while ((map = av->binmap[block]) == 0);
bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}
/* 找到被設置的bin的bit */
while ((bit & map) == 0) {
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}
victim = last (bin);
/* 如果這個bin是空的,清掉bit位 */
if (victim == bin) {
av->binmap[block] = map &= ~bit;
bin = next_bin (bin);
bit <<= 1;
} else {
size = chunksize (victim);
remainder_size = size - nb;
/* 取下來 */
unlink (av, victim, bck, fwd);
/* 耗盡 */
if (remainder_size < MINSIZE) {
set_inuse_bit_at_offset (victim, size);
}
/* 分割 */
else {
remainder = chunk_at_offset (victim, nb);
/* 將分割後的塊丟到unsort裏面去 */
bck = unsorted_chunks (av);
fwd = bck->fd;
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* 如果請求是小塊內存,把被分割設置成最後被分割塊 */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size)){
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
return chunk2mem(victim);
}
}
到此,unknown部分已經完全講解完畢。
總結
下回分解!