Nat*_*C-K 8 c linux malloc mmap brk
malloc
使用brk
/ sbrk
作为从OS声明内存的主要方式的典型实现。但是,它们还用于mmap
获取大容量分配的块。使用brk
代替确实有真正的好处mmap
,还是仅仅是传统?将其与所有内容一起使用mmap
是否会很好?
(注意:我在这里可以互换使用sbrk
,brk
因为它们是同一个Linux系统调用接口brk
。)
作为参考,以下是一些描述glibc malloc的文档:
GNU C库参考手册: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堆。要将块映射到堆,您需要知道哪种情况适用。如果该位为0,则该块来自主区域和主堆。如果该位为1,则该块来自mmap的内存,并且可以从该块的地址计算出堆的位置。
[Glibc malloc源自ptmalloc,而ptmalloc则源自dlmalloc,后者始于1987年。]
该jemalloc手册页(http://jemalloc.net/jemalloc.3.html)有这样一段话:
传统上,分配器使用sbrk(2)获取内存,由于一些原因,该内存不是最佳的,原因包括竞争条件,增加的碎片以及人为限制最大可用内存。如果操作系统支持sbrk(2),则此分配器将按优先顺序使用mmap(2)和sbrk(2);否则,此分配器将使用mmap(2)和sbrk(2)。否则,仅使用mmap(2)。
因此,他们甚至在这里说这sbrk
不是次优的,但是无论如何他们还是会使用它,即使他们已经为编写代码而烦恼,以至于没有它就可以工作。
[jemalloc的编写始于2005年。]
更新:更多地考虑这一点,关于“按优先顺序”的一点让我对询问保持了一致。为什么选择优先顺序?它们是否只是sbrk
在mmap
不支持(或缺少必要功能)的情况下用作备用,还是该进程可能进入可以使用sbrk
但不能使用的状态mmap
?我看一下他们的代码,看看是否能弄清楚它在做什么。
我之所以问是因为我正在用C实现垃圾回收系统,到目前为止,我看不到除之外的任何用途mmap
。我想知道是否还有什么我想念的。
(就我而言,我还有另一个避免使用的原因brk
,那就是malloc
在某些时候可能需要使用。)
mmap()
在 Unix 的早期版本中不存在。brk()
是当时增加进程数据段大小的唯一方法。Unix 的第一个版本mmap()
是SunOS
在 80 年代中期,第一个开源版本是 1990 年的 BSD-Reno。
并且可用于malloc()
您不想需要真实文件来备份内存。1988 年 SunOS/dev/zero
为此目的而实施,而在 1990 年代的 HP-UX 实施了该MAP_ANONYMOUS
标志。
现在有一些版本mmap()
提供了多种分配堆的方法。
系统调用brk()
的优点是只有一个数据项可以跟踪内存使用情况,而这很高兴也与堆的总大小直接相关。
自1975年Unix V6以来,它的形式完全相同。请注意,V6支持65,535字节的用户地址空间。因此,对于管理超过64K(肯定不是TB)的信息并没有太多想法。
使用mmap
直到我开始琢磨着如何改变或增加,垃圾收集器可以使用似乎是合理的mmap但没有太重写分配算法。
请问这项工作很好地与realloc()
,fork()
等?
明显的优势是您可以将最后的分配增加到位,这是您无法做到的mmap(2)
(mremap(2)
是Linux扩展,不可移植)。
对于天真的(不是那么天真的)程序,realloc(3)
例如使用。附加到字符串后,转换为1或2个数量级的速度提升;-)
小智 5
调用mmap(2)
每个存储器分配一次不是一个通用存储器分配器的可行方法,因为分配粒度(其可以在一个时间被分配的最小个体单元),用于mmap(2)
为PAGESIZE
(通常是4096个字节),并且因为它需要一个缓慢而复杂的系统调用. 具有低碎片的小分配的分配器快速路径应该不需要系统调用。
所以不管你使用什么策略,你仍然需要支持多个glibc所说的内存arenas,GNU手册提到:“多个arenas的存在允许多个线程同时在不同的arenas中分配内存,从而提高性能。”
jemalloc 联机帮助页 ( 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,则该块来自主 arena 和主堆。如果该位为 1,则该块来自 mmap 的内存,并且可以从该块的地址计算出堆的位置。
所以现在的问题是,如果我们已经在使用mmap(2)
,为什么不在进程开始时分配一个 arenammap(2)
而不是使用sbrk(2)
?尤其是,如引用的那样,有必要跟踪使用了哪种分配类型。有几个原因:
mmap(2)
可能不支持。sbrk(2)
已经为进程初始化,而mmap(2)
会引入额外的要求。mmap(2)
不能那么容易地扩展。Linux 有mremap(2)
,但它的使用将分配器限制为支持它的内核。预映射许多具有PROT_NONE
访问权限的页面会使用过多的虚拟内存。使用MMAP_FIXED
取消映射任何可能在没有警告的情况下之前存在的映射。sbrk(2)
没有这些问题,并且明确设计为允许安全地扩展其内存。小智 5
我不知道 Linux 上的具体细节,但在 FreeBSD 上几年来,mmap 是首选,并且 FreeBSD 的 libc 中的 jemalloc 已完全禁用 sbrk() 。brk()/sbrk() 未在 aarch64 和 risc-v 的较新端口上的内核中实现。
如果我正确理解 jemalloc 的历史,它最初是 FreeBSD libc 中的新分配器,然后才被突破并变得可移植。现在FreeBSD是jemalloc的下游消费者。它对 mmap() 而不是 sbrk() 的偏好很可能源于 FreeBSD VM 系统的特性,该系统是围绕实现 mmap 接口而构建的。
值得注意的是,在 SUS 和 POSIX 中,brk/sbrk 已被弃用,此时应被视为不可移植。如果您正在开发新的分配器,您可能不想依赖它们。