Search code examples
c++linuxmmap

mmap() cannot allocate memory when repeatedly mapping and unmapping one single page


I have read many SO (and other) questions, but I couldn't find one that helped me. I want to mmap two files at once and copy their content byte-by-byte (I know this seems ridiculous, but this is my minimal reproducibly example). Therefore I loop through every byte, copy it, and after the size of one page in my files, I munmap the current page and mmap the next page. Imo there should only ever be one page (4096 bytes) of each file be needed so there shouldn't be any memory problem.

Also, if the output file is too small, the memory is allocated via posix_fallocate, which runs fine. I a lack of memory space in the hard drive can't be the problem either imo.

But as soon as I am going for a bit larger files with ~140 MB, I get the cannot allocate memory error from the output-file that I am writing into. Do you guys have any idea how this is?

#include <sys/types.h>
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
#include <bitset>
#include <fcntl.h>
#include <sys/stat.h>
#include <math.h>
#include <errno.h>

using namespace std;

int main()
{

    char file_input[] = "medium_big_file";
    char file_output[] = "foo_output";
    int fd_input = -1;
    int fd_output = -1;
    unsigned char *map_page_input, *map_page_output;
    struct stat stat_input, stat_output;

    if ((fd_input = open(file_input, O_RDONLY)) == -1 ||
          (fd_output = open(file_output, O_RDWR|O_CREAT, 0644)) == -1) {
            cerr << "Error on open()" << endl;
            return EXIT_FAILURE;
    }

    // get file size via stat()
    stat(file_input, &stat_input);
    stat(file_output, &stat_output);
    const size_t size_input = stat_input.st_size;
    const size_t size_output = stat_output.st_size;

    const size_t pagesize = getpagesize();

    size_t page = 0;
    size_t pos = pagesize;

    if (size_output < size_input) {
      if (posix_fallocate(fd_output, 0, size_input) != 0) {
        cerr << "file space allocation didn't work" << endl;
        return EXIT_FAILURE;
      }
    }

    while(pos + (pagesize * (page-1)) < size_input) {
      // check if input needs the next page
      if (pos == pagesize) {
        munmap(&map_page_input, pagesize);
        map_page_input = (unsigned char*)mmap(NULL, pagesize, PROT_READ,
          MAP_FILE|MAP_PRIVATE, fd_input, page * pagesize);
        munmap(&map_page_output, pagesize);
        map_page_output = (unsigned char*)mmap(NULL, pagesize,
          PROT_READ|PROT_WRITE, MAP_SHARED, fd_output, page * pagesize);
        page += 1;
        pos = 0;
        if (map_page_output == MAP_FAILED) {
      cerr << "errno: " << strerror(errno) << endl;
          cerr << "mmap failed on page " << page << endl;
          return EXIT_FAILURE;
        }
      }

      memcpy(&map_page_output[pos], &map_page_input[pos], 1);

      pos += 1;
    }

    munmap(&map_page_input, pagesize);
    munmap(&map_page_output, pagesize);


    close(fd_input);
    close(fd_output);
    return EXIT_SUCCESS;
}

Solution

  • The very first iteration of the loop attempts to unmap something that was never mapped, and passes a completely uninitialized pointer to munmap. Not once, but twice.

    Finally, munmap expects a pointer to the mmap-ed memory, and not a pointer to a pointer to the mmap-ed memory.

    The shown code fails to check the return status from munmap. If it did, it would've discovered that every call to munmap fails (hopefully, but if the first call happens to pass an aligned pointer, a chunk of the stack might end up being unmapped, with the ensuing hilarity), so the shown code just keeps allocating more, and more pages, and running out of memory.

    You must fix both bugs.