Beej's Guide to Unix IPC — Chapter 10

Memory Mapped Files

Map a file directly into your address space — read and write with pointer arithmetic instead of read() and write().

Prerequisites: Shared Memory (Ch. 9) + open() / file descriptors. That's it.
9
Chapters
3+
Simulations
0
Assumed Knowledge

Chapter 0: Why Memory Mapped Files?

You've already seen shared memory segments in Chapter 9. They're fast — zero-copy, direct pointer access. But they live in the kernel's memory, disconnected from the filesystem. When the system reboots (or you forget to clean up), the data is gone.

What if you could get the same pointer-based access, but backed by a real file on disk? That's exactly what memory-mapped files give you. You call mmap(), and the OS maps a section of a file directly into your process's virtual address space. From that point on, reading from the pointer reads from the file. Writing to the pointer writes to the file. No read(), no write(), no fseek().

The mental model: Imagine the file is a long scroll of paper. Normally you'd read it by asking a librarian (the kernel) to copy a page for you. With mmap, the librarian just unfurls the scroll on your desk. You can look at any part of it directly — point at byte 47, read byte 1000. And if two people share the same desk, they see the same scroll.
mmap vs read/write

Compare traditional I/O (two copies through the kernel) with memory-mapped access (direct pointer).

Mode: traditional read/write
What advantage does mmap() have over read()/write() for file access?

Chapter 1: open() — Getting the File Descriptor

Before you can map a file to memory, you need a file descriptor. That means calling open() the usual way:

c
#include <fcntl.h>

int fd;
fd = open("mapdemofile", O_RDWR);

The mode you pass to open() matters — it must match what you'll tell mmap() later. If you open the file read-only (O_RDONLY), you can only map it with PROT_READ. If you want to write through the mapping, open with O_RDWR.

Think of it this way: The file descriptor is your ticket to access the file. The open() mode stamps that ticket: "read-only" or "read-write." When you hand the ticket to mmap(), the permissions must match — you can't get write access with a read-only ticket.
open() ModeAllowed mmap() Protection
O_RDONLYPROT_READ only
O_WRONLYNot usable with mmap (needs read access)
O_RDWRPROT_READ | PROT_WRITE
Key insight: You need O_RDWR (not O_WRONLY) for a writable mmap. The OS needs to read the file contents into the page table, so read access is always required under the hood.
Why must the open() mode match the mmap() protection flags?

Chapter 2: mmap() — The Star of the Show

Here it is — the system call that turns a file into a pointer:

c
#include <sys/mman.h>

void *mmap(void *addr, size_t len, int prot,
           int flags, int fildes, off_t off);

Six parameters. Don't panic. Let's walk through each one in plain English.

ParameterWhat It DoesTypical Value
addrWhere in memory to place the mappingNULL (let the OS choose)
lenHow many bytes to mapFile size or page-aligned length
protRead/write/execute permissionsPROT_READ or PROT_READ|PROT_WRITE
flagsShared or private mappingMAP_SHARED or MAP_PRIVATE
fildesFile descriptor from open()Your fd
offOffset in the file to start mapping from0 (start of file) or page-aligned value
c
int fd, pagesize;
char *data;

fd = open("foo", O_RDONLY);
pagesize = getpagesize();
data = mmap(NULL, pagesize, PROT_READ, MAP_SHARED, fd, pagesize);

This maps one page of the file foo, starting at the second page. The returned pointer data now points directly at that section of the file. data[0] is the first byte of the second page.

Critical detail: mmap() returns MAP_FAILED (which is (void *)(-1)) on error — not NULL. You must check for (void *)(-1), just like shmat() in Chapter 9.
What does the off parameter to mmap() specify?

Chapter 3: Key Parameters in Detail

Let's zoom in on the three parameters that trip people up: prot, flags, and off.

prot — Protection

This controls what you can do with the mapped memory. Bitwise-OR the values you need:

FlagMeaning
PROT_READPages can be read
PROT_WRITEPages can be written
PROT_EXECPages can be executed
PROT_NONEPages cannot be accessed at all

If you set PROT_READ and then try to write (data[0] = 'B'), you'll get a segmentation fault. The hardware MMU enforces this.

flags — Shared vs Private

MAP_SHARED
Changes are written back to the file. Other processes mapping the same file see your writes. This is the IPC mode.
↓ vs
MAP_PRIVATE
Copy-on-write. Your changes are private. The file on disk is never modified. Other processes don't see your writes.
Key insight: MAP_SHARED is what makes mmap useful for IPC. Without it, each process gets its own copy and changes don't propagate. If you want two processes to share data through a file, MAP_SHARED is mandatory.

off — Page Alignment

The offset must be a multiple of the system's virtual memory page size. You can get this value with getpagesize(). Typical values:

SystemTypical Page Size
x86 Linux4,096 bytes (4K)
ARM (4K granule)4,096 bytes (4K)
ARM (64K granule)65,536 bytes (64K)

If len is not a multiple of the page size, the OS rounds up. The extra bytes beyond the file's actual content are zero-filled, and any changes to them are not written back to the file.

What happens if you map a file with MAP_PRIVATE and modify the data?

Chapter 4: munmap() — Unmapping the File

When you're done with the mapping, call munmap() to release it:

c
int munmap(void *addr, size_t len);
ArgumentPurpose
addrThe pointer returned by mmap()
lenThe same length you passed to mmap()
c
/* Map the file */
data = mmap(NULL, file_size, PROT_READ, MAP_SHARED, fd, 0);

/* ... use data[0], data[1], etc. ... */

/* Unmap when done */
munmap(data, file_size);
After munmap(): Any access through the old pointer causes a segmentation fault. The pointer is dangling, just like after shmdt() in shared memory. Set it to NULL after unmapping to be safe.

A few important details:

Compare with shared memory: In Ch. 9, you had two steps: shmdt() (detach) and shmctl(IPC_RMID) (delete). With mmap, there's just munmap(). The file continues to exist on disk after unmapping — no manual cleanup needed.
What happens if you access the pointer after calling munmap()?

Chapter 5: Concurrency, Again?!

Just like shared memory segments, memory-mapped files have no built-in synchronization. If two processes map the same file with MAP_SHARED and both write to overlapping regions simultaneously, you get a data race.

Same problem, same solutions: Use file locking (fcntl(), Ch. 6) or semaphores (Ch. 8) to protect concurrent access to mapped regions. The fact that it's a file doesn't magically make it thread-safe.

The good news: since mmap is backed by a real file, you can use fcntl() file locking directly — which is arguably more natural than System V semaphores:

c
struct flock fl;
fl.l_type = F_WRLCK;    /* write lock */
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;          /* lock entire file */

fcntl(fd, F_SETLKW, &fl);  /* acquire lock (blocking) */

/* write to mapped memory safely */
data[0] = 'X';

fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl);   /* release lock */
Shared Memory (Ch. 9)
Sync with System V semaphores (semop)
↓ vs
Memory-Mapped Files (Ch. 10)
Sync with file locks (fcntl) or semaphores
Key insight: Memory-mapped files give you the speed of shared memory with the persistence and familiar locking model of files. You don't need ftok() keys or ipcrm cleanup. The file IS the shared resource.
How can you protect concurrent access to a memory-mapped file?

Chapter 6: mmapdemo.c — The Full Program

Beej's demo program maps its own source file to memory, then prints the byte at whatever offset you specify on the command line. It uses stat() to get the file size so it knows how much to map.

c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    int fd, offset;
    char *data;
    struct stat sbuf;

    if (argc != 2) {
        fprintf(stderr, "usage: mmapdemo offset\n");
        exit(1);
    }

    if ((fd = open("mmapdemo.c", O_RDONLY)) == -1) {
        perror("open");
        exit(1);
    }

    if (stat("mmapdemo.c", &sbuf) == -1) {
        perror("stat");
        exit(1);
    }

    offset = atoi(argv[1]);
    if (offset < 0 || offset > sbuf.st_size - 1) {
        fprintf(stderr, "offset must be 0-%ld\n",
                sbuf.st_size - 1);
        exit(1);
    }

    data = mmap(NULL, sbuf.st_size, PROT_READ,
               MAP_SHARED, fd, 0);
    if (data == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    printf("byte at offset %d is '%c'\n",
           offset, data[offset]);

    munmap(data, sbuf.st_size);
    close(fd);
    return 0;
}
Try it: Compile and run mmapdemo 30. It maps its own source code and prints the character at byte 30. Change the offset to explore different parts of the file.
Step 1: open()
Get a file descriptor for "mmapdemo.c"
Step 2: stat()
Get the file size (sbuf.st_size)
Step 3: mmap()
Map the entire file into memory → returns pointer
Step 4: data[offset]
Access any byte via pointer arithmetic
Step 5: munmap()
Release the mapping
Why does mmapdemo call stat() before mmap()?

Chapter 7: Memory Mapping Simulator

Watch a file being mapped into a process's virtual address space. Click offsets to read bytes, or write to see changes appear in both the file and the mapping.

mmap() Visualization

The top row is the file on disk. The bottom row is the memory mapping. Clicking "Map" links them. Writes to the mapping appear in the file (MAP_SHARED).

File: unmapped
MAP_SHARED vs MAP_PRIVATE

Two processes map the same file. Toggle the flag to see how writes propagate (or don't).

Flag: MAP_SHARED
When a file is mapped with MAP_SHARED and a process writes to the mapping, what happens?

Chapter 8: Beyond Memory Mapped Files

Memory-mapped files sit at the intersection of shared memory and file I/O. They give you pointer-based access (like shared memory) with file-backed persistence (like regular I/O). On systems that don't support System V shared memory, mmap is often the only zero-copy option.

FeatureShared Memory (Ch. 9)Memory-Mapped Files (Ch. 10)
Backing storeKernel memoryFile on disk
PersistenceUntil ipcrm or rebootFile persists on disk
Setupftok() + shmget() + shmat()open() + mmap()
Cleanupshmdt() + shmctl(IPC_RMID)munmap() (file remains)
Sync toolSemaphoresFile locks or semaphores
PortabilitySystem V onlyPOSIX (widely available)
Key/IDftok() keyFilename (easier!)
Coming up: We've covered every one-way and shared-data IPC mechanism. The final chapter covers Unix domain sockets — full-duplex, bidirectional communication between processes on the same machine.
When to use mmap: Use mmap when you need fast, pointer-based access to file data, when you want file-backed persistence, or when you need shared memory but don't want System V IPC's complexity. It's the modern, POSIX-friendly alternative.

"Memory mapped files can be very useful, especially on systems that don't support shared memory segments. In fact, the two are very similar in most respects." — Beej

What is the key advantage of mmap over System V shared memory?