The MAP_FIXED Flag Fixed-Address Memory Mappings

 

The MAP_FIXED Flag
Fixed-Address Memory Mappings | Chapter 49 | EmbeddedPathashala
๐Ÿ“ Fixed VA Address
๐Ÿ—‚๏ธ Contiguous File Mapping
โš ๏ธ Non-Portable

What is MAP_FIXED?

Normally when you call mmap(), you pass an address hint in the addr parameter. The kernel treats this as a suggestion and is free to place the mapping anywhere it likes. The actual address is returned by mmap().

When you specify MAP_FIXED, the kernel must place the mapping at exactly the address specified in addr. That address must be page-aligned. If any existing mapping occupies any part of the requested range, those pages are silently unmapped first.

MAP_FIXED is generally non-portable (different systems handle it differently) and dangerous if misused. However, there is one important portable use case: mapping multiple file regions into a contiguous address range.

Key Concepts

MAP_FIXED addr hint vs forced Page Alignment Contiguous Mapping Overlay Mapping Anonymous Placeholder File Region Mapping Virtual Address Space

addr Hint vs MAP_FIXED โ€” The Difference

Without MAP_FIXED (normal)
addr = 0x7f000000 /* hint */
return = 0x7f230000 /* kernel chose */

Kernel may ignore the hint. Safe: no existing mapping is destroyed.

With MAP_FIXED
addr = 0x7f000000 /* REQUIRED */
return = 0x7f000000 /* exact */

โš ๏ธ Any existing mapping at that range is silently removed first.

โš ๏ธ Why MAP_FIXED is Dangerous

If you blindly specify MAP_FIXED with an arbitrary address, you may overwrite critical regions of your process:

  • Your stack
  • Existing mmap() mappings (shared libraries, etc.)
  • The heap
  • Program text/data segments

Linux silently unmaps whatever was there before and places your new mapping at that address. There is no error. This causes hard-to-debug crashes.

/* DANGEROUS: overlaps unknown regions */
void *p = mmap((void *)0x400000, 4096,
               PROT_READ | PROT_WRITE,
               MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
               -1, 0);
/* This may have just unmapped part of your .text segment! */

Safe practice: If you need MAP_FIXED, always first create a large anonymous reservation to let the kernel choose a safe region, then overlay file mappings on top of that reservation.

The Portable Use Case: Contiguous Multi-File Mapping

This is the one scenario where a portable application should use MAP_FIXED. The goal: map several non-contiguous regions of a file (or multiple files) so they appear as a single contiguous block in memory.

Two-Step Portable Pattern

Step 1

Call mmap() with addr=NULL, no MAP_FIXED, anonymous mapping, total size = sum of all regions.

โ†’ Kernel picks a safe VA range for you

Step 2

For each file region, call mmap() with addr = base + offset and MAP_FIXED to overlay that region onto the reserved space.

โ†’ File regions appear contiguous in VA

Resulting Virtual Address Layout:
base
File A โ€” Region 1
(MAP_FIXED overlay)
base + sizeA
File A โ€” Region 2
(MAP_FIXED overlay)
base + sizeA + sizeB
File B โ€” Region 1
(MAP_FIXED overlay)
All regions appear contiguous even though they come from different file offsets

Example 1: Basic MAP_FIXED โ€” Replace an Existing Mapping
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

int main(void)
{
    size_t pgsz = getpagesize();

    /* Step 1: Create initial anonymous mapping โ€” let kernel choose address */
    char *base = mmap(NULL, 2 * pgsz,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS,
                      -1, 0);
    if (base == MAP_FAILED) { perror("mmap base"); return 1; }

    memset(base, 'A', 2 * pgsz);
    printf("Base mapping at %p, first byte = %c\n", base, base[0]);

    /* Step 2: Overlay the second page with a new anonymous mapping */
    char *overlay = mmap(base + pgsz, pgsz,
                         PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
                         -1, 0);
    if (overlay == MAP_FAILED) { perror("mmap overlay"); return 1; }

    /* overlay == base + pgsz guaranteed */
    memset(overlay, 'B', pgsz);

    printf("First page: %c (original)\n", base[0]);
    printf("Second page: %c (overlaid with MAP_FIXED)\n", base[pgsz]);

    munmap(base, 2 * pgsz);
    return 0;
}
Example 2: Map Two File Regions Contiguously (Safe Pattern)
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>

/*
 * Create a test file and map two separate regions of it
 * into a contiguous virtual address range.
 */
int main(void)
{
    size_t pgsz = getpagesize();
    const char *fname = "/tmp/test_mapfixed.bin";

    /* Create a 4-page test file */
    int fd = open(fname, O_CREAT | O_RDWR | O_TRUNC, 0600);
    if (fd == -1) { perror("open"); return 1; }
    ftruncate(fd, 4 * pgsz);

    /* Write known data to each page */
    char page_data[4096];
    for (int p = 0; p < 4; p++) {
        memset(page_data, 'A' + p, pgsz);
        pwrite(fd, page_data, pgsz, p * pgsz);
    }

    /*
     * Goal: Map page 0 and page 3 of the file into
     *       contiguous virtual addresses [base, base+2*pgsz)
     */

    /* Step 1: Reserve 2 pages of VA space anonymously โ€” safe address */
    char *base = mmap(NULL, 2 * pgsz,
                      PROT_NONE,            /* no access yet */
                      MAP_PRIVATE | MAP_ANONYMOUS,
                      -1, 0);
    if (base == MAP_FAILED) { perror("mmap reserve"); close(fd); return 1; }
    printf("Reserved VA range: %p - %p\n", base, base + 2 * pgsz);

    /* Step 2: Overlay first slot with file page 0 */
    char *slot0 = mmap(base, pgsz,
                       PROT_READ,
                       MAP_SHARED | MAP_FIXED,
                       fd, 0);           /* offset = 0 (page 0) */
    if (slot0 == MAP_FAILED) { perror("mmap slot0"); return 1; }

    /* Step 3: Overlay second slot with file page 3 */
    char *slot1 = mmap(base + pgsz, pgsz,
                       PROT_READ,
                       MAP_SHARED | MAP_FIXED,
                       fd, 3 * pgsz);   /* offset = page 3 */
    if (slot1 == MAP_FAILED) { perror("mmap slot1"); return 1; }

    printf("base[0]     = %c (from file page 0)\n", base[0]);
    printf("base[pgsz]  = %c (from file page 3)\n", base[pgsz]);

    /* The two non-contiguous file pages now appear contiguous in VA */
    printf("Pointer arithmetic: base[pgsz - 1]=%c, base[pgsz]=%c\n",
           base[pgsz - 1], base[pgsz]);

    munmap(base, 2 * pgsz);
    close(fd);
    unlink(fname);
    return 0;
}
Example 3: Map Multiple Files into Contiguous Memory
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>

#define N_FILES 3

int main(void)
{
    size_t pgsz = getpagesize();
    const char *filenames[N_FILES] = {
        "/tmp/mapfile_a.bin",
        "/tmp/mapfile_b.bin",
        "/tmp/mapfile_c.bin"
    };
    int fds[N_FILES];
    size_t sizes[N_FILES] = { pgsz, pgsz, pgsz };
    size_t total = 0;

    /* Create 3 test files, each 1 page, filled with distinct characters */
    for (int i = 0; i < N_FILES; i++) {
        fds[i] = open(filenames[i], O_CREAT | O_RDWR | O_TRUNC, 0600);
        if (fds[i] == -1) { perror("open"); return 1; }
        ftruncate(fds[i], pgsz);
        char buf[4096];
        memset(buf, 'X' + i, pgsz);
        write(fds[i], buf, pgsz);
        total += sizes[i];
    }

    /* Step 1: Reserve contiguous VA space for all files */
    char *base = mmap(NULL, total,
                      PROT_NONE,
                      MAP_PRIVATE | MAP_ANONYMOUS,
                      -1, 0);
    if (base == MAP_FAILED) { perror("reserve"); return 1; }

    /* Step 2: Overlay each file into its slot */
    size_t offset = 0;
    for (int i = 0; i < N_FILES; i++) {
        void *addr = mmap(base + offset, sizes[i],
                          PROT_READ,
                          MAP_SHARED | MAP_FIXED,
                          fds[i], 0);
        if (addr == MAP_FAILED) { perror("overlay"); return 1; }
        offset += sizes[i];
        close(fds[i]);
    }

    /* All three files now appear as one contiguous buffer */
    for (int i = 0; i < N_FILES; i++) {
        printf("File %d at base[%zu] = %c\n",
               i, i * pgsz, base[i * pgsz]);
    }

    munmap(base, total);

    for (int i = 0; i < N_FILES; i++) unlink(filenames[i]);
    return 0;
}
Example 4: Verifying Page Alignment Requirement
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(void)
{
    size_t pgsz = getpagesize();
    printf("Page size: %zu bytes\n", pgsz);

    /* Reserve a region first */
    char *base = mmap(NULL, 4 * pgsz,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS,
                      -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); return 1; }

    /* Try MAP_FIXED with a non-aligned address โ€” must fail */
    void *bad_addr = base + 100;   /* not page-aligned */
    void *p = mmap(bad_addr, pgsz,
                   PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
                   -1, 0);
    if (p == MAP_FAILED) {
        printf("MAP_FIXED with unaligned addr failed as expected: %s\n",
               strerror(errno));
    } else {
        printf("Unexpectedly succeeded at %p\n", p);
    }

    /* Try with aligned address โ€” should succeed */
    void *good_addr = base + pgsz;  /* page-aligned */
    p = mmap(good_addr, pgsz,
             PROT_READ | PROT_WRITE,
             MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
             -1, 0);
    if (p == MAP_FAILED) {
        printf("MAP_FIXED with aligned addr failed: %s\n", strerror(errno));
    } else {
        printf("MAP_FIXED with aligned addr succeeded at %p\n", p);
    }

    munmap(base, 4 * pgsz);
    return 0;
}

MAP_FIXED vs addr Hint โ€” Quick Reference
Property Without MAP_FIXED With MAP_FIXED
Address placement Kernel chooses (hint used) Exactly addr (mandatory)
Alignment requirement None (hint can be 0) Must be page-aligned
Existing mappings Not affected Silently unmapped
Portability Fully portable (POSIX) Non-portable (varies)
Safety Safe Risky if used carelessly
Portable use case General use Contiguous multi-region file mapping

Interview Questions & Answers

Q1. What does MAP_FIXED do in mmap()?

A: It forces the kernel to place the mapping at exactly the address specified in addr. Without MAP_FIXED, addr is just a hint and the kernel may choose a different address. With MAP_FIXED, the returned address will always equal addr. Any existing mapping at that VA range is silently unmapped first.

Q2. Why is MAP_FIXED dangerous?

A: If you specify an address that already holds a valid mapping (stack, heap, shared library, program text), that mapping is silently destroyed. There is no error or warning. Your process can immediately crash or behave unpredictably. The kernel does not protect you from this.

Q3. What is the safe way to use MAP_FIXED portably?

A: First call mmap() without MAP_FIXED to reserve a large anonymous region (letting the kernel choose a safe address). Then use MAP_FIXED to overlay file regions at specific offsets within that reserved region. This guarantees no pre-existing mapping is accidentally destroyed.

Q4. What happens to the bytes in an overlaid MAP_FIXED region?

A: The original anonymous pages are replaced by the new mapping. Any data in the original anonymous mapping at that range is lost. The new mapping’s contents are what you specified (file data, new anonymous zeroed pages, etc.).

Q5. Why would you map multiple file regions into a contiguous VA range?

A: To allow pointer arithmetic across regions without knowing boundaries. For example, combining a file header (first 4KB) with a data section (at a later offset) so you can walk through them as a single buffer. This simplifies code that processes non-contiguous file sections as if they were adjacent in memory.

Q6. Does MAP_FIXED require addr to be page-aligned?

A: Yes. MAP_FIXED requires addr to be a multiple of the system page size (getpagesize()). An unaligned address causes mmap() to fail with EINVAL.

Q7. What is the relationship between MAP_FIXED and MREMAP_FIXED?

A: They serve analogous purposes for different syscalls. MAP_FIXED forces mmap() to place a new mapping at a specific address. MREMAP_FIXED forces mremap() to move an existing mapping to a specific address. Both require the address to be page-aligned, and both silently unmap whatever was there.

Q8. Can MAP_FIXED overlap with the program’s stack or text segment?

A: Technically yes โ€” the kernel will unmap those regions and place your mapping there. In practice this immediately crashes the process. This is why you should never use MAP_FIXED with an arbitrary address unless you have reserved that VA range safely first.

Chapter 49 โ€” Complete Topic Summary
Topic Key Syscall / File Core Idea
mremap() mremap() Resize/relocate existing mapping without copying
MAP_NORESERVE mmap(MAP_NORESERVE), /proc/sys/vm/overcommit_* Lazy swap reservation; overcommit control
OOM Killer /proc/PID/oom_score, /proc/PID/oom_adj Kill processes to recover memory under OOM
MAP_FIXED mmap(MAP_FIXED) Force mapping at exact VA; safe use for contiguous file mapping

Chapter 49 โ€” Memory Mappings Series

mremap() MAP_NORESERVE OOM Killer

๐Ÿ“š Series Complete โ€” Chapter 49 Memory Mappings

Leave a Reply

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