ptmalloc - malloc 終章

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部分已經完全講解完畢。

總結


下回分解!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章