Glibc 内存管理知识点总结

Posted the_scent_of_th_soul

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Glibc 内存管理知识点总结相关的知识,希望对你有一定的参考价值。

这几天在看Glibc 内存管理模块的内容,感觉收获颇多,在此做个简单的总结,以便知识点回顾。

  • 先介绍一下相关的背景。有个项目组在研发一个类似数据库的NoSql 系统时,遇到了Glibc 内存暴增问题。据此,在经过一系列排查过后,他们提出了几个问题,分别是:

  • 1.Glibc 在什么情况下不会将内存归还给操作系统系统?

  • 2.Glibc 的内存管理方式有哪些约束?适合什么样的内存分配场景?
  • 3.我们系统中的内存管理方式是与Glibc的内存管理的约束相悖的吗?

  • 4.Glibc 是如何管理内存的?

带着这些问题,我去看了Glibc的ptmalloc2源码,总结出了一些知识点。

首先,我们来看一个x86_64下Linux进程的默认内存布局形式的示意图:


如上图所示,对于AMD64 系统,内存布局采用经典布局:

  • 首先被载入的是.text 段 ,然后是.data段,最后是.bss段。
  • 最上面的128TB是内核使用的,应用程序不可以直接访问。
  • 应用程序的堆栈从高地址处开始向下生长。
  • .bss段与堆栈之间的空间是空闲的,空闲空间被分成两部分,一部分为heap,一部分为mmap映射区域。mmap区域与栈区域相对增长。

  • 在不同的Linux内核和机器上,mmap区域的开始位置一般是不同的。并且在当前内核默认配置下,进程的 mmap映射区域 并不是从一个固定地址开始,而且每次启动时的值都不一样。当然,可以通过以下命令:
    sudo sysctl -w kernel.randomize_va_space

改变全局变量randomize_va_space的值,使之为0,让进程的栈和mmap映射区域从一个固定位置开始。

简单了解了一下x86_64下Linux进程的默认内存布局后,让我们来看一些
操作系统内存分配的相关函数

  • heap和mmap: heap和mmap映射区域都是可以供用户程序自由使用的虚拟内存空间,但是它们在刚开始的时候并没有映射到内存空间内,是不可访问的。在内核请求分配该空间之前,对这个空间的访问会导致segmentation fault。用户可以直接使用系统调用来管理heap 和mmap映射区域,但更多的时候程序都是使用c语言提供的malloc 函数和free函数来动态分配和释放内存。
  • Heap操作相关函数:系统调用的brk()和C库函数sbrk()。Glibc的malloc函数族就是调用sbrk(),sbrk()函数在内核的管理下将虚拟地址空间映射到内存,供malloc函数使用。
    C语言的动态内存分配基本函数是malloc(),在Linux上的实现(系统调用)是通过调用简单的brk()。
  • Mmap映射区域操作相关函数:Unix进程可以使用mmap函数来创建新的虚拟存储器区域,并将文件或对象映射到这些区域中。文件被映射到多个页上,如果文件的大小不是所有页之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。函数的定义如下:
#include<sys/mman.h>
void *mmap(void *addr, size_t length, int port, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);

内存的延迟分配:注意,只有在真正访问一个地址的时候才建立这个地址的物理映射。Linux内核在用户申请内存的时候,只是给它分配了一个线性区(也就是虚拟内存),并没有分配实际物理内存;只有当用户使用这块内存的时候,内核才会分配具体的物理页面给用户,这时候才占用宝贵的物理内存。释放时,释放线性区,找到对应的物理页面,将其全部释放。

说明:当不知道程序的每个部分将需要多少内存时,系统内存空间有限,而内存需求又是变化的,这时就需要内存管理程序来负责分配和回收内存。程序的动态性越强,内存管理就越重要,内存分配程序的选择也就越重要。

内存管理的方法:内存管理的方法有很多,它们各有优缺点,都有各自的最适用情形,在这里我只列出一些方法的主要优缺点及其比较适用的场景。

1.c风格的内存管理程序:

c风格的内存管理程序主要实现malloc和free函数。内存管理程序主要通过调用 brk()或者mmap()给进程添加额外的虚拟内存。

  • 优点:

    a.生存期局限于当前函数的内存时  非常容易  管理
    b.对程序结构的变化要求不高。
    
  • 缺点:不适合管理内存生存期长的程序。

  • 常用情形:Doug Lea Malloc 、ptmalloc、BSD malloc、Hoard、TCMalloc .

2.池式内存管理:内存池是一种半内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序经历一些特定的阶段,每个阶段都有分配给进程的特定阶段自己的内存池。在结束每个阶段时,会一次释放所有内存。

  • 优点:

    a.显著优点是:内存分配和回收更快。因为每次都是在一个池中完成的,分配可以在O(1)时间内完成,释放内存池所需时间也差不多。
    b.可以预先分配错误处理池,以便程序在常规内存被耗尽时,仍可以恢复。
    c.应用程序可以简单地管理内存。有非常易于使用的标准实现。
    
  • 缺点:缺点很多,个人觉得池式内存管理一般只适用特定的、有明显内存池特征的程序。

  • 常用情形:很多网络服务进程,例如,Apache。

3.引用计数器

4.垃圾收集器:垃圾收集器是一种动态存储分配器,它自动释放程序不再需要的已分配的块。在c程序的上下文中,应用调用malloc,但从不调用free,垃圾收集器定期识别垃圾块,并相应地调用free,将这些块放回空闲链表中。很多类型的垃圾收集器都需要知道数据结构内部指针的规划,为了正确运行,它们必须是语言本身的一部分。

  • 优点:

    a.永远不必担心内存的双重释放或者对象的生命周期。
    b. 使用某些收集器,可以使用与常规分配相同的API。
    
  • 缺点:

    a.无法干涉何时释放内存。
    b.慢
    c.垃圾收集错误引发的缺陷难以调试。
    d.不把 不再使用的指针 设置为 NULL,会有内存泄漏(很容易忘的,对吧!)
    
  • 常用情形:诸如Java、ML、Perl等现代语言系统的一个重要部分。

好啦,第一部分的简单介绍就以 对ptmalloc 内存管理的概述结尾吧:

1.ptmalloc实现了malloc(),free()以及一组其他的函数,以提供动态内存管理的支持。

2.分配器处在用户程序和内核之间,它响应用户的分配请求,向操作系统申请内存,然后将其返回给用户程序。

3.为了保持高效的分配,分配器一般都会预先分配一块大于用户请求的内存,并通过某种算法来管理这块内存。

4.用户释放掉内存并不立即返回给操作系统,分配器会管理这些被释放掉的空闲空间。当响应用户分配要求时,分配器会首先在空闲空间找一块合适的内存给用户,当在空闲空间找不到的情况下才分配一块新的内存。
5.为了设计一个高效的分配器,所考虑的因素应该有很多,例如分配器本身所占内存等。

以上是关于Glibc 内存管理知识点总结的主要内容,如果未能解决你的问题,请参考以下文章

Linux系列:glibc程序设计规范与内存管理思想

Linux glibc内存管理:用户态内存分配器——ptmalloc实现原理

tcmalloc jemalloc glibc内存分配管理模块性能测试对比

从上到下看linux内存管理--glibc malloc

内存GLIBC堆内存申请

十问 Linux 虚拟内存管理 (glibc)