05:内存管理-用户篇:从 brk 到 malloc —— 用户态内存分配器深度解析

“用户程序调用 malloc 时,内核和 libc 如何协同工作? 本文将深入 brk/mmap 系统调用、dlmalloc 算法,并对比 glibc 的实现细节, 构建一个高效、低碎片的用户态内存分配器。”

引言:用户态内存管理的挑战

当用户程序调用 malloc(100) 时,看似简单的操作背后涉及 用户态与内核态的复杂协作 :

小内存

(<128KB):通过
brk
扩展堆,libc 管理空闲链表
大内存

(≥128KB):通过
mmap
匿名映射,直接分配虚拟内存
内存回收


free
需合并相邻空闲块,避免碎片
安全边界

:内核需验证用户指针,防止越界访问

用户态内存分配器(如 glibc 的 ptmalloc )是 性能与内存效率的关键 ,其设计直接影响应用性能。

本文将系统性地剖析用户态内存管理,从系统调用到 libc 实现,并 深度对比 glibc 的 dlmalloc 算法 ,最终提供一个可运行的工业级框架。


第一章:brk 与 mmap 系统调用

1.1 brk 系统调用:堆管理基础

brk 的核心作用

管理堆顶指针

(Program Break)
扩展/收缩进程数据段
返回新的堆顶地址

系统调用接口

用户态封装

1.2 mmap 系统调用:灵活的内存映射

mmap 的核心能力

匿名映射

:分配大块虚拟内存(
MAP_ANONYMOUS

文件映射

:将文件映射到内存(
MAP_SHARED
/
MAP_PRIVATE

共享内存

:进程间共享内存区域

系统调用接口

用户态封装

1.3 brk vs mmap 对比

特性 brk mmap
适用场景 小内存(堆) 大内存、文件映射
内存连续性 连续 可不连续
分配粒度 页对齐 页对齐
回收方式 sbrk(负值) munmap
碎片风险 高(中间无法释放) 低(可释放任意区域)
性能 快(单次系统调用) 慢(需 VMA 操作)

💡 glibc 默认阈值 128KB :小内存 brk,大内存 mmap


第二章:用户态 malloc 实现框架

2.1 malloc 设计目标

核心需求:

  1. 高性能

    :分配/释放 O(1) 时间

  2. 低内存开销

    :元数据 < 8 字节/块

  3. 低碎片

    :合并相邻空闲块

  4. 线程安全

    :支持多线程(本文简化为单线程)

设计决策:

小内存

(<128KB):使用
brk 扩展的堆
+
空闲链表
大内存

(≥128KB):使用
mmap 匿名映射
空闲块管理


边界标签法
(Boundary Tag)

2.2 内存块结构设计

块头(Chunk Header)

关键设计:

低 3 位存储标志

:节省内存
prev_size 字段

:实现双向合并
fd/bk 指针

:空闲块时用作链表指针

2.3 空闲链表管理

边界标签法原理

每个块存储自身大小
空闲块额外存储前一块大小
通过 prev_size 实现向前合并

合并算法

空闲链表操作


第三章:malloc/free 核心算法实现

3.1 malloc 实现

主分配函数

堆分配(heap_alloc)

扩展堆(expand_heap)

mmap 分配(mmap_alloc)

3.2 free 实现

主释放函数

mmap 释放(munmap_alloc)


第四章:glibc ptmalloc 深度对比

4.1 glibc 内存分配器演进

版本 分配器 特点
glibc 2.0 dlmalloc 单线程,边界标签
glibc 2.1+ ptmalloc2 多线程,arena 分区
glibc 2.23+ ptmalloc3 优化碎片,tcache

ptmalloc2 核心改进:

多 Arena

:每个线程独立 arena,减少锁竞争
Bins 分类

:fastbins(小块)、bins(普通块)、top chunk
线程缓存

(tcache):glibc 2.26+,进一步减少锁

4.2 ptmalloc2 核心数据结构

Arena 结构

Bins 分类:

Fastbins

:16-80 字节,LIFO,无合并
Unsorted bin

:刚释放的块,快速重用
Small bins

:<512 字节,FIFO,合并
Large bins

:≥512 字节,按大小排序

4.3 ptmalloc2 分配流程

malloc 流程:

  1. 检查 fastbins

    :小块直接分配

  2. 检查 unsorted bin

    :快速重用

  3. 检查 small/large bins

    :查找合适块

  4. 使用 top chunk

    :无合适块时分割 top

  5. 扩展堆/mmap

    :top 不足时

free 流程:

  1. 检查 fastbins

    :小块直接放入

  2. 合并相邻块

    :向前/向后合并

  3. 放入 unsorted bin

    :合并后的块

  4. 整理 bins

    :定期将 unsorted bin 移到对应 bin

4.4 tcache 优化(glibc 2.26+)

tcache 设计:

每个线程独立缓存

:64 个 bin(1-1032 字节)
无锁操作

:分配/释放完全无锁
缓存大小限制

:每个 bin 最多 7 块

性能提升:

小内存分配提速 2-3 倍
减少 arena 锁竞争


第五章:高级特性与安全机制

5.1 内存对齐与最小块大小

对齐要求:

8 字节对齐

:满足 double、指针对齐
块头 8 字节

:prev_size + size

最小块大小:

请求大小对齐:

5.2 安全机制:防止堆溢出

块头保护:

size 字段校验

:防止覆盖
magic number

(可选):检测块头损坏

glibc 安全特性:

FORTIFY_SOURCE

:编译时检查
_FORTIFY_SOURCE=2

:运行时检查
malloc_check

:环境变量启用额外检查

5.3 内存泄漏检测

malloc_stats:

malloc_info:

XML 格式详细统计
可用于内存分析工具


第六章:与内核的交互安全

6.1 copy_from_user / copy_to_user

问题:内核如何安全访问用户内存?

用户指针可能无效

(NULL、内核地址)
用户指针可能未映射

(触发 Page Fault)

安全验证函数:

系统调用中的使用:

6.2 用户态指针验证

malloc 中的指针验证:

⚠️ 生产环境 malloc 库通常不验证指针 (性能考虑),依赖 valgrind 等工具检测


结论:用户态内存管理的工程权衡

用户态内存分配器是 性能、内存效率、复杂度 的完美平衡:

brk/mmap 分离

:小内存高效,大内存灵活
边界标签法

:O(1) 合并,低碎片
多级缓存

(tcache):无锁分配,极致性能

理解 malloc 不仅有助于应用开发,更能培养 内存安全意识 :

避免内存泄漏

:及时 free
防止堆溢出

:边界检查
合理使用内存

:避免大内存频繁分配

对于希望深入 glibc 的开发者,建议:

阅读
malloc/malloc.c
源码
使用
valgrind

massif
分析内存使用
通过
MALLOC_TRACE
跟踪分配轨迹

用户态内存管理的故事,仍在继续。随着 jemalloc、tcmalloc 等高性能分配器的出现,这一古老而关键的组件将持续演进。


附录:关键数据结构与函数速查

核心数据结构

结构 作用 文件
struct malloc_chunk 内存块头 malloc.h
struct malloc_state Arena 状态 malloc.c
tcache_perthread_struct 线程缓存 malloc.c

关键函数

函数 功能 文件
__libc_malloc malloc 入口 malloc.c
sysmalloc 大内存分配 malloc.c
int_free free 核心 malloc.c
malloc_stats 内存统计 malloc.c

调试环境变量

变量 用途
MALLOC_TRACE 跟踪分配轨迹
MALLOC_CHECK_ 启用安全检查
TCMALLOC_SAMPLE_PARAMETER tcmalloc 采样
LD_PRELOAD 替换分配器(如 jemalloc)
最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。