为什么 malloc() 交替调用 mmap() 和 brk()?

Why does malloc() call mmap() and brk() interchangeably?

我是 C 和堆内存的新手,仍在努力理解动态内存分配。

我跟踪了Linux个系统调用,发现如果我使用malloc请求少量堆内存,那么malloc会在内部调用brk

但是如果我使用malloc请求大量堆内存,那么malloc会在内部调用mmap

所以brkmmap肯定有很大的区别,但是理论上我们应该可以使用brk来分配堆内存而不考虑请求的大小。那么为什么分配大量内存时malloc会调用mmap呢?

mmap(与 MAP_ANONYMOUS 一起使用时)分配一块 RAM,可以将其放置在进程虚拟地址 space 内的任何位置,并且可以稍后释放(与 munmap) 独立于所有其他分配。

brk 更改虚拟地址的单个连续“竞技场”的结束地址 space:如果增加此地址,它会为竞技场分配更多内存,如果减少,它在竞技场的尽头释放内存。因此,用brk分配的内存只有在进程不再需要arena末端的连续地址范围时才能释放回操作系统。

对小分配使用brk,对大分配使用mmap,是一种基于小分配更可能具有相同生命周期的假设的启发式,而大分配更可能可能具有与任何其他分配的生命周期无关的生命周期。因此,大分配使用允许它们独立于其他任何东西被释放的系统原语,而小分配使用不这样做的原语。

这种启发式方法不是很可靠。当前一代的 malloc 实现,如果我没记错的话,已经完全放弃了 brk 并使用 mmap 来处理所有事情。我怀疑您正在查看的 malloc 实现(GNU C 库中的实现,基于您的标记)非常古老并且主要继续使用,因为没有人敢于冒险将其换成更新的东西可能但不会肯定更好。

那么为什么malloc在分配大内存的时候会调用mmap呢?

简短的回答是 为了提高效率 Linux 的较新实现,以及它们附带的更新内存分配算法。但请记住,这是一个非常依赖于实现的主题,原因和原因会因所讨论的特定 Linux OS 的不同年份和口味而有很大差异。

Here is fairly recent write-up regarding the low-level parts mmap() and brk() play in Linux memory allocation. And, a not so recent, but still relevant Linux Journal 文章,其中包含一些非常 on-point 与此处主题相关的内容,包括:

For very large requests, malloc() uses the mmap() system call to find addressable memory space. This process helps reduce the negative effects of memory fragmentation when large blocks of memory are freed but locked by smaller, more recently allocated blocks lying between them and the end of the allocated space. In this case, in fact, had the block been allocated with brk(), it would have remained unusable by the system even if the process freed it.
(emphasis mine)

关于brk()
incidentally, "...mmap() 在 Unix 的早期版本中不存在。brk() 是增加进程数据段大小的唯一方法那个时候。带有 mmap() 的第一个 Unix 版本是 80 年代中期的 SunOS,第一个 open-source 版本是 1990 年的 BSD-Reno.”。从那时起,内存分配算法的现代实现已经通过许多改进进行了重构,大大减少了对它们使用 brk().

的需求。

brk() 是 UNIX 中分配内存的一种传统方式——它只是将数据区域扩展给定的数量。 mmap() 允许您分配独立的内存区域,而不受限于单个连续的虚拟地址块 space。

malloc() 将数据 space 用于“小”分配,将 mmap() 用于“大”分配,原因有很多,包括减少内存碎片。这只是一个您不必担心的实现细节。

请同时检查此

我想强调另一个观点。

malloc 是分配内存的系统函数。

你真的不需要调试它,因为在某些实现中,它可能会给你来自静态“arena”的内存(例如静态字符数组)。

在其他一些实现中,它可能只是 return 空指针。

如果你想看看锦葵到底是干什么的,建议你看看
http://gee.cs.oswego.edu/dl/html/malloc.html

Linux gcc malloc就是基于这个

你也可以看看jemalloc。它基本上使用相同的 brk 和 mmap,但组织数据的方式不同,通常“更好”。

研究愉快。

减少碎片通常被认为是 mmap 用于大型分配的原因;有关详细信息,请参阅 。但我认为这不是当今真正的好处;实际上,即使 mmap 仍然存在碎片,只是在更大的池中(虚拟地址 space,而不是堆)。

mmap 的一大优势是可丢弃性。

当用sbrk分配内存时,如果内存实际被使用(以便内核在某个点映射物理内存),然后释放,内核本身无法知道,除非分配器还减少了程序中断(如果释放的块不是程序中断下最顶层的 previously-used 块,它就不能)。结果是,就内核而言,物理内存的内容变得“宝贵”;如果它需要 re-purpose 那个物理内存,那么它必须确保它不会丢失它的内容。因此它最终可能会换出页面(这很昂贵),即使拥有进程不再关心它们。

当使用 mmap 分配内存时,释放内存不只是 return 块到某处的池;相应的虚拟内存分配 returned 到内核,这告诉内核不再需要任何相应的物理内存,无论是脏内存还是其他内存。然后内核可以 re-purpose 该物理内存而不用担心它的内容。

我认为的关键部分,我从chat Peter

说的复制过来的

free() is a user-space function, not a system call. It either hands them back to the OS with munmap or brk, or keeps them dirty in user-space. If it doesn't make a system call, the OS must preserve the contents of those pages as part of the process state.

所以当你用brk增加你的内存地址时,return回来的时候,你要用brk一个负值,所以brk只能return你最近分配的内存块,当您调用 malloc(huge)、malloc(small)、free(huge) 时。 huge不能return返回系统,你只能为这个进程维护一个碎片列表,所以huge实际上被这个进程持有。这是brk的缺点。

但是mmap和munmap可以避免这种情况。