Search code examples
carmmmapopensuse

mmap different behavior on intel (i7) and arm?


I use the a program compiled on Opensuse 13.1 with intel i7 processor. I compiled the same program in qemu (virtual) environment to simulate an OpenSuse 13.1 with arm processor. This line of code:

rvp = mmap(rvp, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fildes, 0);

gives me a pointer to memory in Ram. Yet the size of this memory differs between intel and arm:

on intel the size is "length", independent of the size of the file pointed to by fildes.

On ARM the size is (approximately) the size of the file pointed to by fildes, and ignores the fact that size is bigger than fildes.

I want to have more memory allocated than just the file...

EDIT: I tried to circumvent this problem with two successive calls... without success:

    rvp = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    unsigned int i;
    for (i = 0; i < length; i += 5000)
            printf("acces buffer at %i --> %u\n", i, rvp[i]);
    rvp = mmap(rvp, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fildes, 0);
    for (i = 0; i < length; i += 5000)
            printf("acces buffer at %i --> %u\n", i, rvp[i]);

as output I get:

access buffer at 0 --> 0
...
access buffer at 90000 -> 0
access buffer at 0 --> 0
...
access buffer at 85000 --> 0
Segmentation fault...

Solution

  • The official specification of mmap is not terribly clear on this point, but please note the sentence:

    [Memory] references[,] within the address range starting at pa and continuing for len bytes[,] to whole pages following the end of an object shall result in delivery of a SIGBUS signal.

    I've stuck in some commas and added emphasis to make the point clearer: if you mmap a region larger than the file that backs it, the OS is supposed to fire a signal if you try to access beyond the end of the file, provided you go past a page boundary (this license is simply because the hardware doesn't allow the OS to set a boundary between accessible and inaccessible memory that doesn't fall on a page boundary) (SIGBUS and SIGSEGV are treated as interchangeable by many OSes nowadays).

    You were on the right track with your "two successive calls" approach, but it didn't work because you used the same length for both calls. If you had instead specified the size of the file in the second call, it would have worked. You can retrieve the actual size of the file with the system call fstat. You need to take a little care in case the file is larger than your desired allocation length. Here's how I would write that:

    char *map_file(int fd, size_t len)
    {
        struct stat st;
        char *rv;
    
        if (fstat(fd, &st))
            return report_error("fstat");
    
        if (st.st_size >= (off_t) len) {
            /* If the file is at least as big as expected, just map the
               chunk we want. */
            rv = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
            if (rv == MAP_FAILED)
                return report_error("mmap");
    
        } else {
            /* Otherwise, we must allocate anonymous memory to fill in the gap. */
            char *anon;
            anon = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
            if (anon == MAP_FAILED)
                return report_error("mmap (anon)");
    
            rv = mmap(anon, (size_t) st.st_size, PROT_READ|PROT_WRITE,
                      MAP_PRIVATE|MAP_FIXED, fd, 0);
            if (rv == MAP_FAILED) {
                int save_errno = errno;
                (void) munmap(anon, len);
                errno = save_errno;
                return report_error("mmap");
            }
        }
        return rv;
    }
    

    Depending on your larger goal, it may instead make sense to enlarge the file if it's not as big as expected, using ftruncate:

    char *map_file(int fd, size_t len)
    {
        struct stat st;
        char *rv;
    
        if (fstat(fd, &st))
            return report_error("fstat");
        /* if the file isn't as big as expected, make it bigger */
        if (st.st_size < (off_t) len)
            if (ftruncate(fd, (off_t) len))
                return report_error("ftruncate");
    
        rv = mmap(0, len, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
        if (rv == MAP_FAILED)
            return report_error("mmap");
        return rv;
    }
    

    But probably, if that was what you wanted in context, you would be using MAP_SHARED instead of MAP_PRIVATE.

    (N.B. The function report_error, not shown, logs an error message and then returns NULL.)