在malloc,为什么要使用brk?为什么不直接使用mmap?

问题描述 投票:6回答:4

malloc的典型实现使用brk / sbrk作为从OS声明内存的主要手段。但是,他们也使用mmap来获取大量分配的块。使用brk而不是mmap有什么好处,还是仅仅是传统?使用mmap做这一切不会有效吗?

(注意:我在这里可以互换使用sbrkbrk,因为它们是同一个Linux系统调用的接口,brk。)


作为参考,这里有几个描述glibc malloc的文档:

GNU C Library参考手册:GNU分配器 https://www.gnu.org/software/libc/manual/html_node/The-GNU-Allocator.html

glibc wiki:Malloc概述 https://sourceware.org/glibc/wiki/MallocInternals

这些文件描述的是sbrk用于声称小分配的主要竞技场,mmap用于声称二级竞技场,而mmap也用于为大型物体(“远大于一页”)声称空间。

应用程序堆(使用sbrk声明)和mmap的使用引入了一些可能不必要的额外复杂性:

分配竞技场 - 主竞技场使用应用程序的堆。其他竞技场使用mmap'd堆。要将块映射到堆,您需要知道哪种情况适用。如果该位为0,则块来自主竞技场和主堆。如果该位为1,则块来自mmap'd内存,并且可以从块的地址计算堆的位置。

[Glibc malloc衍生自ptmalloc,它来源于dlmalloc,始于1987年。]


jemalloc联机帮助页(http://jemalloc.net/jemalloc.3.html)有这样的说法:

传统上,分配器使用sbrk(2)来获取内存,由于多种原因,包括竞争条件,增加的碎片以及对最大可用内存的人为限制,这是次优的。如果操作系统支持sbrk(2),则此分配器按优先顺序使用mmap(2)和sbrk(2);否则只使用mmap(2)。

因此,他们甚至在这里说sbrk不是最理想的,但他们仍然使用它,即使他们已经去编写代码的麻烦,以便它没有它。

[jemalloc的写作始于2005年。]

更新:考虑到这一点,关于“按照优先顺序”的那一点给了我一条询问线。为什么选择顺序?他们只是使用sbrk作为后备,以防mmap不支持(或缺乏必要的功能),或者是否有可能进入一些状态,它可以使用sbrk而不是mmap?我会看看他们的代码,看看我能弄清楚它在做什么。


我问,因为我在C中实现垃圾收集系统,到目前为止,除了mmap之外我没有理由使用任何东西。我想知道是否有一些我缺少的东西。

(在我的情况下,我有另外的理由避免brk,这是我可能需要在某些时候使用malloc。)

c linux malloc mmap brk
4个回答
5
投票

系统调用brk()的优点是只有一个数据项来跟踪内存使用情况,这也很好地与堆的总大小直接相关。

这与1975年的Unix V6完全相同。请注意,V6支持65,535字节的用户地址空间。因此,对于管理超过64K,当然不是太字节,没有太多的想法。

使用mmap似乎是合理的,直到我开始想知道如何改变或添加垃圾收集可以使用mmap,但也没有重写分配算法。

这与realloc()fork()等有效吗?


4
投票

显而易见的优点是你可以增加最后的分配,这是你无法用mmap(2)做的事情(mremap(2)是Linux扩展,不可移植)。

对于使用realloc(3)的幼稚(而不是那么幼稚)的程序,例如。附加到一个字符串,这转换为1或2个数量级的速度提升;-)


3
投票

mmap()在早期版本的Unix中不存在。 brk()是当时增加流程数据段大小的唯一方法。带有mmap()的Unix的第一个版本是80年代中期的SunOS,第一个开源版本是1990年的BSD-Reno。

并且可用于malloc(),您不希望需要一个真实的文件来备份内存。 1988年,SunOS为此目的实施了/dev/zero,并在1990年的HP-UX实施了MAP_ANONYMOUS标志。

现在有mmap()版本提供了各种分配堆的方法。


2
投票

每次内存分配调用mmap(2)对于通用内存分配器来说不是一种可行的方法,因为mmap(2)的分配粒度(一次可以分配的最小单个单元)是PAGESIZE(通常是4096字节),并且因为它需要慢和复杂的系统调用。具有低碎片的小分配的分配器快速路径应该不需要系统调用。

所以不管你使用什么策略,你仍然需要支持多个glibc调用内存竞技场,the GNU manual提到:“多个竞技场的存在允许多个线程在不同的场地同时分配内存,从而提高性能。”


jemalloc manpage(http://jemalloc.net/jemalloc.3.html)有这样说:

传统上,分配器使用sbrk(2)来获取内存,由于多种原因,包括竞争条件,增加的碎片以及对最大可用内存的人为限制,这是次优的。如果操作系统支持sbrk(2),则此分配器按优先顺序使用mmap(2)和sbrk(2);否则只使用mmap(2)。

根据我的理解,我不知道这些中的任何一个如何适用于sbrk(2)的现代用途。竞争条件由线程原语处理。碎片的处理方式与mmap(2)分配的内存竞技场一样。最大可用内存是无关紧要的,因为mmap(2)应该用于任何大型分配以减少碎片并在free(3)上立即将内存释放回操作系统。


使用应用程序堆(声称用sbrk)和mmap引入了一些可能不必要的额外复杂性:

分配竞技场 - 主竞技场使用应用程序的堆。其他竞技场使用mmap'd堆。要将块映射到堆,您需要知道哪种情况适用。如果该位为0,则块来自主竞技场和主堆。如果该位为1,则块来自mmap'd内存,并且可以从块的地址计算堆的位置。

所以现在的问题是,如果我们已经在使用mmap(2),为什么不在mmap(2)的流程开始时分配竞技场而不是使用sbrk(2)?特别是如果引用的话,有必要跟踪使用哪种分配类型。有几个原因:

  1. 可能不支持mmap(2)
  2. sbrk(2)已经初步化为一个过程,而mmap(2)将引入额外的要求。
  3. 正如glibc wiki所说,“如果请求足够大,则使用mmap()直接从操作系统请求内存[...],并且可能会限制一次可以有多少这样的映射。”
  4. mmap(2)分配的内存映射不能轻易扩展。 Linux有mremap(2),但它的使用限制了分配器到支持它的内核。使用PROT_NONE访问预映射许多页面会占用太多虚拟内存。使用MMAP_FIXED取消映射之前可能没有警告的任何映射。 sbrk(2)没有这些问题,并且明确设计为允许安全地扩展其内存。
© www.soinside.com 2019 - 2024. All rights reserved.