mmap() stands for memory map. It asks the kernel to map either a file or a block of anonymous memory into the calling process’s virtual address space. After the mapping is done, you can read and write that memory region just like a normal array — the kernel handles moving data between RAM and the file automatically.
This is fundamentally different from read()/write(): with those system calls, data is copied between user space and a kernel buffer. With mmap(), the file pages are mapped directly — there is no extra copy.
#include <sys/mman.h>
void *mmap(void *addr, /* Suggested start address (usually NULL) */
size_t length, /* Length of mapping in bytes */
int prot, /* Memory protection flags */
int flags, /* Mapping type and options */
int fd, /* File descriptor (-1 for anonymous) */
off_t offset); /* Offset in file (must be page-aligned) */
/* Returns: starting address of mapping on success, MAP_FAILED on error */
| Stack (grows ↓) |
| mmap() region ← mapped here |
| Heap (grows ↑) |
| BSS / Data segment |
| Text segment (code) |
The kernel chooses a free region in the process’s virtual address space (between heap and stack) and places the mapping there. You can suggest an address via the first argument, but normally you pass NULL and let the kernel decide.
| Flag | Meaning |
PROT_READ |
Pages can be read |
PROT_WRITE |
Pages can be written |
PROT_EXEC |
Pages can be executed (used for JIT code) |
PROT_NONE |
Pages cannot be accessed at all (guard pages) |
| Flag | Description |
MAP_SHARED |
Mapping is shared; changes visible to other processes and written back to file |
MAP_PRIVATE |
Private copy-on-write mapping; changes not visible to others, not written to file |
MAP_ANONYMOUS |
Not backed by a file; fd should be -1; memory is zero-initialized |
MAP_FIXED |
Force mapping exactly at addr; use with caution |
MAP_NORESERVE |
Do not reserve swap space for this mapping |
Instead of calling read(), map the file and access it like an array:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int fd;
struct stat sb;
char *addr;
if (argc != 2) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Open the file read-only */
fd = open(argv[1], O_RDONLY);
if (fd == -1) { perror("open"); exit(EXIT_FAILURE); }
/* Get file size */
if (fstat(fd, &sb) == -1) { perror("fstat"); exit(EXIT_FAILURE); }
/* Map entire file into memory: read-only, private */
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd); /* fd can be closed after mmap — mapping stays valid */
/* Access file contents directly through pointer */
fwrite(addr, 1, sb.st_size, stdout);
/* Unmap when done */
if (munmap(addr, sb.st_size) == -1) { perror("munmap"); exit(EXIT_FAILURE); }
return 0;
}
Anonymous mappings are not backed by a file. They are used to allocate zeroed memory (similar to malloc() but at page granularity).
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
int main(void) {
size_t size = 4096 * 4; /* 4 pages = 16 KB */
/* Allocate anonymous private mapping */
char *buf = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, /* fd = -1 for anonymous */
0); /* offset = 0 */
if (buf == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
/* Memory is zero-initialized by the kernel */
printf("First byte: %d\n", buf[0]); /* prints 0 */
/* Use it like normal memory */
strcpy(buf, "Hello from anonymous mapping!");
printf("%s\n", buf);
munmap(buf, size);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
int main(void) {
int fd;
char *addr;
const char *msg = "Written via mmap!";
size_t len = 64;
/* Create / open a file */
fd = open("testfile.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("open"); exit(EXIT_FAILURE); }
/* Set file size — mapping length must not exceed file size */
if (ftruncate(fd, len) == -1) { perror("ftruncate"); exit(EXIT_FAILURE); }
/* Map file: shared so writes go to the file */
addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd);
/* Write directly into the mapping = writes into the file */
strncpy(addr, msg, len);
munmap(addr, len);
printf("Done. Check testfile.txt\n");
return 0;
}
#include <sys/mman.h>
int munmap(void *addr, size_t length);
/* Returns 0 on success, -1 on error */
After calling munmap(), accessing the unmapped region causes SIGSEGV. When a process terminates, all its mappings are automatically unmapped. You should always unmap explicitly in long-running programs to avoid virtual address space exhaustion.
This is a classic interview exercise (TLPI Exercise 49-1):
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[]) {
int src_fd, dst_fd;
struct stat sb;
char *src, *dst;
if (argc != 3) {
fprintf(stderr, "Usage: %s <src> <dst>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Open source */
src_fd = open(argv[1], O_RDONLY);
if (src_fd == -1) { perror("open src"); exit(EXIT_FAILURE); }
if (fstat(src_fd, &sb) == -1) { perror("fstat"); exit(EXIT_FAILURE); }
/* Open/create destination */
dst_fd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, sb.st_mode);
if (dst_fd == -1) { perror("open dst"); exit(EXIT_FAILURE); }
/* Set destination file size */
if (ftruncate(dst_fd, sb.st_size) == -1) { perror("ftruncate"); exit(EXIT_FAILURE); }
/* Map source file (read-only) */
src = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
if (src == MAP_FAILED) { perror("mmap src"); exit(EXIT_FAILURE); }
/* Map destination file (read-write shared) */
dst = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, dst_fd, 0);
if (dst == MAP_FAILED) { perror("mmap dst"); exit(EXIT_FAILURE); }
close(src_fd);
close(dst_fd);
/* Copy using memcpy — no read/write system calls */
memcpy(dst, src, sb.st_size);
munmap(src, sb.st_size);
munmap(dst, sb.st_size);
printf("Copied %ld bytes\n", (long)sb.st_size);
return 0;
}
- The
offsetargument must be a multiple of the system page size (sysconf(_SC_PAGESIZE)). - The
lengthneed not be page-aligned, but the kernel rounds it up to the page boundary internally. - Closing
fdaftermmap()does not destroy the mapping — the file is reference-counted internally. - For
MAP_SHAREDfile mappings, the file must be opened with at least read permission; for write access, with write permission. - Mapping a region beyond the end of a file and then accessing it generates
SIGBUS.
A: read()/write() copy data between user space buffers and the kernel page cache — that is an extra copy. mmap() maps the kernel page cache directly into the process’s address space, so there is no intermediate copy. This makes mmap() faster for large sequential or random-access file I/O.
A: The offset is the byte position in the file where the mapping starts. It must be a multiple of the page size because the kernel works at page granularity — memory protection, page faults, and TLB entries are all page-sized. A non-aligned offset would require the kernel to map a partial page, which is not possible in hardware.
A: Yes. Once mmap() returns, the kernel holds its own reference to the underlying file (via the inode and page cache). Closing the file descriptor does not destroy or invalidate the mapping. This is a common pattern to reduce the number of open file descriptors.
A: An anonymous mapping is a memory region not backed by any file. The bytes are initialized to zero by the kernel. It is used for: allocating private process memory (what malloc() uses internally for large allocations), sharing memory between parent and child after fork(), and stack extensions. Pass MAP_ANONYMOUS and fd = -1.
A: The kernel maps the file and zero-fills the remainder of the final page. However, if you access pages beyond the end of the last page of the file, the kernel delivers SIGBUS, because those pages have no corresponding file content. Use ftruncate() to extend the file before mapping if you need write access beyond its current size.
A: MAP_FIXED forces the kernel to map the region exactly at the address given by the addr argument. If any existing mapping overlaps that range, it is silently replaced. This is dangerous because it can accidentally overwrite the stack, text segment, or libc mappings. A safer use is: first create a large placeholder mapping to reserve a contiguous virtual address range, then use MAP_FIXED within that range.
