mmap: File Mappings, Basics

Reading A File, Not Using File I/O

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>

using namespace std;


int main(int argc, char** argv)
{
    if (argc != 2) {
        cerr << "Usage: " << argv[0] << " FILENAME\n";
        return 1;
    }

    const char* filename = argv[1];
    int fd = open(filename, O_RDONLY);                 // <-- O_RDONLY
    if (fd == -1) {
        perror("open");
        return 1;
    }

    struct stat stat;
    int error = fstat(fd, &stat);                      // <-- determine file size (stat.st_size)
    if (error) {
        perror("fstat");
        return 1;
    }

    void* content = mmap(
        NULL,                                          // <-- (addr) let kernel choose address
        stat.st_size,                                  // <-- (length) extending from offset (0)
        PROT_READ,                                     // <-- (prot) memory access protection
        MAP_PRIVATE,                                   // <-- (flags) copy-on-write (read-only anyway)
        fd,                                            // <-- (fd) file mapping (as opposed to "anonymous")
        0                                              // <-- (offset) offset; map from beginning of file
    );
    if (content == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    close(fd);                                         // <-- abandon; mapping keeps a reference

    write(STDOUT_FILENO, content, stat.st_size);

    munmap(content, stat.st_size);                     // <-- (done anyway at exit)

    return 0;
}
  • Open file O_RDONLY

  • Create mapping

  • Close file (mapping already knows)

  • PROT_READ: memory protection read only

  • MAP_PRIVATE: private mapping

    • Pointless as we don’t write to it, but we have to say something

    • Copy on write otherwise ⟶ private to each address space (later)

  • offset and length span entire file

    • offset must start at a page boundary. 4096 on many systems; determined exactly by sysconf(_SC_PAGE_SIZE)

$ echo 0123456789 > /tmp/a-file
$ ./file-mapping-ro /tmp/a-file
0123456789

Writing: MAP_PRIVATE, And Copy-On-Write (COW)

  • Change PROT_READ to PROT_WRITE

  • Note how open() is left as-is - O_RDONLY

  • Touch one byte in mapping

  • Look with cat ⟶ no change

  • Copy-On-Write (COW)

  • ⟶ this is why this works on a O_RDONLY file

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>

using namespace std;


int main(int argc, char** argv)
{
    if (argc != 2) {
        cerr << "Usage: " << argv[0] << " FILENAME\n";
        return 1;
    }

    const char* filename = argv[1];
    int fd = open(filename, O_RDONLY);                 // <-- read-only!
    if (fd == -1) {
        perror("open");
        return 1;
    }

    struct stat stat;
    int error = fstat(fd, &stat);
    if (error) {
        perror("fstat");
        return 1;
    }

    void* content = mmap(
        NULL,
        stat.st_size,
        PROT_WRITE,                                    // <-- content is writeable
        MAP_PRIVATE,                                   // <-- and *private* -> COW
        fd,
        0
    );
    if (content == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    close(fd);

    ((char*)content)[0] = 'a';                         // <-- touch first couple bytes
    ((char*)content)[1] = 'b';
    ((char*)content)[2] = 'c';
    ((char*)content)[3] = 'd';

    munmap(content, stat.st_size);

    return 0;
}

Writing: MAP_SHARED - Make Changes Visible

  • Change MAP_PRIVATE to MAP_SHARED

  • Fails with EACCESS

  • ⟶ change O_RDONLY to O_WRONLY

  • Still fails

  • O_RDWR - mapping needs to be populated

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>

using namespace std;


int main(int argc, char** argv)
{
    if (argc != 2) {
        cerr << "Usage: " << argv[0] << " FILENAME\n";
        return 1;
    }

    const char* filename = argv[1];
    int fd = open(filename, O_RDWR);                 // <-- read and write
    if (fd == -1) {
        perror("open");
        return 1;
    }

    struct stat stat;
    int error = fstat(fd, &stat);
    if (error) {
        perror("fstat");
        return 1;
    }

    void* content = mmap(
        NULL,
        stat.st_size,
        PROT_WRITE,
        MAP_SHARED,                                    // <-- make changes visible to others
        fd,
        0
    );
    if (content == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    close(fd);

    ((char*)content)[0] = 'a';
    ((char*)content)[1] = 'b';
    ((char*)content)[2] = 'c';
    ((char*)content)[3] = 'd';

    munmap(content, stat.st_size);

    return 0;
}