Additional mmap() Flags Linux Memory Mappings

 

Additional mmap() Flags
Chapter 49.6 — Linux Memory Mappings — TLPI Deep Dive
8
mmap Flags
5+
Code Examples
10
Interview Q&A

Series Navigation:   You are here: 49.6 mmap Flags  |  Next: 49.7 Anonymous Mappings →

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

MAP_ANONYMOUS MAP_FIXED MAP_LOCKED MAP_HUGETLB MAP_NORESERVE MAP_POPULATE MAP_UNINITIALIZED mmap() mlock() page fault swap space huge pages

Quick Reminder: mmap() Signature

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

MAP_ANONYMOUS — No File Backing

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.

Key fact: 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);

MAP_FIXED — Force Exact Address

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.

Warning: If 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");
}

MAP_LOCKED — Pin Pages in RAM

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().

When to use: Real-time applications, cryptographic key storage, latency-critical code paths where you cannot afford a page fault delay. Requires 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);

MAP_HUGETLB — Huge Pages

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.

Normal Pages (4 KB)
512 TLB entries for 2 MB
Huge Page (2 MB)
2 MB
1 TLB entry for 2 MB
#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 */
}
Setup required: Before using huge pages, the admin must reserve them: echo 64 > /proc/sys/vm/nr_hugepages (reserves 64 huge pages = 128 MB).

MAP_POPULATE — Pre-fault All Pages

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.

Access Pattern Comparison

Without MAP_POPULATE
mmap() → returns fast
First access → PAGE FAULT → slow
Read from disk (I/O)

With MAP_POPULATE
mmap() → reads all pages
First access → NO page fault
Data served from RAM (fast)
#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);
Caveat: MAP_POPULATE increases mmap startup time. Under memory pressure, pages can still be swapped out later. It’s a hint, not a guarantee.

MAP_NORESERVE — Skip Swap Reservation

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.

Risk: If you actually write to those pages and no physical memory or swap is available, the kernel OOM (Out-Of-Memory) killer will terminate a process. This is called overcommit. Use 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 */

MAP_UNINITIALIZED — Skip Zeroing (Embedded Only)

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.

Security warning: The pages may contain leftover data from another process. This flag should only be used on dedicated embedded systems where the kernel is built with 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 */

Combining Multiple Flags — Practical Patterns

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

Q1. What is the difference between MAP_PRIVATE and MAP_SHARED?

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.

Q2. What happens if you use MAP_FIXED with an address that is already mapped?

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.

Q3. What is MAP_POPULATE useful for?

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.

Q4. Why does MAP_LOCKED require privileges?

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.

Q5. What is the security risk of MAP_UNINITIALIZED?

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.

Q6. What are huge pages and why do they matter for performance?

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.

Q7. What is the difference between MAP_POPULATE and madvise(MADV_WILLNEED)?

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.

Q8. What is overcommit and how does MAP_NORESERVE relate to it?

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.

Q9. Which mmap flags are standardized in POSIX/SUSv3?

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.

Q10. How do you check if a mmap() call succeeded?

You must compare the return value against MAP_FAILED, which is (void *) -1. Do not compare against NULLmmap() 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.

Next: Anonymous Mappings → EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *