glibc内存管理分析
字数 2286 2025-08-22 12:23:00
GLibc内存管理机制深入解析
1. 分配区概念
1.1 主分配区(main arena)与非主分配区(no main arena)
-
主分配区:
- 系统中仅存在一个主分配区
- 内存分配时需加锁确保线程安全
- 可通过
sbrk和mmap两种方式向操作系统申请虚拟内存
-
非主分配区:
- 只能通过
mmap映射得到内存区域 - 每次申请内存默认大小为"HEAP MAX SIZE"
- 系统仅在必要时使用
mmap申请内存
- 只能通过
2. Chunk结构
2.1 使用中的chunk结构
标志位:
- P (PREV_INUSE): 0表示前一个chunk空闲,1表示使用中
- M (IS_MMAPPED): 1表示通过mmap分配,0表示不是
- A (NON_MAIN_ARENA): 1表示主分配区,0表示非主分配区
内存布局示例:
char *name = malloc(8); // name指向Mem指针
// name-2就是Chunk指针指向的地方
2.2 空闲中的chunk结构
- 标志位与使用中chunk相同(无M标志位)
fd指向下一个空闲chunkbk指向前一个空闲chunkfd_nextsize和bk_nextsize仅出现在large bin中
2.3 chunk的空间复用
- 为避免空间浪费,ptmalloc采用空间复用技术
- 32位系统中最小chunk为16字节:
- prev_size: 4字节
- size: 4字节
- fd: 4字节
- bk: 4字节
- 64位系统同理(最小chunk为32字节)
3. 空闲chunk容器
3.1 容器分类
#define NBINS 128 // 总bin数量
#define NSMALLBINS 64 // small bin数量
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define MIN_LARGE_SIZE (NSMALLBINS * SMALLBIN_WIDTH)
3.2 索引计算
// 判断是否在small bin范围内
#define in_smallbin_range(sz) \
((unsigned long)(sz) < (unsigned long)MIN_LARGE_SIZE)
// small bin索引计算
#define smallbin_index(sz) \
(SMALLBIN_WIDTH == 16 ? (((unsigned)(sz)) >> 4) : (((unsigned)(sz)) >> 3))
// large bin索引计算(32位)
#define largebin_index_32(sz) \
(((((unsigned long)(sz)) >> 6) <= 38) ? 56 + (((unsigned long)(sz)) >> 6) : \
((((unsigned long)(sz)) >> 9) <= 20) ? 91 + (((unsigned long)(sz)) >> 9) : \
/* 其他情况省略... */)
// large bin索引计算(64位)
#define largebin_index_64(sz) \
(((((unsigned long)(sz)) >> 6) <= 48) ? 48 + (((unsigned long)(sz)) >> 6) : \
((((unsigned long)(sz)) >> 9) <= 20) ? 91 + (((unsigned long)(sz)) >> 9) : \
/* 其他情况省略... */)
// 通用bin索引计算
#define bin_index(sz) \
((in_smallbin_range(sz)) ? smallbin_index(sz) : largebin_index(sz))
3.3 bins的作用
- 作为内存管理的缓存机制,减少系统调用
- 存储不同大小的空闲内存块(chunks)
- 共维护128个bin,使用数组存储
- 大小相近的chunk通过双向链表链接
4. 特殊容器
4.1 fastbin
- 用于快速分配小内存块(32位中<0x80)
- 不改变chunk的P标志位,避免合并
- 分配时优先查找fastbin
- 特定时机会合并fastbin中的chunk到unsorted bin
4.2 unsorted bin
- 释放的大于max_fast的chunk首先放入此处
- fastbin合并后的chunk也会放入此处
- malloc时若fastbin不满足,会先查找此处
- 可看作bins的缓冲区,加速分配
4.3 small bin
- 管理较小的空闲chunk(32位:16B-504B,64位:32B-1008B)
- 每个bin维护双向环形链表
- 链表中的chunk大小相同
- 62个small bin(索引1-62)
4.4 large bin
- 管理较大的空闲chunk
- 每个bin中的chunk大小不等
- 使用
fd_nextsize和bk_nextsize指针管理不同大小的chunk
5. 特殊chunk
5.1 top chunk
- 位于arena最顶部(最高内存地址处)
- 不属于任何bin
- 当所有free chunk都无法满足请求时使用
- 分配规则:
- 若大小足够,分割为用户chunk和新top chunk
- 否则扩展heap(主分配区用sbrk,非主分配区用mmap)
5.2 mmaped chunk
- 当top chunk也不满足时,直接使用mmap映射
- free时直接解除映射
5.3 last remainder
- 当从bins中分割chunk时,剩余部分成为last remainder
- 记录在arena的
malloc_state.last_remainder中
6. 内存分配与回收
6.1 分配步骤
- 获取分配区锁(线程安全)
- 检查请求大小:
- 若<max_fast,查找fastbin
- 若在small bin范围,查找smallbin
- 否则处理largebin
- 若fastbin/smallbin无合适chunk:
- 合并fastbin到unsortedbin
- 检查unsortedbin
- 若仍无合适chunk:
- 查找largebin
- 尝试使用top chunk
- 若top chunk不足:
- 主分配区:sbrk扩展
- 非主分配区:mmap新heap
- 若请求大小>mmap阈值,直接mmap映射
6.2 回收步骤
- 获取分配区锁
- 检查指针有效性
- 判断是否为mmap分配:
- 是:直接munmap
- 否:
- 若<max_fast且不在堆顶:放入fastbin
- 否则:
- 检查前一个chunk是否空闲,若空闲则合并
- 检查是否与top chunk相邻,若相邻则合并到top
- 否则放入unsortedbin
- 若合并后chunk超过FASTBIN_CONSOLIDATION_THRESHOLD:
- 合并fastbin到unsortedbin
- 检查top chunk大小:
- 若超过mmap收缩阈值:
- 主分配区:部分返回系统(初始堆不返回)
- 非主分配区:收缩sub-heap
- 若超过mmap收缩阈值:
7. 版本差异
- 2.23及之前版本:无tcache机制
- 2.26+版本:引入tcache(优先级高于fastbin,安全性低)
- 2.29+版本:加固tcache安全性
8. 关键参数
- max_fast:32位系统通常为0x80
- mmap阈值:32位系统通常为128KB
- small bin范围:
- 32位:16B-504B(8B递增)
- 64位:32B-1008B(16B递增)
9. 系统调用
- sbrk:主分配区扩展heap
- mmap:
- 非主分配区分配sub-heap
- 大内存直接分配
- 主分配区在sbrk失败时使用
10. 安全考虑
- 分配区锁确保线程安全
- 空间复用减少内存浪费
- 分箱机制提高分配效率
- tcache在速度与安全间权衡
本文档涵盖了glibc 2.23及之前版本的内存管理核心机制,重点包括分配区、chunk结构、各类bin的管理策略,以及内存分配与回收的具体流程。对于理解ptmalloc的工作原理和进行内存相关漏洞分析具有重要意义。