【问题标题】:Why does malloc() call mmap() and brk() interchangeably?为什么 malloc() 可以互换调用 mmap() 和 brk()?
【发布时间】:2021-01-09 17:30:25
【问题描述】:

我是 C 和堆内存的新手,仍然难以理解动态内存分配。

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

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

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

【问题讨论】:

    标签: c linux linux-kernel heap-memory dynamic-memory-allocation


    【解决方案1】:

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

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

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

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

    【讨论】:

    • brk vs mmap 与寿命无关。只是,如果分配大小不受限制(不同的策略有不同的最坏情况),brk 的任何可能实现的碎片都可能变得任意糟糕。 mmap 仍然仅在分配大到足以证明一次给它整页时才使用。 brk 没有被“删除”,只是被有效地内联并变得更加复杂。
    • @Ext3h:确切地说:假设当小分配被释放时,它们可以被放置在用户空间的空闲列表中,以使未来的分配更快。如果没有办法将它们交还给操作系统,那很好。 (如果一个程序分配了许多兆字节的小对象,然后将它们全部释放,除了最后的一些小分配,这个假设就会失效,留下一堆脏数据。)
    【解决方案2】:

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

    简短的回答是为了提高效率在较新的 Linux 实现上,以及随之而来的更新的内存分配算法。但请记住,这是一个非常依赖于实现的主题,并且讨论的特定 Linux 操作系统的不同年份和风格会导致原因和原因会有很大差异。

    Here is fairly recent write-up关于低级部分mmap()brk()在Linux内存分配中发挥作用。而且,一篇不是最近但仍然相关的Linux Journal 文章,其中包含一些与此处的主题非常相关的内容,包括:

    对于非常大的请求,malloc() 使用 mmap() 系统调用来查找 可寻址的内存空间。 这个过程有助于减少负面影响 释放大块内存时内存碎片的影响 但被较小的、最近分配的块锁定在它们之间 它们和分配空间的末尾。在这种情况下,事实上, 该块是用 brk() 分配的,它会一直不可用 即使进程释放了它,系统也会被系统释放。
    (强调我的)

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

    【讨论】:

    • 评论不用于扩展讨论;这个对话是moved to chat
    • 请注意任何访问此帖子的人,尤其是我的回答,我建议您单击上一条评论中的聊天链接以获得一些非常好的评论,其中大部分都集中在碎片化主题上。跨度>
    【解决方案3】:

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

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

    也请查看question

    【讨论】:

      【解决方案4】:

      我想强调另一个观点。

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

      您实际上不需要调试它,因为在某些实现中,它可能会从静态“竞技场”(例如静态 char 数组)中为您提供内存。

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

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

      Linux gcc malloc 就是基于此。

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

      研究愉快。

      【讨论】:

      • 对于有关 glibc malloc 的设计选择的问题,这几乎不是一个有用的答案。必须有人设计和编写 glibc 的 malloc。
      • 我认为这个答案提出了另一个重要的观点。问题的作者似乎在做与我 4-5 年前完全相同的研究。我相信我的回答对他很有用,即使它会收集一些反对票。
      【解决方案5】:

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

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

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

      当使用mmap 分配内存时,释放内存不只是将块返回到某个地方的池中;相应的虚拟内存分配被返回给内核,这告诉内核任何相应的物理内存,无论是脏的还是其他的,都不再需要。然后内核可以重新使用该物理内存,而无需担心其内容。

      【讨论】:

      • 为什么在使用sbrk 分配内存时内核没有得到通知?当调用free(ptr)时,我们传递块的起始虚拟地址,内核应该知道就像使用mmap分配块一样?
      • 因为释放在堆中分配的块(在程序中断下)根本不涉及内核,除非程序中断也减少了。在一般情况下,内核不知道分配器在做什么。
      【解决方案6】:

      我认为的关键部分原因,我从Peter所说的chat复制过来

      free() 是用户空间函数,而不是系统调用。它要么使用 munmap 或 brk 将它们交还给操作系统,要么将它们保持在用户空间中。如果它不进行系统调用,则操作系统必须将这些页面的内容保留为进程状态的一部分。

      所以当你使用 brk 增加你的内存地址时,返回时你必须使用 brk 一个负值,所以 brk 只能返回你最近分配的内存块,当你调用 malloc(huge), malloc (小),免费(大)。 huge 无法返回系统,只能为这个进程维护一个碎片列表,所以这个 huge 实际上是由这个进程持有的。这是 brk 的缺点。

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

      【讨论】:

        猜你喜欢
        • 2015-08-13
        • 2019-09-10
        • 1970-01-01
        • 1970-01-01
        • 2017-04-06
        • 2023-02-22
        • 2019-04-07
        • 1970-01-01
        • 2011-10-15
        相关资源
        最近更新 更多