selph
selph
发布于 2024-01-02 / 99 阅读
0
0

[libc 2.35 源码学习] 申请内存从 tcachebin

tcache_init:初始化 tcache_perthread_struct

static void
tcache_init(void)
{
    mstate ar_ptr;  // arena
    void *victim = 0;
    const size_t bytes = sizeof(tcache_perthread_struct);
    // 是否关闭 tcache
    if (tcache_shutting_down)
        return;
    // 获取arena,如果没有就创建一个
    arena_get(ar_ptr, bytes);
    // 申请内存,就是tcache_perthread_struct大小
    victim = _int_malloc(ar_ptr, bytes);
    if (!victim && ar_ptr != NULL)
    {
        // 如果申请失败,就再次申请内存
        ar_ptr = arena_get_retry(ar_ptr, bytes);
        victim = _int_malloc(ar_ptr, bytes);
    }

    if (ar_ptr != NULL)
        __libc_lock_unlock(ar_ptr->mutex);

    /* In a low memory situation, we may not be able to allocate memory
       - in which case, we just keep trying later.  However, we
       typically do this very early, so either there is sufficient
       memory, or there isn't enough memory to do non-trivial
       allocations anyway.  */
    if (victim)
    {
        // 初始化tcache_perthread_struct结构体为0
        tcache = (tcache_perthread_struct *)victim;
        memset(tcache, 0, sizeof(tcache_perthread_struct));
    }
}

主要是在arena中申请第一块内存,保存到tcache变量中,是tcache_perthread_struct​结构体,每个线程都有自己的该结构体

tcache_perthread_struct 结构体

/* We overlay this structure on the user-data portion of a chunk when
   the chunk is stored in the per-thread cache.  */
typedef struct tcache_entry
{
    struct tcache_entry *next;
    /* This field exists to detect double frees.  */
    uintptr_t key;
} tcache_entry;

/* There is one of these for each thread, which contains the
   per-thread cache (hence "tcache_perthread_struct").  Keeping
   overall size low is mildly important.  Note that COUNTS and ENTRIES
   are redundant (we could have just counted the linked list each
   time), this is for performance reasons.  */
typedef struct tcache_perthread_struct
{
    uint16_t counts[TCACHE_MAX_BINS];
    tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

static __thread bool tcache_shutting_down = false;
static __thread tcache_perthread_struct *tcache = NULL;

/* Process-wide key to try and catch a double-free in the same thread.  */
static uintptr_t tcache_key;

一些宏定义

#if USE_TCACHE
/* We want 64 entries.  This is an arbitrary limit, which tunables can reduce.  */
#define TCACHE_MAX_BINS 64
#define MAX_TCACHE_SIZE tidx2usize(TCACHE_MAX_BINS - 1)

/* Only used to pre-fill the tunables.  */
#define tidx2usize(idx) (((size_t)idx) * MALLOC_ALIGNMENT + MINSIZE - SIZE_SZ)

/* When "x" is from chunksize().  */
#define csize2tidx(x) (((x)-MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT)
/* When "x" is a user-provided size.  */
#define usize2tidx(x) csize2tidx(request2size(x))

/* With rounding and alignment, the bins are...
   idx 0   bytes 0..24 (64-bit) or 0..12 (32-bit)
   idx 1   bytes 25..40 or 13..20
   idx 2   bytes 41..56 or 21..28
   etc.  */

/* This is another arbitrary limit, which tunables can change.  Each
   tcache bin will hold at most this number of chunks.  */
#define TCACHE_FILL_COUNT 7

/* Maximum chunks in tcache bins for tunables.  This value must fit the range
   of tcache->counts[] entries, else they may overflow.  */
   // 65535
#define MAX_TCACHE_COUNT UINT16_MAX 
#endif

几个主要内容:

  • tidx2usize​计算bin中索引对应的大小
  • csize2tidx​计算指定大小在bin中的索引
  • TCACHE_MAX_BINS​是最大的bin数量,最多可以有64个项默认情况下,tcachebin的最大大小是:0x410
  • TCACHE_FILL_COUNT​指定tcache填满数量

全局变量 mp_

/* There is only one instance of the malloc parameters.  */

static struct malloc_par mp_ =
    {
        .top_pad = DEFAULT_TOP_PAD,
        .n_mmaps_max = DEFAULT_MMAP_MAX,
        .mmap_threshold = DEFAULT_MMAP_THRESHOLD,
        .trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof(long) == 4 ? 2 : 8))
        .arena_test = NARENAS_FROM_NCORES(1)
#if USE_TCACHE
            ,
        .tcache_count = TCACHE_FILL_COUNT,
        .tcache_bins = TCACHE_MAX_BINS,
        .tcache_max_bytes = tidx2usize(TCACHE_MAX_BINS - 1),
        .tcache_unsorted_limit = 0 /* No limit.  */
#endif
};

tcache的申请

malloc.c​的__libc_malloc​中:从tcache申请内存是链表断链的过程

#if USE_TCACHE  // 如果使用tcache
    /* int_free also calls request2size, be careful to not pad twice.  */
    size_t tbytes;
    // 获取需要申请的大小,如果超过最大大小(64位0x7FFFFFFFFFFFFFFF,32位0x7FFFFFFF)返回 false
    // 如果是标记的内存,做一个处理(待分析)
    // 调用request2size()返回申请的大小到tbytes,如果小于最小对齐大小,则返回最小大小,否则返回申请的大小
    if (!checked_request2size(bytes, &tbytes))  
    {
        __set_errno(ENOMEM);
        return NULL;
    }
    // 计算 tcache 索引
    size_t tc_idx = csize2tidx(tbytes);
    // 初始化tcache
    MAYBE_INIT_TCACHE();

    DIAG_PUSH_NEEDS_COMMENT;
    // tcache 索引
    if (tc_idx < mp_.tcache_bins && tcache && tcache->counts[tc_idx] > 0)
    {
        // 从tcachebin中对应的索引处断链取出一个entry
        victim = tcache_get(tc_idx);
        return tag_new_usable(victim);
    }
    DIAG_POP_NEEDS_COMMENT;
#endif
  1. 计算申请的大小对应tcachebin索引号

  2. 如果tcache结构体没初始化,就初始化(会在第一次申请内存的时候初始化)

  3. 如果tcache索引号,小于最大bin数量,且tcache初始化完毕了,且该索引号对应的剩余大小大于0,就可以进行申请

    1. 从tcachebin中对应的索引处断链取出一个entry
    2. 如果启用了tag相关的选项,会进行一些处理
    3. 返回取出来的地址

tcache_get

/* Caller must ensure that we know tc_idx is valid and there's
   available chunks to remove.  */
static __always_inline void *
tcache_get(size_t tc_idx)
{
    tcache_entry *e = tcache->entries[tc_idx];
    // 内存对齐检查
    if (__glibc_unlikely(!aligned_OK(e)))
        malloc_printerr("malloc(): unaligned tcache chunk detected");
    // 取出链表下一个到 entries
    tcache->entries[tc_idx] = REVEAL_PTR(e->next);
    // counts 减少
    --(tcache->counts[tc_idx]);
    // key清零
    e->key = 0;
    return (void *)e;
}

这里是断链取出entry的过程,这里有个内存对齐检查:

#define aligned_OK(m) (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)

// sysdeps/generic/malloc-size.h
/* The corresponding bit mask value.  */
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)

tag_new_usable

static __always_inline void *
tag_new_usable(void *ptr)
{
    // 如果mtag_enabled=1,ptr有值
    if (__glibc_unlikely(mtag_enabled) && ptr)
    {
        // 获取 chunk header ptr
        mchunkptr cp = mem2chunk(ptr);
        // tag 相关处理
        ptr = __libc_mtag_tag_region(__libc_mtag_new_tag(ptr), memsize(cp));
    }
    // 返回 ptr
    return ptr;
}


评论