ptmalloc - 第一次申請小內存

ptmalloc - 第一次申請小內存


glibc中malloc的代碼包括了對線程同步,平臺兼容性等問題的處理,但是本系列文章主要的研究對象ptmalloc。所以提供的代碼都是經過簡化,部分宏也會展開,能夠說清楚ptmalloc的運行流程就可以了。

要點


  • 用例
  • 第一次申請小內存

用例


int main(void)
{
    char   *mem = malloc(10);

    free(mem);

    return  -1;
}

第一次申請小內存


通過gdb調試,我們可以看到,第一個進入的函數是__libc_malloc

void *__libc_malloc(size_t bytes)
{
    mstate ar_ptr;
    void *victim;
    void *(*hook) (size_t, const void *) = __malloc_hook;

    if (hook != NULL) {
        hook(bytes, 0);
    }

    ar_ptr = thread_arena;
    victim = _int_malloc(ar_ptr, bytes);

    return victim;
}

查看__malloc_hook的初始賦值

__malloc_hook = malloc_hook_ini;

繼續跟蹤malloc_hook_ini

static void *
malloc_hook_ini (size_t sz, const void *caller)
{
    __malloc_hook = NULL;
    ptmalloc_init ();

    return __libc_malloc (sz);
}

static void
ptmalloc_init (void)
{
    if (__malloc_initialized >= 0)
        return;

    __malloc_initialized = 0;
    thread_arena = &main_arena;
}

從malloc_hook_ini 可以看到,最後調用的就是__libc_malloc,往回看,因爲hook被設置爲NULL,所以接下來調用的就是_int_malloc,先放些宏出來,如果不想看可以直接跳過去看簡化的_int_malloc代碼

#define SIZE_SZ sizeof(size_t)

/*
 * 如果size_t的大小是8,那麼MALLOC_ALIGN_MASK就是8 * 2 -1
 * 2進制是01111111,代碼中的作用就是通過位運算將數值16bit對齊
 */
#define MALLOC_ALIGN_MASK (SIZE_SZ * 2 - 1)

/*
 * 最小塊大小,每次申請返回的內存都是經過大小調整後加上MIN_CHUNK_SIZE
 * 這樣,每次返回的內存前面就包含了兩個size_t的數值
 * mchunk_size和mchunk_prev_size
 * x86_64下,sizeof(size_t) = 8,那麼MIN_CHUNK_SIZE = 16
 */
#define MIN_CHUNK_SIZE \
    (offsetof(struct malloc_chunk, fd_nextsize))

/*
 * (16 + 15) & ~15 = 16
 */
#define MINSIZE \
    ((MIN_CHUNK_SIZE + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/*
 * 注意代碼中的是-2,-2 * MINSIZE然後再強轉後就是一個很大的數值了
 */
#define REQUEST_OUT_OF_RANGE(req) \                             
    ((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE))

#define request2size(req) \
    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
    MINSIZE : \
    /* (req + 8 + 15) & ~15 */
    ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

#define checked_request2size(req, sz) \
    if (REQUEST_OUT_OF_RANGE (req)) { \
        __set_errno (ENOMEM); \
        return 0; \
    } \
    (sz) = request2size (req);

當size是10的話,參數nb的值就是32,想知道怎麼算出來的直接看以上貼出來的宏就可以了

struct malloc_chunk {
    INTERNAL_SIZE_T      mchunk_prev_size;
    INTERNAL_SIZE_T      mchunk_size;

    struct malloc_chunk* fd;
    struct malloc_chunk* bk;

    struct malloc_chunk* fd_nextsize;
    struct malloc_chunk* bk_nextsize;
};

struct mstate {
    ...
    int flags;

    mchunkptr top;
    mchunkptr last_remainder;
    mchunkptr bins[254];
    ...
};

typedef struct malloc_chunk *mbinptr;
typedef struct malloc_state *mstate;

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

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

    /* Establish circular links for normal bins */
    for (i = 1; i < NBINS; ++i) {
        bin = bin_at (av, i);
        bin->fd = bin->bk = bin;
    }

#if MORECORE_CONTIGUOUS
    if (av != &main_arena)
#endif
        av->flags |= NONCONTIGUOUS_BIT;

    if (av == &main_arena)
        global_max_fast = 128;

    av->flags |= FASTCHUNKS_BIT;
    av->top = bin_at(av, 1);
}

static void *
_int_malloc (mstate av, size_t bytes)
{
    int32_t nb = 32;

    malloc_init_state(av);

    return sysmalloc(nb, av);
}

代碼是很多,不過第一次申請也準備到達盡頭了,最後一個函數sysmalloc

static struct malloc_par mp_ =
{
    ...
    .top_pad = 0x20000,
    ...
};

#define PREV_INUSE 0x1

static void *
sysmalloc(size_t nb, mstate av)
{
    size_t pagesize = 4096;
    /*
     * 可以看出第一次申請內存的時候
     * ptmalloc會申請一塊很大的內存
     */
    size_t size = nb + mp_.top_pad + MIN_SIZE;

    char *brk;

    /* size必須要pagesize的倍數 */
    size = (size + pagesize - 1) & -pagesize;

    if (size > 0) {
        brk = sbrk(size);
    }

    if (brk != NULL) {
        av->system_mem += size;
    }

    if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
        av->max_system_mem = av->system_mem;

    size_t remainder_size;
    mchunkptr p = av->top, remainder;

    if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) {
        remainder_size = size - nb;
        remainder = p + nb;
        av->top = remainder;

        /*
         * 要記得nb永遠是16的倍數,所以數值的第一字節都是0xX0000
         */
        p->mchunk_size = nb | PREV_INUSE;
        remainder->mchunk_size = remainder_size | PREV_INUSE

        return p + MIN_CHUNK_SIZE;
    }
}

本來的代碼有更多的分支,處理,判斷,我上面的代碼都簡化了很多,如果想感受ptmalloc的代碼難看性,自行下載吧,下一章有可能是第一次申請大內存或者釋放小內存,看心情吧。

完結撒花

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