mmap 允许合并页面吗?

Is merging pages allowed in mmap?

简而言之,我想调整内存大小,但旧内存位于新内存中间。

所以我所做的是使用 mmap 作为初始大小 (p1),在 p1 之前的地址使用 mmap 来假装我把内存变大了,然后将新指针视为我用单个 mmapp3mremap)创建的指针。该代码似乎有效,但我不确定这是否是我应该做的。如果不是,我应该如何创建更多内存并让 old/current 内存位于它的中间?

#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;
}

如所述here

The munmap() function shall remove any mappings for those entire pages containing any part of the address space of the process starting at addr and continuing for len bytes.

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

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

When MAP_FIXED is set in the flags argument, the implementation is informed that the value of pa shall be addr, exactly. If MAP_FIXED is set, mmap() may return MAP_FAILED and set errno to [EINVAL]. If a MAP_FIXED request is successful, the mapping established by mmap() replaces any previous mappings for the process' pages in the range [pa,pa+len).

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

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