理解 glibc malloc

本文章引用
堆内存是一个很有意思的领域,这样的问题:
henan.qq.com/zt/2018/dyzb/qi angwang.htm?from=singlemessage&isappinstalled=0

堆内存是如何从内核中分配的? 
内存管理效率怎样? 
它是由内核、库函数,还是应用本身管理的? 
堆内存可以开发吗?

我也困惑了很久,但是直到最近我才有时间去了解它。下面就让我来谈谈我的研究成果。开源社区提供了很多现成的内存分配器(memory allocators ):

  • dlmalloc – General purpose allocator
  • ptmalloc2 – glibc
  • jemalloc – FreeBSD and Firefox
  • tcmalloc – Google
  • libumem – Solaris

每一种分配器都宣称自己快(fast)、可拓展(scalable )、效率高(memory efficient)!但是并非所有的分配器都适用于我们的应用。内存吞吐量大(memory hungry)的应用程序的性能很大程度上取决于内存分配器的性能。
在这篇文章中,我将只谈论「glibc malloc」内存分配器。为了更好地理解「glibc malloc」,我会联系最近的源代码。

历史:[ptmalloc2](http://www.malloc.de/en/) 基于 [dlmalloc](http://g.oswego.edu/dl/html/malloc.html) 开发,并添加了对多线程的支持,于 2006 年公布。在公布之后,ptmalloc2 被整合到 glibc 源代码中,此后 ptmalloc2 所有的修改都直接提交到 glibc 的 malloc 部分去了。因此,ptmalloc2 的源码和 glibc 的 malloc源码有很多不一致的地方。(译者注:1996 年出现的 dlmalloc 只有一个主分配区,为所有线程所争用,1997 年发布的 ptmalloc 在 dlmalloc 的基础上引入了非主分配区的支持。 )

理解 glibc malloc

  • 系统调用
  • 线程处理
  • Example
    • 输出分析
      • 在主线程 malloc 之前
    • Multiple Arena
    • Multiple Heaps
  • Chunk
    • Allocated chunk
    • Free chunk
  • Bins
    • Fast Bin
    • Unsorted Bin
    • mall Bin
    • Large Bin
    • Top Chunk
    • Last Remainder Chunk

系统调用

在之前的文章中提到过malloc的内部调用为 brk 或 mmap 。

译者注:其中有一张关于虚拟地址空间分布的图片,我觉得很有助于本篇文章的理解,因此把它放在此处。

虚拟地址空间分布

线程处理
Linux 的早期版本使用 dlmalloc 为默认内存分配器,但是因为 ptmalloc2 提供了多线程支持,所以 Linux 后来采用 ptmalloc2 作为默认内存分配器。多线程支持可以提升内存分配器的性能,进而间接提升应用的性能。

在 dlmalloc 中,当有两个线程同时调用 malloc 时,只有一个线程能够访问临界区(critical section)——因为「空闲列表数据结构」(freelist data structure)被所有可用线程共享。正如此,使用 dlmalloc 的多线程应用会在内存分配上耗费过多时间,导致整个应用性能的下降。

而在 ptmalloc2 中,当有两个线程同时调用 malloc 时,内存均会得到立即分配——因为每个线程都维护着一个独立的「堆段」(heap segment),因此维护这些堆的「空闲列表数据结构」也是独立的。这种为每个线程独立地维护堆和「空闲列表数据结构」的行为就称为 per thread arena。

/* 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(&t1, NULL, threadFunc, NULL);
        if(ret)
        {
                printf("Thread creation error\n");
                return -1;
        }
        ret = pthread_join(t1, &s);
        if(ret)
        {
                printf("Thread join error\n");
                return -1;
        }
        return 0;
}

输出分析

在主线程 malloc 之前

在如下的输出里我们可以看到,这里还 没有「 堆段」 也没有 「每线程栈」(per-thread stack),因为 thread1 还没有创建!

你可能感兴趣的:(理解 glibc malloc)