1. 程式人生 > >malloc原始碼分析---3

malloc原始碼分析---3

malloc原始碼分析—_int_malloc

上一章分析了_int_malloc的前面一小部分,本章繼續往下看,

_int_malloc — fastbin

static void * _int_malloc(mstate av, size_t bytes) {

    ...

    if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) {
        idx = fastbin_index(nb);
        mfastbinptr *fb = &fastbin(av, idx);
        mchunkptr pp = *fb;
        do {
            victim = pp;
            if
(victim == NULL) break; } while ((pp = catomic_compare_and_exchange_val_acq(fb, victim->fd, victim)) != victim); if (victim != 0) { if (__builtin_expect(fastbin_index (chunksize (victim)) != idx, 0)) { errstr = "malloc(): memory corruption (fast)"
; errout: malloc_printerr(check_action, errstr, chunk2mem(victim), av); return NULL; }check_remalloced_chunk (av, victim, nb); void *p = chunk2mem(victim); alloc_perturb(p, bytes); return p; } } ...
}

get_max_fast返回fastbin可以儲存記憶體的最大值,它在ptmalloc的初始化函式malloc_init_state中定義,後面會分析這個函式。
如果需要分配的記憶體大小nb落在fastbin的範圍內,首先呼叫fastbin_index獲得chunk大小nb對應的fastbin索引。

#define fastbin_index(sz) \
  ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

減2是根據fastbin儲存的記憶體最小值計算的,本章假設SIZE_SZ=4,因此改寫後idx = nb/8-2
獲得索引idx後,就通過fastbin取出空閒chunk連結串列指標,mfastbinptr其實就是malloc_chunk指標,

#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

下面的do、while迴圈又是一個CAS操作,其作用是從剛剛得到的空閒chunk連結串列指標中取出第一個空閒的chunk(victim),並將連結串列頭設定為該空閒chunk的下一個chunk(victim->fd)。這裡注意,fastbin中使用的是單鏈表,而後面smallbin使用的是雙鏈表。
獲得空閒chunk後,需要轉換為可以儲存的記憶體指標,chunk2mem上一章分析過了,就是返回malloc_chunk結構中fd所在的位置,因為當一個chunk被使用時,malloc_chunk結構中fdbk包括後面的變數都沒有用了。最後呼叫alloc_perturb對使用者使用的記憶體進行初始化,然後就返回該記憶體的指標了。
假設fastbin中沒有找到空閒chunk,或者fastbin根本沒有初始化,或者其他原因,就進入下一步,從smallbin中獲取記憶體,因此繼續往下看.

_int_malloc — smallbin & largebin

static void * _int_malloc(mstate av, size_t bytes) {

    ...

    if (in_smallbin_range(nb)) {
        idx = smallbin_index(nb);
        bin = bin_at (av, idx);

        if ((victim = last(bin)) != bin) {
            if (victim == 0)
                malloc_consolidate(av);
            else {
                bck = victim->bk;
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                set_inuse_bit_at_offset(victim, nb);
                bin->bk = bck;
                bck->fd = bin;

                if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                check_malloced_chunk (av, victim, nb);
                void *p = chunk2mem(victim);
                alloc_perturb(p, bytes);
                return p;
            }
        }
    }else {
        idx = largebin_index(nb);
        if (have_fastchunks(av))
            malloc_consolidate(av);
    }

    ...

}

首先

#define in_smallbin_range(sz)  \
  ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)

基於本章假設,MIN_LARGE_SIZE經過換算後為512位元組,因此低於512位元組大小的記憶體塊都歸smallbin管理。
接下來通過bin_at獲得smallbin空閒chunk連結串列指標,

#define bin_at(m, i) \
  (mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))               \
             - offsetof (struct malloc_chunk, fd))

這裡乘2,並且減去fd相對於malloc_chunk中的位置是因為smallbin中儲存的是fd和bk指標。
last定義為

#define last(b)      ((b)->bk)

該函式獲得chunk的前一個chunk,由因為該chunk是smallbin的連結串列頭,因此獲得的是最後一個chunk,如果兩者相等,表示對應的連結串列為空,什麼都不做。
這裡假設不相等,接下來有兩種情況,第一種是victim=0,表示smallbin還沒有初始化,這裡需要特別說明一下這裡。smallbin初始化為malloc_chunk指標陣列,雖然定義為指標陣列,但實際上儲存的是fd和bk指標,如下所示
|fd|bk|fd|bk|…|fd|bk|
當smallbin還未初始化時,假設idx=1,根據bin_at取出的bin是一個虛擬的malloc_chunk指標,bin->fd,是第二個fd,因此bin->bk就是對應的bk,其值為0(bin->bk取出的不是地址,而是值)。因此當victim為0時,可以斷定smallbin未初始化,此時呼叫malloc_consolidate進行初始化,

static void malloc_consolidate(mstate av) {

    ...

    if (get_max_fast () != 0) {

        ...

    } else {
        malloc_init_state(av);
        check_malloc_state(av);
    }
}

省略程式碼的if語句裡是將fastbin中的chunk進行合併,然後新增到bins中,這裡不分析,因為還未初始化,因此get_max_fast返回0,後面的章節碰到了再分析。進入else部分,check_malloc_state為空函式,malloc_init_state就是主要的初始化函式,

static void malloc_init_state(mstate av) {
    int i;
    mbinptr bin;

    for (i = 1; i < NBINS; ++i) {
        bin = bin_at (av, i);
        bin->fd = bin->bk = bin;
    }

#if MORECORE_CONTIGUOUS
    if (av != &main_arena)
#endif
        set_noncontiguous(av);
    if (av == &main_arena)
        set_max_fast(DEFAULT_MXFAST);
    av->flags |= FASTCHUNKS_BIT;

    av->top = initial_top (av);
}

該函式做了四件事情,第一是初始化malloc_state中的bins陣列,初始化的結果是對bins陣列中的每一個fd和對應的bk,都初始化為fd的地址,即fd=bk=&fd;第二是設定fastbin可管理的記憶體塊的最大值,即global_max_fastDEFAULT_MXFAST定義為,

#define DEFAULT_MXFAST     (64 * SIZE_SZ / 4)

本章假設為64,set_max_fast定義為

#define set_max_fast(s) \
  global_max_fast = (((s) == 0)                           \
                     ? SMALLBIN_WIDTH : ((s + SIZE_SZ) & ~MALLOC_ALIGN_MASK))

第三是設定一些標誌位;第四是初始化分配去中的top chunk,就是一個malloc_chunk指標,fd儲存在bins[0]中(smallbin中不使用bins[0]bins[1])。
重新回到_int_malloc中,假設victim不為0,下面就從雙向連結串列中取出victim,設定其中的標誌位,然後返回使用者可分配的記憶體指標。
假設smallbin中沒有空閒chunk可用,下面就要開始尋找largebin了,largebin_index定義為

#define largebin_index(sz) \
  (SIZE_SZ == 8 ? largebin_index_64 (sz)                                     \
   : MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz)                     \
   : largebin_index_32 (sz))

根據前面SIZE_SZ的假設,這裡largebin_index對應的就是largebin_index_32,定義為

#define largebin_index_32(sz)                                                \
  (((((unsigned long) (sz)) >> 6) <= 38) ?  56 + (((unsigned long) (sz)) >> 6) :\
   ((((unsigned long) (sz)) >> 9) <= 20) ?  91 + (((unsigned long) (sz)) >> 9) :\
   ((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
   ((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
   ((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
   126)

這裡就不多解釋了,如果需要知道sz和索引的對應關係,可以自己計算一下。
再接下來have_fastchunks根據標誌位判斷fastbin中是否有空閒chunk,如果有,就呼叫malloc_consolidate將這些chunk和並,然後加入到unsortedbin中。

_int_malloc — 合併fastbin

下面重新看一下malloc_consolidate函式。

static void malloc_consolidate(mstate av) {
    mfastbinptr* fb;
    mfastbinptr* maxfb;
    mchunkptr p;
    mchunkptr nextp;
    mchunkptr unsorted_bin;
    mchunkptr first_unsorted;

    mchunkptr nextchunk;
    INTERNAL_SIZE_T size;
    INTERNAL_SIZE_T nextsize;
    INTERNAL_SIZE_T prevsize;
    int nextinuse;
    mchunkptr bck;
    mchunkptr fwd;

    if (get_max_fast () != 0) {
        clear_fastchunks(av);
        unsorted_bin = unsorted_chunks(av);

        maxfb = &fastbin(av, NFASTBINS - 1);
        fb = &fastbin(av, 0);
        do {
            p = atomic_exchange_acq(fb, 0);
            if (p != 0) {
                do {
                    check_inuse_chunk(av, p);
                    nextp = p->fd;

                    size = p->size & ~(PREV_INUSE | NON_MAIN_ARENA);
                    nextchunk = chunk_at_offset(p, size);
                    nextsize = chunksize(nextchunk);

                    if (!prev_inuse(p)) {
                        prevsize = p->prev_size;
                        size += prevsize;
                        p = chunk_at_offset(p, -((long ) prevsize));
                        unlink(av, p, bck, fwd);
                    }

                    if (nextchunk != av->top) {
                        nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

                        if (!nextinuse) {
                            size += nextsize;
                            unlink(av, nextchunk, bck, fwd);
                        } else
                            clear_inuse_bit_at_offset(nextchunk, 0);

                        first_unsorted = unsorted_bin->fd;
                        unsorted_bin->fd = p;
                        first_unsorted->bk = p;

                        if (!in_smallbin_range(size)) {
                            p->fd_nextsize = NULL;
                            p->bk_nextsize = NULL;
                        }

                        set_head(p, size | PREV_INUSE);
                        p->bk = unsorted_bin;
                        p->fd = first_unsorted;
                        set_foot(p, size);
                    }

                    else {
                        size += nextsize;
                        set_head(p, size | PREV_INUSE);
                        av->top = p;
                    }

                } while ((p = nextp) != 0);

            }
        } while (fb++ != maxfb);
    } else {

        ...

    }
}

因為ptmalloc前面已經初始化過了,這裡直接進入if內部,首先通過clear_fastchunks設定標誌位表示fastbin中存在空閒chunk,

#define clear_fastchunks(M)    catomic_or (&(M)->flags, FASTCHUNKS_BIT)

然後通過unsorted_chunks獲得bins陣列中unsortedbin對應的malloc_chunk指標(其fdbk指標對應bins[0]bins[1])。

#define unsorted_chunks(M)          (bin_at (M, 1))

再往下,將fastbin中的最大和最小的chunk對應的malloc_chunk指標賦值給maxfbfb,然後通過do,while迴圈遍歷fastbin中的每個chunk連結串列,atomic_exchange_acq又是一個CAS操作,該函式取出fb指標,並將原來的chunk連結串列頭指標的值設為0,表示chunk連結串列空閒了。然後開始進入內層的迴圈,這裡遍歷的是每個chunk連結串列中的每個malloc_chunk指標。
接下來首先去除chunk中的PREV_INUSENON_MAIN_ARENA標誌,為了獲得chunk的大小(size中的最低三位被用來作為標誌位,並且fastbin中chunk的標誌位IS_MMAPPED預設為0)。然後通過chunk_at_offsetchunksize獲得下一個chunk以及其大小,

#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s)))
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
#define chunksize(p)         ((p)->size & ~(SIZE_BITS))

再往下,如果chunk的前一個chunk沒在使用中,就合併該chunk與前一個chunk,主要是重新計算malloc_chunk的指標,並呼叫unlink將前一個chunk從bins陣列中刪除,

#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;                                   \
    BK = P->bk;                                   \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))             \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {                                    \
        FD->bk = BK;                                  \
        BK->fd = FD;                                  \
        if (!in_smallbin_range (P->size)                      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {            \
        if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)        \
        || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
          malloc_printerr (check_action,                      \
                   "corrupted double-linked list (not small)",    \
                   P, AV);                        \
            if (FD->fd_nextsize == NULL) {                    \
                if (P->fd_nextsize == P)                      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;             \
                else {                                \
                    FD->fd_nextsize = P->fd_nextsize;                 \
                    FD->bk_nextsize = P->bk_nextsize;                 \
                    P->fd_nextsize->bk_nextsize = FD;                 \
                    P->bk_nextsize->fd_nextsize = FD;                 \
                  }                               \
              } else {                                \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;             \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;             \
              }                                   \
          }                                   \
      }                                       \
}

簡單來說,該巨集定義就是將前一個chunk從兩個雙線連結串列中刪除,fdbk指標構成的雙向連結串列存在於smallbin和largebin中,fd_nextsizebk_nextsize指標構成的雙向連結串列只存在於largebin中。
再往下,如果相鄰的下一個chunk不是top chunk,並且下一個chunk不在使用中,就繼續合併,否則,就清除下一個chunk的PREV_INUSE,表示該chunk已經空閒了。
然後將剛剛合併完的chunk新增進unsorted_bin中,unsorted_bin也是一個雙向連結串列。
如果合併完的chunk屬於smallbin的大小,則需要清除fd_nextsizebk_nextsize,因為smallbin中的chunk不會使用這兩個指標。並且通過setHead保證不會有相鄰的兩個chunk都空閒,並且通過setFoot設定下一個chunk的prev_size
如果相鄰的下一個chunk是top chunk,則將合併完的chunk繼續合併到top chunk中。
至此,malloc_consolidate就分析完了,總結一下,malloc_consolidate就是遍歷fastbin中每個chunk連結串列的每個malloc_chunk指標,合併前一個不在使用中的chunk,如果後一個chunk是top chunk,則直接合併到top chunk中,如果後一個chunk不是top chunk,則合併後一個chunk並新增進unsorted_bin中。

下一章繼續往下分析_int_malloc函式。