Map a file directly into your address space — read and write with pointer arithmetic instead of read() and write().
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().
Compare traditional I/O (two copies through the kernel) with memory-mapped access (direct pointer).
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.
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() Mode | Allowed mmap() Protection |
|---|---|
O_RDONLY | PROT_READ only |
O_WRONLY | Not usable with mmap (needs read access) |
O_RDWR | PROT_READ | PROT_WRITE |
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.
| Parameter | What It Does | Typical Value |
|---|---|---|
addr | Where in memory to place the mapping | NULL (let the OS choose) |
len | How many bytes to map | File size or page-aligned length |
prot | Read/write/execute permissions | PROT_READ or PROT_READ|PROT_WRITE |
flags | Shared or private mapping | MAP_SHARED or MAP_PRIVATE |
fildes | File descriptor from open() | Your fd |
off | Offset in the file to start mapping from | 0 (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.
MAP_FAILED (which is (void *)(-1)) on error — not NULL. You must check for (void *)(-1), just like shmat() in Chapter 9.off parameter to mmap() specify?Let's zoom in on the three parameters that trip people up: prot, flags, and off.
This controls what you can do with the mapped memory. Bitwise-OR the values you need:
| Flag | Meaning |
|---|---|
PROT_READ | Pages can be read |
PROT_WRITE | Pages can be written |
PROT_EXEC | Pages can be executed |
PROT_NONE | Pages 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.
The offset must be a multiple of the system's virtual memory page size. You can get this value with getpagesize(). Typical values:
| System | Typical Page Size |
|---|---|
| x86 Linux | 4,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.
When you're done with the mapping, call munmap() to release it:
c int munmap(void *addr, size_t len);
| Argument | Purpose |
|---|---|
addr | The pointer returned by mmap() |
len | The 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);
A few important details:
munmap() returns -1 on error and sets errnomsync()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.
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 */
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; }
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.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.
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).
Two processes map the same file. Toggle the flag to see how writes propagate (or don't).
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.
| Feature | Shared Memory (Ch. 9) | Memory-Mapped Files (Ch. 10) |
|---|---|---|
| Backing store | Kernel memory | File on disk |
| Persistence | Until ipcrm or reboot | File persists on disk |
| Setup | ftok() + shmget() + shmat() | open() + mmap() |
| Cleanup | shmdt() + shmctl(IPC_RMID) | munmap() (file remains) |
| Sync tool | Semaphores | File locks or semaphores |
| Portability | System V only | POSIX (widely available) |
| Key/ID | ftok() key | Filename (easier!) |
"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