7

So I have a region of memory that I have allocated with mmap() similar to the code below:

void * ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

The key here is that I'm using the MAP_SHARED flag. Unlike this related question where it was sufficient to simply call mmap() again to get MAP_PRIVATE and Copy-on-Write semantics, I can't have the kernel allocate me a different range of virtual addresses. In addition, I do not want to invoke munmap() and risk the kernel giving part/all of that address range to something else within the process before I can call mmap() again.

Is there an existing mechanism to switch a region of mmap'd memory from MAP_SHARED to MAP_PRIVATE to get copy-on-write semantics without unmapping the block?

Community
  • 1
  • 1
Phoenios
  • 146
  • 1
  • 5
  • 1
    Maybe `mmap(ptr, ...)` with a `MAP_FIXED | MAP_PRIVATE` flag would work? Not sure though (which is why I didn't put this in an answer). – Tim Čas Mar 14 '14 at 22:40
  • Interesting. The man page mentions something about overlapped parts of mappings (between ptr and len) being discarded. Not sure if it means the old or the new. Also, if the addr specified with MAP_FIXED can't be used the mmap just fails. – Phoenios Mar 15 '14 at 20:57
  • Well, one option is to unmap and then `mmap` again, this time with `MAP_FIXED` -- but then you get the problem that the kernel might give that range to something else in the process. Unless you could ensure that `munmap`/`mmap` calls are atomic. Why do you need the exact same address for the new `mmap` by the way? – Tim Čas Mar 16 '14 at 14:26
  • It's part of a larger project tinkering with QEMU/KVM. During the setup of the guest, it registers the block of memory as a RAMBlock, among other things. So basically QEMU expects that specific base address to be valid memory, and I can't risk changing it. – Phoenios Mar 16 '14 at 20:46

1 Answers1

1

Calling mmap() again with MAP_PRIVATE | MAP_FIXED will work. The MMAP(2) man page states that when using MAP_FIXED:

If the specified address cannot be used, mmap() will fail.

So, just use a temporary pointer to store the mmap() result. If mmap() fails, no harm done. If mmap() succeeds you have successfully switched a memory mapped region from MAP_SHARED to MAP_PRIVATE. (see example)

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd;
    void *shared_0, *shared_1;
    void *private_0;
    struct stat st;

    if((fd = open("filename", O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
        fprintf(stderr, "Failed to open(): %s\n", strerror(errno));
    }
    else if(fstat(fd, &st) < 0) {
        fprintf(stderr, "Failed fstat(): %s\n", strerror(errno));
    }
    else if((shared_0 = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, 0)) == MAP_FAILED) {
        fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
    }
    else if((shared_1 = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
            MAP_SHARED, fd, 0)) == MAP_FAILED) {
        fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
    }
    else if((private_0 = mmap(shared_0, st.st_size, PROT_READ | PROT_WRITE,
            MAP_FIXED | MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
        fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
    }
    else if(shared_0 != private_0) {
        fprintf(stderr, "Error: mmap() didn't map to the same region");
    }
    else {
        printf("shared_0: %p == private_0: %p\n", shared_0, private_0);
        printf("shared_1: %p\n", shared_1);

        printf("Shared mapping before write: %d\n", (*(char *)shared_1));
        printf("Private mapping before write: %d\n", (*(char *)private_0));

        /* write to the private COW mapping and sync changes */
        (*(char*)private_0) = 42;
        if(msync(private_0, 1, MS_SYNC | MS_INVALIDATE) < 0) {
            fprintf(stderr, "Failed msync(): %s\n", strerror(errno));
            return(1);
        }

        printf("Shared mapping after write: %d\n", (*(char *)shared_1));
        printf("Private mapping after write: %d\n", (*(char *)private_0));
    }

    return(0);
}
pughar
  • 148
  • 1
  • 6
  • According to POSIX.1-2008 the addresser are just overwritten, but what happens when you change the attribute from shared to private is a bit more murky. – Surt Oct 16 '15 at 16:19