深入理解glibc malloc:内存分配器实现原理
扫描二维码
随时随地手机看文章
Understanding glibc malloc
日志:
译者言:
文章目录
前言
堆内存(Heap Memory)是一个很有意思的领域。你可能和我一样,也困惑于下述问题很久了: 最近,我终于有时间去深入了解这些问题。下面就让我来谈谈我的调研成果。开源社区公开了很多现成的内存分配器(Memory Allocators,以下简称为分配器): 每一种分配器都宣称自己快(fast)、可拓展(scalable)、效率高(memory efficient)!但是并非所有的分配器都适用于我们的应用。内存吞吐量大(memory hungry)的应用程序,其性能很大程度上取决于分配器的性能。在这篇文章中,我只谈「glibc malloc」分配器。为了方便大家理解「glibc malloc」,我会联系最新的源代码。历史:ptmalloc2 基于 dlmalloc 开发,其引入了多线程支持,于 2006 年发布。发布之后,ptmalloc2 整合进了 glibc 源码,此后其所有修改都直接提交到了 glibc malloc 里。因此,ptmalloc2 的源码和 glibc malloc 的源码有很多不一致的地方。(译者注:1996 年出现的 dlmalloc 只有一个主分配区,该分配区为所有线程所争用,1997 年发布的 ptmalloc 在 dlmalloc 的基础上引入了非主分配区的概念。)
1. 申请堆的系统调用
我在之前的文章中提到过,malloc
内部通过 brk
或 mmap
系统调用向内核申请堆区。译者注:在内存管理领域,我们一般用「堆」指代用于分配动态内存的虚拟地址空间,而用「栈」指代用于分配静态内存的虚拟地址空间。具体到虚拟内存布局(Memory Layout),堆维护在通过brk
系统调用申请的「Heap」及通过mmap
系统调用申请的「Memory Mapping Segment」中;而栈维护在通过汇编栈指令动态调整的「Stack」中。在 Glibc 里,「Heap」用于分配较小的内存及主线程使用的内存。下图为 Linux 内核 v2.6.7 之后,32 位模式下的虚拟内存布局方式。
2. 多线程支持
Linux 的早期版本采用 dlmalloc 作为它的默认分配器,但是因为 ptmalloc2 提供了多线程支持,所以 后来 Linux 就转而采用 ptmalloc2 了。多线程支持可以提升分配器的性能,进而间接提升应用的性能。在 dlmalloc 中,当两个线程同时malloc
时,只有一个线程能够访问临界区(critical section)——这是因为所有线程共享用以缓存已释放内存的「空闲列表数据结构」(freelist data structure),所以使用 dlmalloc 的多线程应用会在 malloc
上耗费过多时间,从而导致整个应用性能的下降。在 ptmalloc2 中,当两个线程同时调用 malloc
时,内存均会得以立即分配——每个线程都维护着单独的堆,各个堆被独立的空闲列表数据结构管理,因此各个线程可以并发地从空闲列表数据结构中申请内存。这种为每个线程维护独立堆与空闲列表数据结构的行为就「per thread arena」。2.1. 案例代码
/* Per thread arena example. */
#include
#include
#include
#include
#include
void* threadFunc(void* arg) {
printf("Before malloc in thread 1\n");
getchar();
char* addr = (char*) malloc(1000);
printf("After malloc and before free in thread 1\n");
getchar();
free(addr);
printf("After free in thread 1\n");
getchar();
}
int main() {
pthread_t t1;
void* s;
int ret;
char* addr;
printf("Welcome to per thread arena example::%d\n",getpid());
printf("Before malloc in main thread\n");
getchar();
addr = (char*) malloc(1000);
printf("After malloc and before free in main thread\n");
getchar();
free(addr);
printf("After free in main thread\n");
getchar();
ret = pthread_create(