简体   繁体   English

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

[英]In malloc, why use brk at all? Why not just use mmap?

Typical implementations of malloc use brk / sbrk as the primary means of claiming memory from the OS. malloc的典型实现使用brk / sbrk作为从操作系统申请内存的主要方式。 However, they also use mmap to get chunks for large allocations.然而,他们也使用mmap来获取大分配的块。 Is there a real benefit to using brk instead of mmap , or is it just tradition?使用brk而不是mmap真的有好处,还是只是传统? Wouldn't it work just as well to do it all with mmap ?使用mmap来完成这一切不是同样有效吗?

(Note: I use sbrk and brk interchangeably here because they are interfaces to the same Linux system call, brk .) (注意:我在这里互换使用sbrkbrk因为它们是同一个 Linux 系统调用brk的接口。)


For reference, here are a couple of documents describing the glibc malloc :作为参考,这里有一些描述 glibc malloc的文档:

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

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

What these documents describe is that sbrk is used to claim a primary arena for small allocations, mmap is used to claim secondary arenas, and mmap is also used to claim space for large objects ("much larger than a page").这些文档描述的是sbrk用于为小分配声明一个主要区域, mmap用于声明次要区域, mmap还用于为大对象(“比页面大得多”)声明空间。

The use of both the application heap (claimed with sbrk ) and mmap introduces some additional complexity that might be unnecessary:应用程序堆(使用sbrk声明)和mmap的使用引入了一些可能不必要的额外复杂性:

Allocated Arena - the main arena uses the application's heap.分配的竞技场 - 主竞技场使用应用程序的堆。 Other arenas use mmap 'd heaps.其他竞技场使用mmap的堆。 To map a chunk to a heap, you need to know which case applies.要将块映射到堆,您需要知道适用于哪种情况。 If this bit is 0, the chunk comes from the main arena and the main heap.如果该位为 0,则 chunk 来自 main arena 和 main heap。 If this bit is 1, the chunk comes from mmap 'd memory and the location of the heap can be computed from the chunk's address.如果该位为 1,则该块来自mmap的内存,并且可以根据块的地址计算堆的位置。

[Glibc malloc is derived from ptmalloc , which was derived from dlmalloc , which was started in 1987.] [Glibc malloc 派生自ptmalloc ,派生自 1987 年开始的dlmalloc 。]


The jemalloc manpage ( http://jemalloc.net/jemalloc.3.html ) has this to say: jemalloc联机帮助页 ( http://jemalloc.net/jemalloc.3.html ) 是这样说的:

Traditionally, allocators have used sbrk(2) to obtain memory, which is suboptimal for several reasons, including race conditions, increased fragmentation, and artificial limitations on maximum usable memory.传统上,分配器使用sbrk(2)来获取内存,这不是最优的,原因有几个,包括竞争条件、增加的碎片和对最大可用内存的人为限制。 If sbrk(2) is supported by the operating system, this allocator uses both mmap(2) and sbrk(2), in that order of preference;如果操作系统支持sbrk(2) ,则此分配器按优先顺序使用mmap(2)和 sbrk(2); otherwise only mmap(2) is used.否则只使用mmap(2)

So, they even say here that sbrk is suboptimal but they use it anyway, even though they've already gone to the trouble of writing their code so that it works without it.因此,他们甚至在这里说sbrk不是最优的,但他们仍然使用它,即使他们已经不厌其烦地编写代码以使其在没有它的情况下也能正常工作。

[Writing of jemalloc started in 2005.] [jemalloc 的编写始于 2005 年。]

UPDATE: Thinking about this more, that bit about "in order of preference" gives me a line on inquiry.更新:更多地考虑这个问题,关于“按优先顺序”的那一点给了我一个查询线。 Why the order of preference?为什么优先顺序? Are they just using sbrk as a fallback in case mmap is not supported (or lacks necessary features), or is it possible for the process to get into some state where it can use sbrk but not mmap ?他们是否只是使用sbrk作为后备以防不支持mmap (或缺少必要的功能),或者进程是否有可能进入可以使用sbrk但不能使用mmap的状态? I'll look at their code and see if I can figure out what it's doing.我将查看他们的代码,看看我是否能弄清楚它在做什么。


I'm asking because I'm implementing a garbage collection system in C, and so far I see no reason to use anything besides mmap .我问是因为我正在用 C 实现垃圾收集系统,到目前为止我认为没有理由使用mmap之外的任何东西。 I'm wondering if there's something I'm missing, though.不过,我想知道我是否遗漏了什么。

(In my case I have an additional reason to avoid brk , which is that I might need to use malloc at some point.) (在我的例子中,我还有一个避免brk的额外原因,那就是我可能需要在某些时候使用malloc 。)

The system call brk() has the advantage of having only a single data item to track memory use, which happily is also directly related to the total size of the heap.系统调用brk()的优点是只有一个数据项来跟踪内存使用情况,这也与堆的总大小直接相关。

This has been in the exact same form since 1975's Unix V6.自 1975 年的 Unix V6 以来,这一直是完全相同的形式。 Mind you, V6 supported a user address space of 65,535 bytes.请注意,V6 支持 65,535 字节的用户地址空间。 So there wasn't a lot of thought given for managing much more than 64K, certainly not terabytes.因此,没有太多考虑管理超过 64K 的数据,当然不是 TB。

Using mmap seems reasonable until I start wondering how altered or added-on garbage collection could use mmap but without rewriting the allocation algorithm too.使用mmap似乎是合理的,直到我开始想知道如何改变或添加垃圾收集可以使用mmap无需重写分配算法。

Will that work nicely with realloc() , fork() , etc.?这能很好地与realloc()fork()等一起使用吗?

Calling mmap(2) once per memory allocation is not a viable approach for a general purpose memory allocator because the allocation granularity (the smallest individual unit which may be allocated at a time) for mmap(2) is PAGESIZE (usually 4096 bytes), and because it requires a slow and complicated syscall.每次内存分配调用mmap(2)一次对于通用内存分配器来说不是一种可行的方法,因为 mmap(2) 的分配粒度(一次可以分配的最小单个单元mmap(2)PAGESIZE (通常为 4096 字节),并且因为它需要一个缓慢而复杂的系统调用。 The allocator fast path for small allocations with low fragmentation should require no syscalls.具有低碎片的小分配的分配器快速路径应该不需要系统调用。

So regardless what strategy you use, you still need to support multiple of what glibc calls memory arenas, and the GNU manual mentions: "The presence of multiple arenas allows multiple threads to allocate memory simultaneously in separate arenas, thus improving performance."所以无论你使用什么策略,你仍然需要支持多个 glibc 所谓的 memory arenas, GNU 手册中提到: “多个 arenas 的存在允许多个线程在单独的 arenas 中同时分配内存,从而提高性能。”


The jemalloc manpage ( http://jemalloc.net/jemalloc.3.html ) has this to say: jemalloc 联机帮助页 ( http://jemalloc.net/jemalloc.3.html ) 是这样说的:

Traditionally, allocators have used sbrk(2) to obtain memory, which is suboptimal for several reasons, including race conditions, increased fragmentation, and artificial limitations on maximum usable memory.传统上,分配器使用 sbrk(2) 来获取内存,这不是最优的,原因有几个,包括竞争条件、增加的碎片和对最大可用内存的人为限制。 If sbrk(2) is supported by the operating system, this allocator uses both mmap(2) and sbrk(2), in that order of preference;如果操作系统支持 sbrk(2),则此分配器按优先顺序同时使用 mmap(2) 和 sbrk(2); otherwise only mmap(2) is used.否则只使用 mmap(2)。

I don't see how any of these apply to the modern use of sbrk(2) , as I understand it.据我所知,我看不出这些如何适用于sbrk(2)的现代使用。 Race conditions are handled by threading primitives.竞争条件由线程原语处理。 Fragmentation is handled just as would be done with memory arenas allocated by mmap(2) .碎片的处理方式与mmap(2)分配的内存区域的处理方式相同。 The maximum usable memory is irrelevant, because mmap(2) should be used for any large allocation to reduce fragmentation and to release memory back to the operating system immediately on free(3) .最大可用内存是无关紧要的,因为mmap(2)应该用于任何大的分配以减少碎片并在free(3)上立即将内存释放回操作系统。


The use of both the application heap (claimed with sbrk) and mmap introduces some additional complexity that might be unnecessary:应用程序堆(使用 sbrk 声明)和 mmap 的使用引入了一些可能不必要的额外复杂性:

Allocated Arena - the main arena uses the application's heap.分配的竞技场 - 主竞技场使用应用程序的堆。 Other arenas use mmap'd heaps.其他竞技场使用 mmap'd 堆。 To map a chunk to a heap, you need to know which case applies.要将块映射到堆,您需要知道适用于哪种情况。 If this bit is 0, the chunk comes from the main arena and the main heap.如果该位为 0,则 chunk 来自 main arena 和 main heap。 If this bit is 1, the chunk comes from mmap'd memory and the location of the heap can be computed from the chunk's address.如果该位为 1,则块来自 mmap 的内存,堆的位置可以根据块的地址计算得出。

So the question now is, if we're already using mmap(2) , why not just allocate an arena at process start with mmap(2) instead of using sbrk(2) ?所以现在的问题是,如果我们已经在使用mmap(2) ,为什么不在进程开始时使用mmap(2)而不是使用sbrk(2)分配一个竞技场? Especially so if, as quoted, it is necessary to track which allocation type was used.尤其是如果如引用的那样,有必要跟踪使用了哪种分配类型。 There are several reasons:有几个原因:

  1. mmap(2) may not be supported.可能不支持mmap(2)
  2. sbrk(2) is already initialized for a process, whereas mmap(2) would introduce additional requirements. sbrk(2)已经为进程初始化,而mmap(2)会引入额外的要求。
  3. As glibc wiki says, "If the request is large enough, mmap() is used to request memory directly from the operating system [...] and there may be a limit to how many such mappings there can be at one time. "正如glibc wiki所说, “如果请求足够大,mmap() 用于直接从操作系统请求内存 [...] 并且一次可以有多少这样的映射可能会有限制。”
  4. A memory map allocated with mmap(2) cannot be extended as easily.mmap(2)分配的内存映射不能轻易扩展。 Linux has mremap(2) , but its use limits the allocator to kernels which support it. Linux 有mremap(2) ,但它的使用将分配器限制为支持它的内核。 Premapping many pages with PROT_NONE access uses too much virtual memory.预映射许多具有PROT_NONE访问权限的页面会使用过多的虚拟内存。 Using MMAP_FIXED unmaps any mapping which may have been there before without warning.使用MMAP_FIXED取消映射之前可能存在的任何映射,而不会发出警告。 sbrk(2) has none of these problems, and is explicitly designed to allow for extending its memory safely. sbrk(2)没有这些问题,并且明确设计为允许安全地扩展其内存。

mmap() didn't exist in the early versions of Unix. mmap()在 Unix 的早期版本中不存在。 brk() was the only way to increase the size of the data segment of the process at that time. brk()是当时增加进程数据段大小的唯一方法。 The first version of Unix with mmap() was SunOS in the mid 80's, the first open-source version was BSD-Reno in 1990.第一个带有mmap()的 Unix 版本是 80 年代中期的SunOS ,第一个开源版本是 1990 年的 BSD-Reno。

And to be usable for malloc() you don't want to require a real file to back up the memory.并且要可用于malloc()你不想需要一个真实的文件来备份内存。 In 1988 SunOS implemented /dev/zero for this purpose, and in the 1990's HP-UX implemented the MAP_ANONYMOUS flag. 1988 年,SunOS 为此目的实现了/dev/zero ,并在 1990 年代的 HP-UX 中实现了MAP_ANONYMOUS标志。

There are now versions of mmap() that offer a variety of methods to allocate the heap.现在的mmap()版本提供了多种分配堆的方法。

The obvious advantage is that you can grow the last allocation in place , which is something you can't do with mmap(2) ( mremap(2) is a Linux extension, not portable).明显的优点是您可以就地增加最后的分配,这是您不能用mmap(2)做的事情( mremap(2)是 Linux 扩展,不可移植)。

For naive (and not-so-naive) programs which are using realloc(3) eg.对于使用realloc(3)的天真的(和不太天真的)程序,例如。 to append to a string, this translates in a 1 or 2 orders of magnitude speed boost;-)附加到一个字符串,这转化为 1 或 2 个数量级的速度提升;-)

I don't know the details on Linux specifically, but on FreeBSD for several years now mmap is preferred and jemalloc in FreeBSD's libc has sbrk() completely disabled.我不知道具体在 Linux 上的细节,但在 FreeBSD 上已经有好几年了,现在 mmap 是首选,而 FreeBSD 的 libc 中的 jemalloc 已经完全禁用了 sbrk() 。 brk()/sbrk() are not implemented in the kernel on the newer ports to aarch64 and risc-v. brk()/sbrk() 未在 aarch64 和 risc-v 的较新端口上的内核中实现。

If I understand the history of jemalloc correctly, it was originally the new allocator in FreeBSD's libc before it was broken out and made portable.如果我正确理解 jemalloc 的历史,它最初是 FreeBSD 的 libc 中的新分配器,然后才被打破并变得可移植。 Now FreeBSD is a downstream consumer of jemalloc.现在 FreeBSD 是 jemalloc 的下游消费者。 Its very possible that its preference for mmap() over sbrk() originated with the characteristics of the FreeBSD VM system that was built around implementing the mmap interface.它对 mmap() 而不是 sbrk() 的偏好很可能源于 FreeBSD VM 系统的特性,该系统是围绕实现 mmap 接口而构建的。

It's worth noting that in SUS and POSIX brk/sbrk are deprecated and should be considered non-portable at this point .值得注意的是,在 SUS 和 POSIX 中,brk/sbrk 已被弃用,此时应将其视为不可移植的 If you are working on a new allocator you probably don't want to depend on them.如果您正在使用新的分配器,您可能不想依赖它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM