Search code examples
cmmap

Is merging pages allowed in mmap?


In short I want to resize memory but have the old memory in the middle of the new.

So what I did was use mmap for the initial size (p1), mmap at an address before p1 to pretend I made the memory bigger, then treat the new pointer as if I created it with a single mmap (p3, mremap). The code seems to work but I'm not sure if this is what I should be doing. If it isn't how should I create more memory and have the old/current memory be in the middle of it?

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

Solution

  • As described 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.

    so it is allowed to remove multiple mappings with a single munmap call (as if it was a single mapping).

    There is a problem with your code though: how do you know if a page (p2) before your page (p1) is not used? It could be already allocated by other pars of the program (including malloc), by using MAP_FIXED like this you will rewrite (remap) its content:

    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).

    So I don't think this trick can be useful in the general case, you should use mremap instead.

    As for how this is implemented: Linux does merge sequential private anonymous mappings, so both will be merged into a single vma_struct in the kernel. This "feature" has undesirable side effects such as munmap failing to free memory with ENOMEM. But this is more of an implementation detail, not something you have control over.