glibc2.35-malloc源码分析
字数 1187 2025-12-03 12:12:36

glibc 2.35 malloc源码分析教学文档

一、__libc_malloc函数分析

1.1 函数概述

__libc_malloc是malloc函数实际调用的入口点,主要负责从tcache bin分配内存。如果tcache bin中没有满足条件的chunk,则调用_int_malloc函数进行分配。

1.2 关键代码逻辑

void *__libc_malloc(size_t bytes)
{
    mstate ar_ptr;
    void *victim;
    
    // 检查malloc是否初始化
    if (!__malloc_initialized)
        ptmalloc_init(); // 没有初始化先进行初始化

1.3 tcache分配流程

#if USE_TCACHE
    size_t tbytes;
    // 检查申请大小,将其转为实际的size大小(申请size加上0x10的chunk头)
    if (!checked_request2size(bytes, &tbytes)) {
        __set_errno(ENOMEM);
        return NULL;
    }
    
    size_t tc_idx = csize2tidx(tbytes); // 将大小转化为对应tcachebin的下标
    MAYBE_INIT_TCACHE(); // 检查并初始化tcache bin
    
    // 如果tcache bin中有可用chunk,直接取出返回
    if (tc_idx < mp_.tcache_bins && tcache && tcache->counts[tc_idx] > 0) {
        victim = tcache_get(tc_idx);
        return tag_new_usable(victim);
    }
#endif

1.4 单线程与多线程处理

// 单线程情况:直接使用main_arena
if (SINGLE_THREAD_P) {
    victim = tag_new_usable(_int_malloc(&main_arena, bytes));
    return victim;
}

// 多线程情况:获取私有arena并上锁
arena_get(ar_ptr, bytes);
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);
}

二、_int_malloc函数分析

2.1 核心分配逻辑

_int_malloc是malloc最核心的分配逻辑,按以下顺序查找可用chunk:

  1. fastbin查找
  2. small bin查找
  3. unsorted bin查找
  4. large bin查找
  5. 所有bin中寻找最小满足要求的chunk进行切割
  6. 从top chunk进行切割

2.2 bin管理机制

  • arena中的bin(除fastbin外)都是双向链表节点
  • 每个bin都被视为chunk来管理,fd和bk对应chunk中的fd和bk
  • fastbin只使用fd,不使用bk
  • bin中的free chunk指向的不是bins数组真实首地址,而是fd减去0x10的虚拟chunk头地址

2.3 unsorted bin处理关键代码

// 从unsorted bin中移除chunk
if (__glibc_unlikely(bck->fd != victim))
    malloc_printerr("malloc(): corrupted unsorted chunks 3");
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);

// 精准匹配时直接取出
if (size == nb) {
    set_inuse_bit_at_offset(victim, size);
    if (av != &main_arena)
        set_non_main_arena(victim);

2.4 large bin排序机制

// 保持large bins有序状态(从大到小排序)
if (fwd != bck) {
    size |= PREV_INUSE;
    
    // 如果比bin中最小的还小,直接插入末尾
    if ((unsigned long)(size) < (unsigned long)chunksize_nomask(bck->bk)) {
        fwd = bck;
        bck = bck->bk;
        // 维护nextsize指针关系
        victim->fd_nextsize = fwd->fd;
        victim->bk_nextsize = fwd->fd->bk_nextsize;
        fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
    }
}

三、tcache_put函数分析

3.1 tcache_entry结构

typedef struct tcache_entry {
    struct tcache_entry *next;
    uintptr_t key;  // 用于检测double free
} tcache_entry;

3.2 指针加密机制

// 加密:将存放fd的地址右移12位后与fd指针异或
#define PROTECT_PTR(pos, ptr) \
    ((__typeof(ptr))((((size_t)pos) >> 12) ^ ((size_t)ptr)))

// 解密:再次进行相同的加密操作
#define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)

3.3 tcache放入操作

static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx)
{
    tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
    
    // 设置key标记chunk为free状态,用于double free检测
    e->key = tcache_key;
    
    // 加密原tcache bin头指针并链接
    e->next = PROTECT_PTR(&e->next, tcache->entries[tc_idx]);
    tcache->entries[tc_idx] = e;
    ++(tcache->counts[tc_idx]);
}

四、unlink_chunk函数分析

4.1 基础检查

static void unlink_chunk(mstate av, mchunkptr p)
{
    // 检查size合法性
    if (chunksize(p) != prev_size(next_chunk(p)))
        malloc_printerr("corrupted size vs. prev_size");
    
    mchunkptr fd = p->fd;
    mchunkptr bk = p->bk;
    
    // 双向链表完整性检查
    if (__builtin_expect(fd->bk != p || bk->fd != p, 0))
        malloc_printerr("corrupted double-linked list");

4.2 标准unlink操作

// 执行unlink操作
fd->bk = bk;
bk->fd = fd;

4.3 large bin特殊处理

if (!in_smallbin_range(chunksize_nomask(p)) && p->fd_nextsize != NULL) {
    // nextsize指针完整性检查
    if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p)
        malloc_printerr("corrupted double-linked list (not small)");
    
    if (fd->fd_nextsize == NULL) {
        if (p->fd_nextsize == p) {
            // 只有一种大小的chunk
            fd->fd_nextsize = fd->bk_nextsize = fd;
        } else {
            // 将p->fd链接到nextsize指针中
            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 {
        // 直接从nextsize指针中移除p
        p->fd_nextsize->bk_nextsize = p->bk_nextsize;
        p->bk_nextsize->fd_nextsize = p->fd_nextsize;
    }
}

五、malloc_consolidate函数分析

5.1 函数作用

合并fastbin中的每个chunk,将其放入unsorted bin或与top chunk合并。

5.2 核心逻辑

static void malloc_consolidate(mstate av)
{
    atomic_store_relaxed(&av->have_fastchunks, false);
    unsorted_bin = unsorted_chunks(av);
    
    // 遍历所有fastbin
    do {
        p = atomic_exchange_acq(fb, NULL); // 获取并清空fastbin
        if (p != 0) {
            do {
                // 对齐和大小检查
                if (__glibc_unlikely(misaligned_chunk(p)))
                    malloc_printerr("malloc_consolidate(): unaligned fastbin chunk detected");
                
                // 向前合并检查
                if (!prev_inuse(p)) {
                    prevsize = prev_size(p);
                    size += prevsize;
                    p = chunk_at_offset(p, -((long)prevsize));
                    unlink_chunk(av, p);
                }

5.3 合并策略

if (nextchunk != av->top) {
    // 不是top chunk:检查后向合并或放入unsorted bin
    nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
    if (!nextinuse) {
        size += nextsize;
        unlink_chunk(av, nextchunk);
    } else {
        clear_inuse_bit_at_offset(nextchunk, 0);
    }
    
    // 链接到unsorted bin
    first_unsorted = unsorted_bin->fd;
    unsorted_bin->fd = p;
    first_unsorted->bk = p;
} else {
    // 是top chunk:直接合并到top chunk
    size += nextsize;
    set_head(p, size | PREV_INUSE);
    av->top = p;
}

六、关键知识点总结

6.1 内存分配优先级

  1. tcache bin(最快)
  2. fast bin
  3. small bin
  4. unsorted bin
  5. large bin
  6. top chunk(最慢)

6.2 安全机制

  1. tcache double free检测:通过key字段标记free状态
  2. 指针加密:PROTECT_PTR/REVEAL_PTR机制防止信息泄露
  3. 完整性检查:双向链表、size字段等多处验证
  4. 边界检查:chunk对齐、大小合法性等检查

6.3 性能优化

  1. tcache缓存:减少锁竞争,提高分配速度
  2. unsorted bin:延迟排序,提高复用率
  3. large bin跳表:nextsize指针加速大内存查找
  4. 合并策略:减少内存碎片

本教学文档详细分析了glibc 2.35中malloc的核心实现机制,涵盖了从用户调用到内存分配完成的完整流程,重点解释了关键数据结构和算法实现。

glibc 2.35 malloc源码分析教学文档 一、__ libc_ malloc函数分析 1.1 函数概述 __libc_malloc 是malloc函数实际调用的入口点,主要负责从tcache bin分配内存。如果tcache bin中没有满足条件的chunk,则调用 _int_malloc 函数进行分配。 1.2 关键代码逻辑 1.3 tcache分配流程 1.4 单线程与多线程处理 二、_ int_ malloc函数分析 2.1 核心分配逻辑 _int_malloc 是malloc最核心的分配逻辑,按以下顺序查找可用chunk: fastbin查找 small bin查找 unsorted bin查找 large bin查找 所有bin中寻找最小满足要求的chunk进行切割 从top chunk进行切割 2.2 bin管理机制 arena中的bin(除fastbin外)都是双向链表节点 每个bin都被视为chunk来管理,fd和bk对应chunk中的fd和bk fastbin只使用fd,不使用bk bin中的free chunk指向的不是bins数组真实首地址,而是fd减去0x10的虚拟chunk头地址 2.3 unsorted bin处理关键代码 2.4 large bin排序机制 三、tcache_ put函数分析 3.1 tcache_ entry结构 3.2 指针加密机制 3.3 tcache放入操作 四、unlink_ chunk函数分析 4.1 基础检查 4.2 标准unlink操作 4.3 large bin特殊处理 五、malloc_ consolidate函数分析 5.1 函数作用 合并fastbin中的每个chunk,将其放入unsorted bin或与top chunk合并。 5.2 核心逻辑 5.3 合并策略 六、关键知识点总结 6.1 内存分配优先级 tcache bin(最快) fast bin small bin unsorted bin large bin top chunk(最慢) 6.2 安全机制 tcache double free检测 :通过key字段标记free状态 指针加密 :PROTECT_ PTR/REVEAL_ PTR机制防止信息泄露 完整性检查 :双向链表、size字段等多处验证 边界检查 :chunk对齐、大小合法性等检查 6.3 性能优化 tcache缓存 :减少锁竞争,提高分配速度 unsorted bin :延迟排序,提高复用率 large bin跳表 :nextsize指针加速大内存查找 合并策略 :减少内存碎片 本教学文档详细分析了glibc 2.35中malloc的核心实现机制,涵盖了从用户调用到内存分配完成的完整流程,重点解释了关键数据结构和算法实现。