【问题标题】:Is merging pages allowed in mmap?mmap 中是否允许合并页面?
【发布时间】:2020-03-02 11:11:04
【问题描述】:

简而言之,我想调整内存大小,但将旧内存放在新内存的中间。

所以我所做的是使用mmap 作为初始大小(p1),mmapp1 之前的地址处假装我使内存更大,然后将新指针视为我创建它使用单个 mmap (p3, mremap)。该代码似乎有效,但我不确定这是否是我应该做的。如果不是,我应该如何创建更多内存并将旧/当前内存放在其中?

#include <sys/mman.h>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <cerrno>
int main()
{
    auto p1 = (char*) mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (!p1 || p1==(char*)-1)
        return -1;

    auto p1_32 = (int *)p1;
    memset(p1, 0, 4096);
    p1_32[5] = 1035;

    auto p2 = mmap(p1-4096, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
    if (!p2 || p2==(void*)-1)
        return -2;

    auto p2_32 = (int *)p2;
    memset(p2, 0, 4096);
    p2_32[5] = 5301;

    assert(p2_32[1024+5] == 1035);

    auto p3 = mremap(p2, 4096*2, 4096*4, MREMAP_MAYMOVE);
    if (p3==(char*)-1)
    {
        printf("Errno %d\n", errno);
        return -2;
    }
    auto p3_32 = (int*)p3;

    assert(p3_32[5] == 5301);
    assert(p3_32[1024+5] == 1035);

    printf("Is this correct?\n");
    return 0;
}

【问题讨论】:

  • 几点注意事项:1) 你不必将mmap 返回的内存归零——它已经被归零了(否则它可能会从另一个进程泄漏敏感信息)。 2) 我认为你应该用linux 标记它,因为mremap 在某些操作系统上不可用。
  • @StaceyGirl 但是memset() 调用将强制将物理内存实际分配给虚拟地址。在实际访问内存之前不会创建这些映射,如果由于内核必须实际创建内存页面而导致时间关键处理停止,延迟这些映射可能会导致严重的性能问题。
  • @Ctx 在这种情况下,规范要求内存用零填充(如果手册页可以称为规范)。不过嵌入式系统有MAP_UNINITIALIZED
  • @AndrewHenle 我认为在这种情况下这并不重要。如果需要避免页面错误,可以使用 MAP_POPULATE 专门为此 - 这样做会更快。
  • @AndrewHenle MAP_ANONYMOUS.

标签: c mmap


【解决方案1】:

here所述

munmap() 函数将删除这些整个页面的任何映射 包含进程的地址空间的任何部分,从 addr 并继续 len 字节。

因此允许通过单个 munmap 调用删除多个映射(就好像它是单个映射一样)。

您的代码存在问题:您如何知道您的页面 (p1) 之前的页面 (p2) 是否未被使用?它可能已经被程序的其他部分分配(包括malloc),通过像这样使用MAP_FIXED,您将重写(重新映射)其内容:

MAP_FIXED 设置在 flags 参数中时,实现是 告知 pa 的值应该是addr,准确地说。如果MAP_FIXED 是 设置,mmap() 可能返回MAP_FAILED 并将errno 设置为[EINVAL]。如果一个 MAP_FIXED请求成功,mmap()建立的映射 替换范围内进程页面的任何先前映射 [pa,pa+len).

所以我认为这个技巧在一般情况下没有用,你应该改用mremap

至于这是如何实现的:Linux 确实合并了顺序私有匿名映射,因此两者将在内核中合并为一个 vma_struct。此“功能”具有不良副作用,例如munmap failing to free memory with ENOMEM。但这更多的是一个实现细节,而不是你可以控制的。

【讨论】:

  • 我认为我必须编写更多逻辑来确保 p1 和 p2 连接。我将如何使用 mremap 实现这一点?我看不到指定旧内容应该在哪里的方法。
  • 我有另一个想法如何实现这一点。我可以使用mremap 将其移动到更大的位置,然后立即在其内部重新映射它+ mmap 之前的空间。虽然我不确定 mremap-ping 本身是否合法
  • @EricStotch 您可以尝试不使用MAP_FIXED 的特定地址,这将起到提示作用:只有在没有适当映射的情况下,内核才会将数据映射到那里。 mremap with address 会做同样的事情 - 它不适用于您的情况。您可以尝试使用文件映射,但您可以使用memfd_create 返回的伪文件来代替文件 - 即使在取消映射和重新映射后,此类文件也允许您保留数据。
猜你喜欢
  • 1970-01-01
  • 2014-10-14
  • 2015-12-01
  • 2010-11-02
  • 2012-01-03
  • 2014-04-24
  • 1970-01-01
  • 1970-01-01
  • 2013-08-22
相关资源
最近更新 更多