带有私有匿名映射的ENOMEM的munmap()失败

时间:2017-05-02 17:06:05

标签: linux posix mmap memory-mapping enomem

我最近发现Linux不能保证用mmap分配的内存可以用munmap释放,如果这导致VMA(虚拟内存区域)结构的数量超过{{1}的情况}。 Manpage(几乎)清楚地说明了这一点:

vm.max_map_count

问题是Linux内核总是尝试合并VMA结构,即使对于单独创建的映射, ENOMEM The process's maximum number of mappings would have been exceeded. This error can also occur for munmap(), when unmapping a region in the middle of an existing mapping, since this results in two smaller mappings on either side of the region being unmapped. 也会失败。我能够编写一个小程序来确认这种行为:

munmap

它使用#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/mman.h> // value of vm.max_map_count #define VM_MAX_MAP_COUNT (65530) // number of vma for the empty process linked against libc - /proc/<id>/maps #define VMA_PREMAPPED (15) #define VMA_SIZE (4096) #define VMA_COUNT ((VM_MAX_MAP_COUNT - VMA_PREMAPPED) * 2) int main(void) { static void *vma[VMA_COUNT]; for (int i = 0; i < VMA_COUNT; i++) { vma[i] = mmap(0, VMA_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (vma[i] == MAP_FAILED) { printf("mmap() failed at %d\n", i); return 1; } } for (int i = 0; i < VMA_COUNT; i += 2) { if (munmap(vma[i], VMA_SIZE) != 0) { printf("munmap() failed at %d (%p): %m\n", i, vma[i]); } } } 分配大量页面(默认允许的最大值的两倍),然后每隔一页mmap分配一个页面,为每个剩余页面创建单独的VMA结构。在我的计算机上,上一次munmap通话始终以munmap启用。

最初我认为ENOMEM如果与用于创建映射的地址和大小的值相同,则永远不会失败。显然在Linux上并非如此,我无法在其他系统上找到有关类似行为的信息。

同时在我看来,应用于映射区域中间的部分取消映射预计会在任何操作系统上针对每个理智的实现失败,但我还没有发现任何文档说明这种失败是可能的。< / p>

我通常认为这是内核中的一个错误,但是知道Linux如何处理内存overcommit和OOM我几乎可以肯定这是一个&#34;功能&#34;存在以提高性能和减少内存消耗。

我能找到的其他信息:

  • Windows上类似的API没有这个&#34;功能&#34;由于他们的设计(请参阅munmapMapViewOfFileUnmapViewOfFileVirtualAlloc) - 他们根本不支持部分取消映射。
  • glibc VirtualFree实施不会创建超过malloc映射,在达到此限制时退回65535https://code.woboq.org/userspace/glibc/malloc/malloc.c.html。这看起来像是解决此问题的方法,但仍然可以使sbrk无声地泄漏内存。
  • jemalloc遇到了麻烦,并试图避免使用free / mmap因为这个问题(我不知道它是如何结束的)。

其他操作系统真的能保证内存映射的重新分配吗?我知道Windows会这样做,但是其他类Unix操作系统呢? FreeBSD的? QNX?

编辑:我正在添加一个示例,说明当内部munmap调用失败并free时,glibc munmap如何泄漏内存。使用ENOMEM查看strace失败:

munmap

1 个答案:

答案 0 :(得分:2)

在Linux上解决此问题的一种方法是同时mmap多1页(例如,每次1 MB),并在其后映射分隔页。因此,您实际上在257页内存上调用mmap,然后使用PROT_NONE重新映射最后一页,以便无法访问它。这应该会破坏内核中的VMA合并优化。由于您一次分配多个页面,因此不应该遇到最大映射限制。缺点是你必须手动管理你想要切割大mmap的方式。

关于你的问题:

  1. 由于各种原因,系统调用可能会在任何系统上失败。文档并不总是完整的。

  2. 只要传入的地址位于页面边界上,您就可以munmap mmap d区域的一部分,并且长度参数会向上舍入到下一个倍数的页面大小。