What are mmap() Flags?
When you call mmap() to create a memory mapping, you pass a flags argument that controls how the mapping behaves. You already know about MAP_PRIVATE and MAP_SHARED — the two fundamental flags. But Linux supports several more flags that give you fine-grained control over locking, swap space, huge pages, and more.
Think of flags like switches on a control panel. Each switch turns on a specific behavior. You combine them using the bitwise OR operator (|).
Key Terms to Know
Before diving into flags, remember the full mmap() prototype:
#include <sys/mman.h>
void *mmap(
void *addr, /* Suggested start address (usually NULL) */
size_t length, /* How many bytes to map */
int prot, /* PROT_READ | PROT_WRITE | PROT_EXEC | PROT_NONE */
int flags, /* MAP_PRIVATE | MAP_SHARED | ... other flags ... */
int fd, /* File descriptor (-1 for anonymous) */
off_t offset /* Offset into file (0 for anonymous) */
);
/* Returns: starting address of mapping on success, MAP_FAILED on error */
The flags argument is where you combine MAP_PRIVATE or MAP_SHARED with any additional flags described in this section using |.
The Complete Flags Table
Linux defines the following flags for mmap(). Only MAP_PRIVATE, MAP_SHARED, and MAP_FIXED are standardized in SUSv3 (POSIX). Everything else is Linux-specific.
| Flag | Description | Linux Version | SUSv3 |
|---|---|---|---|
MAP_ANONYMOUS |
Create a mapping not backed by any file | All versions | No |
MAP_FIXED |
Force mapping to exactly the address given in addr |
All versions | Yes ✓ |
MAP_LOCKED |
Preload and lock pages into RAM (like mlock) | Linux 2.6+ | No |
MAP_HUGETLB |
Use huge pages (2 MB or 1 GB) instead of 4 KB pages | Linux 2.6.32+ | No |
MAP_NORESERVE |
Do not reserve swap space for this mapping in advance | All versions | No |
MAP_PRIVATE |
Write modifications are private (Copy-on-Write) | All versions | Yes ✓ |
MAP_POPULATE |
Pre-populate mapping pages (read-ahead for file mappings) | Linux 2.6+ | No |
MAP_SHARED |
Modifications visible to other processes; propagated to file | All versions | Yes ✓ |
MAP_UNINITIALIZED |
Skip zeroing pages of anonymous mapping (embedded systems only) | Linux 2.6.33+ | No |
Each Flag Explained
Normally, mmap() maps a file into memory. MAP_ANONYMOUS tells the kernel: “I don’t want to map a file. Just give me fresh memory pages.” The resulting memory is initialized to zero. When using this flag, pass fd = -1 for portability.
MAP_ANONYMOUS comes from BSD. The equivalent /dev/zero technique comes from System V. Both do the same thing. MAP_ANON is a synonym for MAP_ANONYMOUS on Linux./* Using MAP_ANONYMOUS (BSD style) */
#define _BSD_SOURCE /* Required to expose MAP_ANONYMOUS */
#include <sys/mman.h>
void *addr = mmap(
NULL, /* Let kernel choose address */
4096, /* 1 page of memory */
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, /* No file descriptor */
0 /* Offset ignored */
);
if (addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
/* Memory at addr is zeroed and ready to use */
munmap(addr, 4096);
Without MAP_FIXED, the addr argument is just a hint — the kernel may place the mapping anywhere it likes. With MAP_FIXED, the kernel must place the mapping at exactly the address you specify.
MAP_FIXED is used and that address range already has a mapping, the old mapping is silently destroyed. Use with extreme care. Portable applications should avoid MAP_FIXED./* MAP_FIXED usage — dangerous if address is wrong */
#include <sys/mman.h>
#include <stdint.h>
/* First, map something to get a valid page-aligned address */
void *base = mmap(NULL, 8192, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
/* Now force a second mapping at exactly base + 4096 */
void *fixed_addr = mmap(
(void *)((uintptr_t)base + 4096), /* Exact address */
4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-1, 0
);
if (fixed_addr == MAP_FAILED) {
perror("MAP_FIXED mmap failed");
}
Normally the OS can swap out memory pages to disk when RAM is tight. MAP_LOCKED prevents this — it preloads and locks the mapping’s pages into physical RAM, similar to calling mlock() after mmap().
CAP_IPC_LOCK privilege (or sufficient RLIMIT_MEMLOCK).#include <sys/mman.h>
/* Allocate locked memory — will not be swapped out */
void *addr = mmap(
NULL,
4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED,
-1, 0
);
if (addr == MAP_FAILED) {
perror("mmap with MAP_LOCKED");
/* May fail if no CAP_IPC_LOCK and limit exceeded */
}
/* Alternative: mmap then mlock */
void *addr2 = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
if (mlock(addr2, 4096) == -1) {
perror("mlock");
}
/* Always unlock before unmapping */
munlock(addr2, 4096);
munmap(addr2, 4096);
Normal pages are 4 KB. Huge pages are typically 2 MB (or even 1 GB on x86-64). Using huge pages reduces the number of TLB (Translation Lookaside Buffer) entries needed, dramatically improving performance for large memory workloads.
#define _GNU_SOURCE
#include <sys/mman.h>
/* Requires kernel huge page support and available huge pages */
/* Check: cat /proc/sys/vm/nr_hugepages */
void *addr = mmap(
NULL,
2 * 1024 * 1024, /* 2 MB — one huge page */
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0
);
if (addr == MAP_FAILED) {
perror("MAP_HUGETLB mmap");
/* Fails if no huge pages configured in system */
}
echo 64 > /proc/sys/vm/nr_hugepages (reserves 64 huge pages = 128 MB).Normally, when you mmap() a file, actual pages are loaded on-demand when you first access them — this is called a page fault. With MAP_POPULATE, the kernel eagerly reads all the pages into memory at mmap time, so later accesses happen without page faults.
#define _GNU_SOURCE
#include <sys/mman.h>
#include <fcntl.h>
int fd = open("large_file.bin", O_RDONLY);
if (fd == -1) { perror("open"); exit(1); }
/* MAP_POPULATE reads all pages at mmap time */
/* Good when you know you'll access all of the file soon */
void *addr = mmap(
NULL,
file_size,
PROT_READ,
MAP_PRIVATE | MAP_POPULATE, /* Pre-fault all pages */
fd, 0
);
close(fd); /* fd can be closed after mmap */
if (addr == MAP_FAILED) {
perror("mmap");
exit(1);
}
/* Now access the data — no page faults! */
process_data(addr, file_size);
munmap(addr, file_size);
MAP_POPULATE increases mmap startup time. Under memory pressure, pages can still be swapped out later. It’s a hint, not a guarantee.Normally, when you create a mapping, the kernel reserves swap space for it in advance so that the process is guaranteed physical backing if it writes to all pages. MAP_NORESERVE skips this reservation — the mapping is allowed even if there’s not enough swap space to back it.
MAP_NORESERVE only when you know most of the mapping will never be written to./* MAP_NORESERVE: useful for sparse large mappings */
void *addr = mmap(
NULL,
1024UL * 1024 * 1024, /* 1 GB virtual space */
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
-1, 0
);
/* Succeeds even if swap < 1 GB because no reservation made */
/* Only physically backed pages that are actually written matter */
By default, anonymous mappings are always zeroed by the kernel before being handed to your process (for security — so one process cannot read another’s data). MAP_UNINITIALIZED disables this zeroing, giving a small performance boost.
CONFIG_MMAP_ALLOW_UNINITIALIZED=y and the developer controls all software on the device./* Only works if kernel built with CONFIG_MMAP_ALLOW_UNINITIALIZED */
void *addr = mmap(
NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_UNINITIALIZED,
-1, 0
);
/* WARNING: memory content is undefined (may have stale data) */
/* Always initialize manually before reading if security matters */
memset(addr, 0, 4096); /* Explicit zero if needed */
Flags are combined with the | operator. Here are common real-world combinations:
/* 1. Simple private anonymous memory (most common) */
void *mem = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
/* 2. Shared memory between parent-child (no file needed) */
void *shared = mmap(NULL, sizeof(int),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
/* 3. Real-time locked memory (won't be swapped) */
void *rt_mem = mmap(NULL, 64 * 1024,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED,
-1, 0);
/* 4. Read-ahead file mapping (no page faults on access) */
int fd = open("data.bin", O_RDONLY);
void *file_data = mmap(NULL, file_size,
PROT_READ,
MAP_PRIVATE | MAP_POPULATE, /* Pre-fault all pages */
fd, 0);
close(fd);
/* 5. Huge page mapping for HPC workloads */
void *huge = mmap(NULL, 2 * 1024 * 1024,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
Interview Questions & Answers
MAP_PRIVATE uses Copy-on-Write (CoW) — when you write to a private mapping, only your process gets a private copy of the modified page. The underlying file (if any) is not changed. Other processes do not see your changes.
MAP_SHARED means writes go directly to the shared pages and are propagated to the underlying file (if any). All processes mapping the same file with MAP_SHARED see each other’s writes in real time.
The kernel silently unmaps whatever was previously at that address range and places the new mapping there. No error is returned. This is dangerous because it can destroy mappings you still need. SUSv3 warns that portable applications should avoid MAP_FIXED altogether.
When you know you will access all (or most) of a file-backed mapping soon after creating it, MAP_POPULATE tells the kernel to perform read-ahead at mmap time. This trades a higher upfront cost (reading all pages now) for zero page-fault latency during actual access. Useful for databases loading index files, media players buffering files, and any sequential-scan workload.
Locking pages in RAM prevents the OS from reclaiming those pages even under memory pressure. If any process could lock unlimited memory, the system could run out of swappable RAM. Linux limits locked memory via RLIMIT_MEMLOCK for unprivileged processes. The CAP_IPC_LOCK capability removes this limit.
Normally the kernel zeroes anonymous pages before giving them to a process so that process A cannot read process B’s old data. MAP_UNINITIALIZED skips this. If a page previously held passwords or cryptographic keys from another process, your process can read that sensitive data. This flag is only safe on embedded systems where a single application controls the entire device.
The CPU’s TLB (Translation Lookaside Buffer) caches virtual-to-physical page address translations. With 4 KB pages, a 2 MB region requires 512 TLB entries. With 2 MB huge pages, the same region needs only 1 entry. Fewer TLB misses means less time spent on address translation, which is critical for workloads like databases, HPC simulations, and JVM heaps that use gigabytes of memory.
Both trigger read-ahead. MAP_POPULATE is specified at mmap creation time and applies to the entire mapping. madvise(MADV_WILLNEED) can be called after mmap and can target specific ranges, giving you finer control over which parts of a large mapping to pre-load.
Overcommit means the system allows more virtual memory to be allocated than it can physically back (RAM + swap). MAP_NORESERVE opts into overcommit for a specific mapping by skipping swap reservation. If pages are later actually written and no physical memory is available, the OOM killer terminates processes. The kernel-wide overcommit policy is set via /proc/sys/vm/overcommit_memory.
Only three: MAP_PRIVATE, MAP_SHARED, and MAP_FIXED. All others (MAP_ANONYMOUS, MAP_LOCKED, MAP_HUGETLB, MAP_POPULATE, MAP_NORESERVE, MAP_UNINITIALIZED) are Linux-specific extensions and may not exist on other UNIX systems.
You must compare the return value against MAP_FAILED, which is (void *) -1. Do not compare against NULL — mmap() can legitimately return addresses near zero (though uncommon). On failure, errno is set.
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
/* CORRECT check */
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
/* WRONG check — do not do this */
if (addr == NULL) { /* BUG: MAP_FAILED is (void*)-1, not NULL */
...
}
Ready for the Next Topic?
Continue to Section 49.7 to learn about Anonymous Mappings in depth.
