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:
- 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处理关键代码
// 从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 内存分配优先级
- 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的核心实现机制,涵盖了从用户调用到内存分配完成的完整流程,重点解释了关键数据结构和算法实现。