malloc tcache
2026/9/12 19:23:44 网站建设 项目流程

tcache(Thread Local Caching,线程本地缓存)是 Glibc 2.26 引入的机制,目的是为每个线程提供一个无锁的小内存缓存,从而大幅减少多线程环境下的锁竞争,提升小内存分配和释放的速度。

可以把它理解为每个线程私有的“随身小口袋”——释放的小内存块先塞进自己口袋,下次申请时直接从口袋里掏,不需要去公共仓库(Arena)排队。


1. 为什么需要 tcache?

在 tcache 出现之前,小内存的分配路径是:

malloc → Arena 锁 → Fastbin/Small Bin → 返回 free → Arena 锁 → Fastbin/Small Bin → 入链

每次操作都要获取 Arena 锁,多线程下锁竞争严重,性能瓶颈明显。

tcache 的设计目标是:

  • 无锁:每个线程独立访问自己的 tcache,不需要任何锁。

  • 快速:分配和释放都是 O(1) 的链表头操作。

  • 局部性:利用线程的时间局部性,最近释放的块最可能被同一线程再次使用。


2. 数据结构

每个线程拥有一个独立的tcache_perthread_struct,通过线程局部存储(TLS)访问:

typedef struct tcache_perthread_struct { uint16_t counts[TCACHE_MAX_BINS]; // 每个 bin 当前的 chunk 数量 tcache_entry *entries[TCACHE_MAX_BINS]; // 每个 bin 的单向链表头 } tcache_perthread_struct; typedef struct tcache_entry { struct tcache_entry *next; // 指向下一个空闲块 struct tcache_perthread_struct *key; // 用于双重释放检测 } tcache_entry;

关键字段

字段作用
counts[i]记录第i个 bin 中当前有多少个空闲块(上限为 7)
entries[i]指向第i个 bin 的单向链表头
next链表指针,指向下一个空闲块
key指向所属的 tcache 结构体,用于检测双重释放

TCACHE_MAX_BINS通常为 64,覆盖 32~1040 字节(步长 16 字节)的小内存块。


3. 核心工作流程

A. 释放路径(free→ tcache)

当程序调用free(p)释放一块小内存时:

  1. 计算 bin 索引:根据 chunk 大小,计算对应的 tcache bin 索引。

  2. 检查 tcache 是否已满

    • 如果counts[idx] < 7(未满),直接将 chunk 插入entries[idx]链表头部。

    • 如果counts[idx] == 7(已满),则回退到传统路径:进入 Fastbin 或 Unsorted Bin。

  3. 设置key字段:将 chunk 的key指向当前线程的 tcache 结构体,用于后续检测双重释放。

  4. 更新计数counts[idx]++

关键点:整个过程不需要获取 Arena 锁,速度极快。

B. 分配路径(malloc→ tcache)

当程序调用malloc(size)申请一块小内存时:

  1. 计算 bin 索引:根据请求大小,计算对应的 tcache bin 索引。

  2. 检查 tcache 是否为空

    • 如果entries[idx] != NULL,直接从链表头部取出一个 chunk,返回给用户。

    • 如果entries[idx] == NULL(空),则回退到传统路径:去 Fastbin、Small Bin 或 Unsorted Bin 查找。

  3. 更新计数counts[idx]--

关键点:同样不需要获取 Arena 锁,O(1) 完成。


4. tcache 的容量限制

每个 bin 最多缓存7 个chunk(由TCACHE_FILL_COUNT定义,通常为 7)。

为什么是 7?

  • 平衡内存开销和命中率:7 个块足以应对大多数“释放-重分配”的短周期模式。

  • 避免过度缓存:如果缓存太多,会导致内存无法归还给 Arena,造成浪费。

  • 经验值:Glibc 开发者通过实际测试发现 7 是一个较好的平衡点。

当 tcache 满了之后,后续释放的块会进入 Fastbin 或 Unsorted Bin,参与传统的合并和整理流程。


5. tcache 与 Fastbin 的关系

tcache 出现后,Fastbin 的角色发生了变化:

特性tcacheFastbin
位置线程私有(TLS)Arena 内(共享)
无锁需要 Arena 锁
大小范围32~1040 字节32~160 字节(默认)
数量限制每个 bin 最多 7 个无硬性限制
合并❌ 不合并❌ 不合并
优先级最高(先查 tcache)次之(tcache 空/满时使用)

实际流程

  • free时:优先放 tcache,tcache 满了才放 Fastbin。

  • malloc时:优先查 tcache,tcache 空了才查 Fastbin。

这意味着 Fastbin 现在更多扮演“tcache 的后备仓库”角色。


6. 安全性:双重释放检测

tcache 引入了一个简单的双重释放检测机制:

  • 当 chunk 被放入 tcache 时,它的key字段被设置为指向当前线程的 tcache 结构体。

  • 当再次释放同一个 chunk 时,如果key字段仍然指向有效的 tcache 结构体,说明这是双重释放,触发malloc_printerr报错。

但注意:这个检测不是万无一失的。如果攻击者能修改key字段,或者 chunk 被重新分配后key被覆盖,仍然可能绕过检测。


7. 完整流程图

malloc(size): │ ▼ 计算 tcache bin 索引 │ ▼ tcache.entries[idx] 非空? │ ├── 是 → 取出链表头,返回 (无锁,O(1)) │ └── 否 → 回退到传统路径 (Fastbin → Small Bin → Unsorted Bin → Top Chunk) free(p): │ ▼ 计算 tcache bin 索引 │ ▼ tcache.counts[idx] < 7? │ ├── 是 → 插入 tcache 链表头,设置 key (无锁,O(1)) │ └── 否 → 回退到传统路径 (Fastbin → Unsorted Bin)

8. 总结

要点说明
核心目标为每个线程提供无锁的小内存缓存,减少锁竞争
数据结构counts[64]+entries[64],每个 bin 最多 7 个块
释放路径优先放 tcache,满了才放 Fastbin
分配路径优先查 tcache,空了才查 Fastbin
大小范围32~1040 字节(64 位系统)
安全性通过key字段检测双重释放,但并非绝对可靠
性能影响小内存分配/释放几乎无锁,速度极快

一句话理解

tcache 是每个线程的“私有小口袋”,让小内存的分配和释放绕过了 Arena 锁,是 Glibc 在性能优化上的一次重要飞跃。它的出现让 Fastbin 从“第一道防线”退居为“第二道防线”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询