1. 程式人生 > >Ptmalloc2筆記-結構體與初始化

Ptmalloc2筆記-結構體與初始化

核心結構體

ptmalloc使用malloc_state來管理分配區,而引數管理使用malloc_par結構體,全域性擁有一個唯一的malloc_par例項.

malloc_state

struct malloc_state { 
    /* Serialize access. */
    mutex_t mutex;

    /* Flags (formerly in max_fast). */
    int flags;

#if THREAD_STATS
    /* Statistics for locking. Only used if THREAD_STATS is defined. */
long stat_lock_direct, stat_lock_loop, stat_lock_wait; #endif /* Fastbins */ mfastbinptr fastbinsY[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2]; /* Bitmap of bins */ unsigned int binmap[BINMAPSIZE]; /* Linked list */ struct malloc_state *next; #ifdef PER_THREAD /* Linked list for free arenas. */ struct malloc_state *next_free; #endif /* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; };

Mutex用於序列化訪問分配區,當有多個執行緒訪問同一個分配區時,第一個獲得這個mutex的執行緒將使用該分配區分配記憶體,分配完成後,釋放該分配區的mutex,以便其它執行緒使用該分配區. Flags記錄了分配區的一些標誌,bit0用於標識分配區是否包含至少一個fast bin chunk,bit1用於標識分配區是否能返回連續的虛擬地址空間.

/* FASTCHUNKS_BIT held in max_fast indicates that there are probably some fastbin chunks. It is set true on entering a chunk into any fastbin, and cleared only in malloc_consolidate. The truth value is inverted so that have_fastchunks will be true upon startup (since statics are zero-filled), simplifying initialization checks. */
#define FASTCHUNKS_BIT (1U)
#define have_fastchunks(M) (((M)->flags & FASTCHUNKS_BIT) == 0)
#ifdef ATOMIC_FASTBINS
    #define clear_fastchunks(M) catomic_or (&(M)->flags, FASTCHUNKS_BIT)
    #define set_fastchunks(M) catomic_and (&(M)->flags, ~FASTCHUNKS_BIT)
#else
    #define clear_fastchunks(M) ((M)->flags |= FASTCHUNKS_BIT)
    #define set_fastchunks(M) ((M)->flags &= ~FASTCHUNKS_BIT)
#endif

這些巨集用於設定flagsfast chunk的標誌位bit0,bit00,表示分配區中有fast chunk,為1表示沒有,初始化後的malloc_stateflags值為0,但實際上沒有,試圖從fast bins中分配chunk都會返回NULL. 在第一次呼叫函式malloc_consolidate()fast bins進行chunk合併時,如果max_fast大於0,會呼叫clear_fastchunks巨集.clear_fastchunks巨集只在函式malloc_consolidate()中呼叫.當有fast chunk加入fast bins時,就呼叫set_fastchunks巨集標識分配區的fast bins中存在fast chunk.

/* NONCONTIGUOUS_BIT indicates that MORECORE does not return contiguous regions. Otherwise, contiguity is exploited in merging together, when possible, results from consecutive MORECORE calls. The initial value comes from MORECORE_CONTIGUOUS, but is changed dynamically if mmap is ever used as an sbrk substitute. */
#define NONCONTIGUOUS_BIT (2U)
#define contiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) == 0)
#define noncontiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) != 0)
#define set_noncontiguous(M) ((M)->flags |= NONCONTIGUOUS_BIT)
#define set_contiguous(M) ((M)->flags &= ~NONCONTIGUOUS_BIT)

flagsbit1如果為0,表示MORCORE返回連續虛擬地址空間,為1,表示返回非連續虛擬地址空間,對於主分配區,MORECORE其實為sbr(),預設返回連續虛擬地址空間,對於非主分配區,使用mmap()分配大塊虛擬記憶體,然後進行切分來模擬主分配區的行為,而預設情況下mmap對映區域是不保證虛擬地址空間連續的,所以非住分配區預設分配非連續虛擬地址空間.

Malloc_state中聲明瞭幾個對鎖的統計變數,預設沒有定義THREAD_STATS,所以不會對鎖的爭用情況做統計.

fastbinsY擁有10(NFASTBINS)個元素的陣列,用於存放每個fast chunk連結串列頭指標,所以fast bins最多包含10個fast chunk的單向連結串列.

top是一個chunk指標,指向分配區的top chunk.

last_remainder是一個chunk指標,分配區上次分配small chunk時,從一個chunk中分裂出一個small chunk返回給使用者,分裂後的剩餘部分形成一個chunk,last_remainder就是指向的這個chunk.

bins用於儲存unstored bin,small binslarge binschunk連結串列頭,bins指標陣列的大小為(128*2-2)*8=2032

binmap欄位是一個int陣列,ptmalloc用一個bit來標識該bit對應的bin中是否包含空閒chunk.

/* Binmap To help compensate for the large number of bins, a one-level index structure is used for bin-by-bin searching. `binmap' is a bitvector recording whether bins are definitely empty so they can be skipped over during during traversals. The bits are NOT always cleared as soon as bins are empty, but instead only when they are noticed to be empty during traversal in malloc. */

/* Conservatively use 32 bits per map word, even if on 64bit system */
#define BINMAPSHIFT 5
#define BITSPERMAP (1U << BINMAPSHIFT)
#define BINMAPSIZE (NBINS / BITSPERMAP)
#define idx2block(i) ((i) >> BINMAPSHIFT)
#define idx2bit(i) ((1U << ((i) & ((1U << BINMAPSHIFT)-1))))
#define mark_bin(m,i) ((m)->binmap[idx2block(i)] |= idx2bit(i))
#define unmark_bin(m,i) ((m)->binmap[idx2block(i)] &= ~(idx2bit(i)))
#define get_binmap(m,i) ((m)->binmap[idx2block(i)] & idx2bit(i))

binmap128bit,binmapint分成4block,每個block32bit,根據bin index使用巨集idx2block得出該binbinmap對應bitblock.idx2bit巨集取第i位為1,其它位都為0的掩碼.mark_bin設定第ibinbinmap中的bit1;unmark_bin設定為0;get_binmap獲取第ibinbinmap中對應的bit.

next欄位用於將分配區以單向連結串列連結起來. next_free欄位將空閒的分配區連結在單向連結串列. system_mem欄位記錄了當前分配區已經分配的記憶體大小. max_system_mem記錄了當前分配區最大能分配的記憶體大小.

malloc_par

struct malloc_par {
    /* Tunable parameters */
    unsigned long trim_threshold;
    INTERNAL_SIZE_T top_pad;
    INTERNAL_SIZE_T mmap_threshold;
#ifdef PER_THREAD
    INTERNAL_SIZE_T arena_test;
    INTERNAL_SIZE_T arena_max;
#endif

    /* Memory map support */
    int n_mmaps;
    int n_mmaps_max;
    int max_n_mmaps;

    /* the mmap_threshold is dynamic, until the user sets it manually, at which point we need to disable any dynamic behavior. */
    int no_dyn_threshold;

    /* Cache malloc_getpagesize */
    unsigned int pagesize; 

    /* Statistics */
    INTERNAL_SIZE_T mmapped_mem;
    INTERNAL_SIZE_T max_mmapped_mem;
    INTERNAL_SIZE_T max_total_mem;

    /* only kept for NO_THREADS */
    /* First address handed out by MORECORE/sbrk. */
    char* sbrk_base;
};

trim_threshold欄位表示收縮閾值,預設為128KB,當分配區的top chunk大小大於這個閾值時,在一定的條件下,呼叫free時會收縮記憶體,減小top chunk的大小.由於mmap分配閾值的動態調整,在free時可能將收縮閾值修改為mmap分配閾值的2倍.收縮閾值可以通過函式mallopt()進行設定.

top_pad欄位表示在分配記憶體時是否新增額外的pad,預設該欄位為0.

mmap_threshold欄位表示mmap分配閾值,預設值128KB,在32位系統上最大值為512KB,64位系統上的最大值為32MB,由於預設開啟mmap分配閾值動態調整,該欄位的值會動態修改.

arena_testarena_max用於PER_THREAD優化,在32位系統上arena_test預設值為2,64位系統上的預設值為8,當每個程序的分配區數量小於等於arena_test時,不會重用已有的分配區.為了限制分配區的總數,用arena_max來儲存分配區的最大數量,當系統中的分配區數量達到arena_max,就不會再建立新的分配區,只會重用已有的分配區.這兩個欄位都可以使用mallopt()函式設定.

n_mmaps欄位表示當前程序使用mmap()函式分配的記憶體塊的個數. n_mmaps_max欄位表示程序使用mmap()函式分配的記憶體塊的最大數量,預設值65536,可使用mallopt()函式修改. max_n_mmaps欄位表示當前程序使用mmap()函式分配的記憶體塊的數量的最大值,有關係n_mmaps <= max_n_mmaps成立.這個欄位是由於mstats()函式輸出統計需要這個欄位.

no_dyn_threshold欄位表示是否開啟mmap分配閾值動態調整機制,預設值為0,也就是預設開啟.

pagesize欄位表示系統的頁大小,預設4KB.

mmapped_memmax_mmapped_mem都用於統計mmap分配的記憶體大小,一般情況下兩個欄位的值相等,max_mmapped_mem用於mstats()函式.

max_total_mem欄位在單執行緒情況下用於統計程序分配的記憶體總數. sbrk_base欄位表示堆的起始地址.

分配區初始化

/* There are several instances of this struct ("arenas") in this malloc. If you are adapting this malloc in a way that does NOT use a static or mmapped malloc_state, you MUST explicitly zero-fill it before using. This malloc relies on the property that malloc_state is initialized to all zeroes (as is true of C statics). */
static struct malloc_state main_arena;

/* There is only one instance of the malloc parameters. */
static struct malloc_par mp_;

/* Maximum size of memory handled in fastbins. */
static INTERNAL_SIZE_T global_max_fast;

main_arena表示主分配區,任何程序有且僅有一個全域性的主分配區,mp_是全域性唯一的一個malloc_par例項,用於管理引數和統計資訊,global_max_fast全域性變量表示fast bins中最大的chunk大小.

分配區main_arena初始化函式

/* Initialize a malloc_state struct. This is called only from within malloc_consolidate, which needs be called in the same contexts anyway. It is never called directly outside of malloc_consolidate because some optimizing compilers try to inline it at all call points, which turns out not to be an optimization at all. (Inlining it in malloc_consolidate is fine though.) */

#if __STD_C static
void malloc_init_state(mstate av)
#else
static void malloc_init_state(av) mstate av;
#endif
{
    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
        set_noncontiguous(av);
    if (av == &main_arena)
        set_max_fast(DEFAULT_MXFAST);
    av->flags |= FASTCHUNKS_BIT;
    av->top = initial_top(av);
}

分配區的初始化函式預設分配區的例項av是全域性靜態變數或是已經將av中的欄位清0. 初始化函式首先遍歷所有的bins,將binfbbk都指向bin本身.av中所有欄位預設為0,即預設分配連續的虛擬地址空間,所以對於非主分配區,需要設定為分配非連續虛擬地址空間.如果初始化的是主分配區,需要設定fast bins中最大chunk大小,由於主分配區只有一個且最先初始化,全域性變數global_max_fast只初始化了一次,只要該全域性變數的值非0,也就意味著主分配區初始化了.最後初始化top chunk.

ptmalloc引數初始化

/* Set up basic state so that _int_malloc et al can work. */
static void ptmalloc_init_minimal (void) {
    #if DEFAULT_TOP_PAD != 0
    mp_.top_pad = DEFAULT_TOP_PAD;
    #endif
    mp_.n_mmaps_max = DEFAULT_MMAP_MAX;
    mp_.mmap_threshold = DEFAULT_MMAP_THRESHOLD;
    mp_.trim_threshold = DEFAULT_TRIM_THRESHOLD;
    mp_.pagesize = malloc_getpagesize;
    #ifdef PER_THREAD
    #define NARENAS_FROM_NCORES(n) ((n) * (sizeof(long) == 4 ? 2 : 8))
    mp_.arena_test = NARENAS_FROM_NCORES (1);
    narenas = 1;
    #endif
}

將全域性變數mp_的欄位初始化為預設值,如果定義了PER_THREAD,會根據系統cpu的個數設定arena_test的值,預設32位系統是雙核,64位系統為8核.

配置選項

mallopt()函式

#if __STD_C
int mALLOPt(int param_number, int value)
#else
int mALLOPt(param_number, value)
int param_number; int value;
#endif
{
    mstate av = &main_arena;
    int res = 1;
    if(__malloc_initialized < 0)
        ptmalloc_init ();
    (void)mutex_lock(&av->mutex);

    /* Ensure initialization/consolidation */
    malloc_consolidate(av);
    switch(param_number) {
        case M_MXFAST:
        if (value >= 0 && value <= MAX_FAST_SIZE) { 
            set_max_fast(value);
        } else res = 0;
        break;

        case M_TRIM_THRESHOLD: mp_.trim_threshold = value;
        mp_.no_dyn_threshold = 1;
        break;

        case M_TOP_PAD: mp_.top_pad = value;
        mp_.no_dyn_threshold = 1;
        break;

        case M_MMAP_THRESHOLD:
        #if USE_ARENAS
        /* Forbid setting the threshold too high. */
        if((unsigned long)value > HEAP_MAX_SIZE/2)
            res = 0;
        else
        #endif
            mp_.mmap_threshold = value;
        mp_.no_dyn_threshold = 1;
        break;

        case M_MMAP_MAX:
        #if !HAVE_MMAP
        if (value != 0)
            res = 0;
        else
        #endif
            mp_.n_mmaps_max = value;
        mp_.no_dyn_threshold = 1;
        break;
        case M_CHECK_ACTION:
        check_action = value;
        break;
        case M_PERTURB:
        perturb_byte = value;
        break;
        #ifdef PER_THREAD
        case M_ARENA_TEST:
        if (value > 0) mp_.arena_test = value;
        break;
        case M_ARENA_MAX:
        if (value > 0) mp_.arena_max = value;
        break;
        #endif
    }
    (void)mutex_unlock(&av->mutex);
    return res;
}

mallopt()函式配置前,需要檢查主分配區是否初始化了,如果沒有初始化,呼叫ptmalloc_init()函式初始化ptmalloc,然後獲得主分配區的鎖,呼叫malloc_consolidate()函式,malloc_consolidate()函式會判斷主分配區是否已經初始化,如果沒有,則初始化主分配區.mp_沒有鎖,對mp_中引數欄位的修改,是通過主分配區的鎖來同步的.

ptmalloc初始化

ptmalloc初始化發生在程序第一個記憶體分配請求,一般都在使用者的第一次呼叫malloc()remalloc()之前,因為作業系統和Glibc庫為程序的初始化做了不少工作,使用者分配記憶體以前,Glibc已經分配了多次記憶體.

ptmallocmalloc()函式的實際介面函式為public_mALLOc(),這個函式開始會執行如下程式碼

__malloc_ptr_t (*hook) (size_t, __const __malloc_ptr_t) = force_reg (__malloc_hook);
if (__builtin_expect (hook != NULL, 0)) return (*hook)(bytes, RETURN_ADDRESS (0));

定義了__malloc_hook()全域性函式的情況下,執行__malloc_hook()函式,在程序初始化__malloc_hook指向的函式為malloc_hook_ini(). __malloc_ptr_t weak_variable (*__malloc_hook) (size_t __size, const __malloc_ptr_t) = malloc_hook_ini malloc_hook_ini()函式定義在hooks.c中,實現程式碼

static Void_t*
#if __STD_C
malloc_hook_ini(size_t sz, const __malloc_ptr_t caller)
#else
malloc_hook_ini(sz, caller) size_t sz; const __malloc_ptr_t caller;
#endif
{
    __malloc_hook = NULL;
    ptmalloc_init();
    return public_mALLOc(sz);
}

ptmalloc_init()函式在初始化ptmalloc完成後,將全域性變數__malloc_initialized設定為1,當public_mALLOc()函式再次執行,先執行malloc_hook_ini()函式,malloc_hook_ini()函式呼叫ptmalloc_init(),ptmalloc_init()函式首先判斷__malloc_initialized是否為1,如果是,則退出ptmalloc_init().

ptmalloc未初始化的分配/釋放記憶體

ptmalloc的初始化函式ptmalloc_init()還沒有呼叫之前,Glibc中可能需要分配記憶體,比如執行緒私有例項的初始化需要分配記憶體,為了解決這一問題,ptmalloc封裝了內部的分配釋放函式供在這種情況下使用. Ptmalloc提供了三個函式,malloc_starter(),memalign_starter(),free_starter().

static Void_t*
#if __STD_C
malloc_starter(size_t sz, const Void_t *caller)
#else
malloc_starter(sz, caller) size_t sz; const Void_t *caller;
#endif
{
    Void_t* victim;
    victim = _int_malloc(&main_arena, sz);
    return victim ? BOUNDED_N(victim, sz) : 0;
}

static Void_t*
#if __STD_C
memalign_starter(size_t align, size_t sz, const Void_t *caller)
#else
memalign_starter(align, sz, caller) size_t align, sz; const Void_t *caller;
#endif
{
    Void_t* victim;
    victim = _int_memalign(&main_arena, align, sz);
    return victim ? BOUNDED_N(victim, sz) : 0;
}

static void
#if __STD_C
free_starter(Void_t* mem, const Void_t *caller)
#else
free_starter(mem, caller) Void_t* mem; const Void_t *caller;
#endif
{
    mchunkptr p;
    if(!mem) return;
    p = mem2chunk(mem);
    #if HAVE_MMAP
    if (chunk_is_mmapped(p)) {
        munmap_chunk(p);
        return;
    }
    #endif
    #ifdef ATOMIC_FASTBINS
    _int_free(&main_arena, p, 1);
    #else
    _int_free(&main_arena, p);
    #endif
}

呼叫ptmalloc的內部實現函式,這裡不詳細介紹.

ptmalloc_init()

ptmalloc_init()函式

static void ptmalloc_init (void) {
    #if __STD_C
    const char* s;
    #else
    char* s;
    #endif
    int secure = 0;
    if(__malloc_initialized >= 0)
        return;
    __malloc_initialized = 0;

首先檢查__malloc_initialized是否大於等於0,如果大於0,表示ptmalloc已初始化.如果為0,表示ptmalloc正在初始化,全域性變數__malloc_initialized用來保證全域性只初始化ptmalloc一次.

#ifdef _LIBC
    #if defined SHARED && !USE___THREAD
    /* ptmalloc_init_minimal may already have been called via __libc_malloc_pthread_startup, above. */
    if (mp_.pagesize == 0)
    #endif
#endif ptmalloc_init_minimal();

#ifndef NO_THREADS
    #if defined _LIBC
    /* We know __pthread_initialize_minimal has already been called, and that is enough. */
    #define NO_STARTER
    #endif
    #ifndef NO_STARTER
    /* With some threads implementations, creating thread-specific data or initializing a mutex may call malloc() itself. Provide a simple starter version (realloc() won't work). */
    save_malloc_hook = __malloc_hook;
    save_memalign_hook = __memalign_hook;
    save_free_hook = __free_hook;
    __malloc_hook = malloc_starter;
    __memalign_hook = memalign_starter;
    __free_hook = free_starter;
        #ifdef _LIBC
        /* Initialize the pthreads interface. */
        if (__pthread_initialize != NULL)
            __pthread_initialize();
        #endif /* !defined _LIBC */
    #endif /* !defined NO_STARTER */
#endif /* !defined NO_THREADS */

為多執行緒版本ptmallocpthread初始化準備,儲存當前的hooks函式,把ptmalloc為初始化時所有使用的分配/釋放函式賦給hooks函式,因為線上程初始化一些私有例項時,ptmalloc還沒有初始化,所以需要做特殊處理.從這些hooks函式可以看出,在ptmalloc未初始化時,不能使用remalloc函式.在相關的hooks函式賦值以後,執行pthread_initilaize()初始化pthread.

mutex_init(&main_arena.mutex);
main_arena.next = &main_arena;

初始化主分配區的mutex,並將主分配區的next指標指向自身組成環形連結串列.

#if defined _LIBC && defined SHARED
/* In case this libc copy is in a non-default namespace, never use brk. Likewise if dlopened from statically linked program. */
Dl_info di;
struct link_map *l;
if (_dl_open_hook != NULL || (_dl_addr (ptmalloc_init, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE))
    __morecore = __failing_morecore;
#endif

ptmalloc需保證只有主分配區才能使用sbrk()分配連續虛擬記憶體空間,大多數情況下Glibc庫都是以動態連結庫的形式載入的,處於預設名稱空間,多個程序共用Glibc庫,Glibc庫程式碼段在記憶體中只有一份拷貝,資料段在每個使用者程序都有一份拷貝.但如果Glibc庫不在預設名字空間,或使用者程式是靜態編譯的並呼叫dlopen函式載入Glibc庫中的ptmalloc_init().這種情況下的ptmalloc不允許使用sbrk()分配記憶體,只需修改__morecore函式指標指向__failing_morecore就可以了,__morecore預設指向sbrk().

mutex_init(&list_lock);
tsd_key_create(&arena_key, NULL);
tsd_setspecific(arena_key, (Void_t *)&main_arena);
thread_atfork(ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);

初始化list_lock,list_lock主要用於同步分配區單向迴圈連結串列.然後建立執行緒私有例項arena_key,該私有例項儲存的是分配區(arena)的malloc_state例項指標.arena_key指向的可能是主分配區的指標,也可能是非主分配區的指標.這裡將呼叫ptmalloc_init()的執行緒的arena_key繫結到主分配區上.意味著本執行緒首選從主分配區分配記憶體. 然後呼叫thread_atfork()設定當前程序在fork子執行緒(linux下執行緒是輕量級程序,使用類似fork程序的機制建立)時處理mutex的回撥函式,在本程序fork子執行緒時,呼叫ptmalloc_lock_all()獲得所有分配區的鎖,禁止所有分配區分配記憶體,當子執行緒建立完畢,父程序呼叫ptmalloc_unlock_all()重新unlock每個分配區的鎖mutex,子執行緒呼叫ptmalloc_unlock_all2()重新初始化每個分配區的鎖mutex.

#ifndef NO_THREADS
    #ifndef NO_STARTER
    __malloc_hook = save_malloc_hook;
    __memalign_hook = save_memalign_hook;
    __free_hook = save_free_hook;
    #else
    #undef NO_STARTER
    #endif
#endif

pthread初始化完成後,將相應的hooks函式還原為原值.

#ifdef _LIBC
secure = __libc_enable_secure;
s = NULL;
if (__builtin_expect (_environ != NULL, 1)) {
    char **runp = _environ;
    char *envline;
    while (__builtin_expect ((envline = next_env_entry (&runp)) != NULL, 0)) {
        size_t len = strcspn (envline, "=");
        if (envline[len] != '=')
        /* This is a "MALLOC_" variable at the end of the string without a '=' character. Ignore it since otherwise we will access invalid memory below. */
            continue;
        switch (len) {
            case 6:
            if (memcmp (envline, "CHECK_", 6) == 0)
                s = &envline[7];
            break;
            case 8:
            if (! secure) {
                if (memcmp (envline, "TOP_PAD_", 8) == 0)
                    mALLOPt(M_TOP_PAD, atoi(&envline[9]));
                else if (memcmp (envline, "PERTURB_", 8) == 0)
                    mALLOPt(M_PERTURB, atoi(&envline[9]));
            }
            break;
            case 9:
            if (! secure) {
                if (memcmp (envline, "MMAP_MAX_", 9) == 0)
                    mALLOPt(M_MMAP_MAX, atoi(&envline[10]));
                #ifdef PER_THREAD
                else if (memcmp (envline, "ARENA_MAX", 9) == 0)
                    mALLOPt(M_ARENA_MAX, atoi(&envline[10]));
                #endif
            }
            break;
            #ifdef PER_THREAD
            case 10:
            if (! secure) {
                if (memcmp (envline, "ARENA_TEST", 10) == 0)
                    mALLOPt(M_ARENA_TEST, atoi(&envline[11]));
            }
            break;
            #endif
            case 15:
            if (! secure) {
                if (memcmp (envline, "TRIM_THRESHOLD_", 15) == 0)
                    mALLOPt(M_TRIM_THRESHOLD, atoi(&envline[16]));
                else if (memcmp (envline, "MMAP_THRESHOLD_", 15) == 0)
                    mALLOPt(M_MMAP_THRESHOLD, atoi(&envline[16]));
            }
            break;
            default:
            break;
        }
    }
}
#else
if (! secure) {
    if((s = getenv("MALLOC_TRIM_THRESHOLD_")))
        mALLOPt(M_TRIM_THRESHOLD, atoi(s));
    if((s = getenv("MALLOC_TOP_PAD_")))
        mALLOPt(M_TOP_PAD, atoi(s));
    if((s = getenv("MALLOC_PERTURB_")))
        mALLOPt(M_PERTURB, atoi(s));
    if((s = getenv("MALLOC_MMAP_THRESHOLD_")))
        mALLOPt(M_MMAP_THRESHOLD, atoi(s));
    if((s = getenv("MALLOC_MMAP_MAX_")))
        mALLOPt(M_MMAP_MAX, atoi(s));
}
s = getenv("MALLOC_CHECK_");
#endif
if(s && s[0]) {
    mALLOPt(M_CHECK_ACTION, (int)(s[0] - '0'));
    if (check_action != 0)
        __malloc_check_init();
}

從環境變數中讀取相應的配置引數值,這些引數包括MALLOC_TRIM_THRESHOLD_,MALLOC_TOP_PAD_,MALLOC_PERTURB_,MALLOC_MMAP_THRESHOLD_,MALLOC_CHECK_,MALLOC_MMAP_MAX_,MALLOC_ ARENA_MAX,MALLOC_ ARENA_TEST,如果某些項存在,呼叫mALLOPt()函式設定相應的選項.如果這段程式是在Glibc庫初始化中執行的,會做更多的安全檢查工作.

void (*hook) (void) = force_reg (__malloc_initialize_hook);
if (hook != NULL)
    (*hook)();
    __malloc_initialized = 1;

ptmalloc_init()函式結束處,檢視是否存在__malloc_initialize_hook函式,如果存在,執行該hook函式.最後將全域性變數__malloc_initialized設定為1,表示ptmalloc_init()已經初始化完成.

ptmalloc_lock_all(),ptmalloc_unlock_all(),ptmalloc_unlock_all2()

/* Magic value for the thread-specific arena pointer when malloc_atfork() is in use. */
#define ATFORK_ARENA_PTR ((Void_t*)-1)
/* The following hooks are used while the `atfork' handling mechanism is active. */
static Void_t* malloc_atfork(size_t sz, const Void_t *caller) {
    Void_t *vptr = NULL;
    Void_t *victim;
    tsd_getspecific(arena_key, vptr);
    if(vptr == ATFORK_ARENA_PTR) {
        /* We are the only thread that may allocate at all. */
        if(save_malloc_hook != malloc_check) {
            return _int_malloc(&main_arena, sz);
        } else {
            if(top_check()<0)
                return 0;
            victim = _int_malloc(&main_arena, sz+1);
            return mem2mem_check(victim, sz);
        }
    } else {
        /* Suspend the thread until the `atfork' handlers have completed. By that time, the hooks will have been reset as well, so that mALLOc() can be used again. */
        (void)mutex_lock(&list_lock);
        (void)mutex_unlock(&list_lock);
        return public_mALLOc(sz);
    }
}

父程序中某個執行緒使用fork的機制建立子執行緒時,如果程序中執行緒需要分配記憶體,將使用malloc_atfork()函式.malloc_atfork()函式首先檢視自己執行緒私有例項的分配區指標,如果該指標為ATFORK_ARENA_PTR,意味著本執行緒正在fork新執行緒,並鎖住了全域性鎖list_lock和每個分配區,當前只有本執行緒可以分配記憶體,如果在fork執行緒前的分配函式不是處於check模式,直接呼叫內部分配函式_int_malloc().否則在分配記憶體的同時做檢查.如果執行緒私有例項中的指標不是ATFORK_ARENA_PTR,意味著當前執行緒只是常規執行緒,有另外的執行緒在fork子執行緒,當前執行緒只能等待fork子執行緒的執行緒完成分配,於是等待獲得全域性鎖list_lock,如果獲得全域性鎖成功,表示fork子執行緒的執行緒已經完成fork操作,當前執行緒可以分配記憶體了,於是是釋放list_lock並呼叫public_mALLOc()分配記憶體.

static void free_atfork(Void_t* mem, const Void_t *caller) {
    Void_t *vptr = NULL;
    mstate ar_ptr;
    mchunkptr p;
    /* chunk corresponding to mem */
    if (mem == 0)
    /* free(0) has no effect */
        return; p = mem2chunk(mem);
    /* do not bother to replicate free_check here */
    #if HAVE_MMAP
    if (chunk_is_mmapped(p))
    /* release mmapped memory. */
    {
        munmap_chunk(p);
        return;
    }
    #endif
    #ifdef
    ATOMIC_FASTBINS ar_ptr = arena_for_chunk(p);
    tsd_getspecific(arena_key, vptr);
    _int_free(ar_ptr, p, vptr == ATFORK_ARENA_PTR);
    #else
    ar_ptr = arena_for_chunk(p);
    tsd_getspecific(arena_key, vptr);
    if(vptr != ATFORK_ARENA_PTR)
        (void)mutex_lock(&ar_ptr->mutex);
    _int_free(ar_ptr, p);
    if(vptr != ATFORK_ARENA_PTR)
        (void)mutex_unlock(&ar_ptr->mutex);
    #endif
}

父程序中某個執行緒使用fork的機制建立子執行緒時,如果程序中的執行緒需要釋放記憶體,將使用free_atfork()函式釋放記憶體.free_atfork()函式首先通過需free的記憶體塊指標獲得chunk的指標,如果該chunk是通過mmap分配的,呼叫munmap(),否則呼叫_int_free()函式釋放記憶體.在呼叫_int_free()函式前,先根據chunk指標獲得分配區指標,並讀取本執行緒私用例項的指標,如果開啟ATOMIC_FASTBINS優化,這個優化使用了lock-free的技術優化fastbins中單向連結串列操作.如果沒有,並且當前執行緒沒有正在fork新子執行緒,則對分配區加鎖,然後呼叫_int_free()函式,然後對分配區解鎖.而對於正在fork子執行緒的執行緒來說,是不需要對分配區加鎖的,因為該執行緒已經對所有的分配區加鎖了.

/* Counter for number of times the list is locked by the same thread. */
static unsigned int atfork_recursive_cntr;
/* The following two functions are registered via thread_atfork() to make sure that the mutexes remain in a consistent state in the fork()ed version of a thread. Also adapt the malloc and free hooks temporarily, because the `atfork' handler mechanism may use malloc/free internally (e.g. in LinuxThreads). */
static void ptmalloc_lock_all (void) {
    mstate ar_ptr;
    if(__malloc_initialized < 1)
        return;
    if (mutex_trylock(&list_lock)) {
        Void_t *my_arena;
        tsd_getspecific(arena_key, my_arena);
        if (my_arena == ATFORK_ARENA_PTR)
        /* This is the same thread which already locks the global list. Just bump the counter. */
            goto out;
        /* This thread has to wait its turn. */
        (void)mutex_lock(&list_lock);
    }
    for(ar_ptr = &main_arena;;) {
        (void)mutex_lock(&ar_ptr->mutex);
        ar_ptr = ar_ptr->next;
        if(ar_ptr == &main_arena)
            break;
    }
    save_malloc_hook = __malloc_hook;
    save_free_hook = __free_hook;
    __malloc_hook = malloc_atfork;
    __free_hook = free_atfork;
    /* Only the current thread may perform malloc/free calls now. */
    tsd_getspecific(arena_key, save_arena);
    tsd_setspecific(arena_key, ATFORK_ARENA_PTR);
    out: ++atfork_recursive_cntr;
}

父程序中某個執行緒使用fork機制建立子執行緒時,首先呼叫ptmalloc_lock_all()函式對全域性鎖list_lock和所有的分配區加鎖,從而保證分配區狀態的一致性.ptmalloc_lock_all()函式首先檢查ptmalloc是否已初始化,如果沒有初始化,退出,如果已經初始化,嘗試對全域性鎖list_lock加鎖,直到獲得全域性鎖list_lock,接著對所有的分配區加鎖,接著儲存原有分配釋放函式,將malloc_atfork()free_atfork()函式作為fork子執行緒期間所使用的記憶體分配釋放函式,然後儲存當前執行緒私有例項的原有分配區指標,將ATFORK_ARENA_PTR存放到當前執行緒的私有例項中,用於標識當前現在正在fork子執行緒. 為了保證父執行緒fork多個子執行緒工作正常,當一個子執行緒已經建立,當前執行緒繼續建立其它子執行緒,發現當前執行緒已經對list_lock和所有分配區加鎖,對全域性變數atfork_recursive_cntr1,表示遞迴fork子執行緒層數,保證父執行緒fork子執行緒過程ptmalloc_unlock_all()加鎖次數與ptmalloc_lock_all()解鎖次數保持一致,同時也保證所有子執行緒ptmalloc_unlock_all()加鎖次數與父執行緒ptmalloc_lock_all()解鎖次數保持一致,防止沒有釋放鎖.

static void ptmalloc_unlock_all (void) {
    mstate ar_ptr;
    if(__malloc_initialized < 1)
        return;
    if (--atfork_recursive_cntr != 0)
        return;
    tsd_setspecific(arena_key, save_arena);
    __malloc_hook = save_malloc_hook;
    __free_hook = save_free_hook;
    for(ar_ptr = &main_arena;;) {
        (void)mutex_unlock(&ar_ptr->mutex);
        ar_ptr = ar_ptr->next;
        if(ar_ptr == &main_arena)
            break;
    }
    (void)mutex_unlock(&list_lock);
}

程序的某個執行緒完成fork子執行緒後,父執行緒和子執行緒都呼叫ptmall_unlock_all()函式釋放list_lock和所有分配區的鎖.ptmall_unlock_all()函式首先檢查ptmalloc是否初始化,只有初始化後才能呼叫該函式,接著將全域性變數atfork_recursive_cntr1,如果atfork_recursive_cntr0,,才繼續執行,這保證了遞迴fork子執行緒只會解鎖一次.接著將當前執行緒私有例項還原,__malloc_hook__free_hook還原為原來的hook函式.然後遍歷所有分配區,依次解鎖每個分配區,最後解鎖list_lock.

#ifdef __linux__
/* In NPTL, unlocking a mutex in the child process after a fork() is currently unsafe, whereas re-initializing it is safe and does not leak resources. Therefore, a special atfork handler is installed for the child. */
static void ptmalloc_unlock_all2 (void) {
    mstate ar_ptr;
    if(__malloc_initialized < 1)
        return;
    #if defined _LIBC || defined MALLOC_HOOKS
    tsd_setspecific(arena_key, save_arena);
    __malloc_hook = save_malloc_hook;
    __free_hook = save_free_hook;
    #endif
    #ifdef PER_THREAD
    free_list = NULL;
    #endif
    for(ar_ptr = &main_arena;;) {
        mutex_init(&ar_ptr->mutex);
        #ifdef PER_THREAD
        if (ar_ptr != save_arena) {
            ar_ptr->next_free = free_list;
            free_list = ar_ptr;
        }
        #endif
        ar_ptr = ar_ptr->next;
        if(ar_ptr == &main_arena)
            break;
        }
        mutex_init(&list_lock);
        atfork_recursive_cntr = 0;
    }
#else
    #define ptmalloc_unlock_all2 ptmalloc_unlock_all
#endif

函式ptmalloc_unlock_all2()fork出的子執行緒呼叫,在Linux系統中,子執行緒(程序)unlock從父執行緒(程序)中繼承的mutex不安全,會導致資源洩漏,但重新初始化mutex是安全的,所有增加了這個特殊版本用於Linux下的atfork handler.ptmalloc_unlock_all2()函式的處理流程跟ptmalloc_unlock_all()函式差不多,使用mutex_init()代替了mutex_unlock(),如果開啟了PER_THREAD的優化,將從父執行緒中繼承來的分配區加入到free_list中,對於子執行緒來說,無論全域性變數atfork_recursive_cntr的值是多少,都將該值設定為0,因為ptmalloc_unlock_all2()函式只會被子執行緒呼叫一次.