How does Mapping Memory With Mmap work in Linux-Free Embedded Linux Course

PREV_LEC NEXT_LEC
Mapping Memory With Mmap
Part of the free Linux kernel development course — Managing Memory chapter
Chapter 11
Lecture 6
Driver + Userspace

Mapping Memory With Mmap On Linux

mmap() is one of the most versatile system calls in Linux, and understanding it properly is essential for anyone serious about this free linux kernel development course. It is how a process gets its code and data mapped in the first place, how large allocations bypass the heap, how two unrelated processes share memory without copying, and how a device driver hands raw hardware memory straight to an application. This lecture covers all four uses, with original working examples for each.

mmap anonymous mapping shared memory driver mmap fop free linux device drivers course

What You Will Learn

  • What mmap() actually does at the page-table level
  • How a process’s memory map is built up from mappings, not just “the heap” and “the stack”
  • How to use anonymous mmap for large private allocations
  • How to share memory between two unrelated processes with shm_open() and mmap()
  • How to implement an mmap file operation in your own character device driver

Prerequisites

This lecture assumes you understand pages and virtual addressing from the earlier lectures in this free embedded systems course, and that you can already write and load a basic character device driver. If you have not written a driver with open/read/write file operations yet, do that first — this lecture only adds the mmap operation on top.

The Mmap System Call

Every process starts life with a handful of mappings already in place: the executable’s code and data segments, and the shared libraries it links against. All of that is set up by the kernel using the same underlying mechanism a program can invoke directly:

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);

length bytes are mapped starting at offset in the file behind descriptor fd, and the kernel returns a pointer to the mapping. Because the MMU works in whole pages, length is always rounded up to a page boundary internally. prot is a bitwise combination of PROT_READ, PROT_WRITE, and PROT_EXEC; flags must include either MAP_SHARED (changes are written back and visible to other mappers) or MAP_PRIVATE (copy-on-write, changes stay local to this process).

A Process’s Memory Map Is A Set Of Mappings
0x0000… low addresses +———————–+ | .text (file-backed) | code segment, executable’s mapping +———————–+ | .data / .bss | private, anonymous after copy-on-write +———————–+ | heap (brk / malloc) | grows via brk() or small mmap chunks +———————–+ | mmap area | shared libs, large mallocs, explicit mmap() +———————–+ | stack | grows downward +———————–+ 0xFFFF… high addresses

Using Mmap For Large Private Allocations

Set MAP_ANONYMOUS and pass fd = -1 to get a block of private memory with no file behind it — the same kind of memory malloc() hands you, except page-aligned and always a whole number of pages. This is what glibc’s allocator itself does internally once a request grows past a threshold (128 KiB on many glibc builds), precisely because large anonymous mappings do not fragment the heap the way many small brk() extensions can.

#include <sys/mman.h>
#include <stdio.h>

int main(void)
{
    size_t len = 4 * 1024 * 1024; /* 4 MiB */
    void *block = mmap(NULL, len, PROT_READ | PROT_WRITE,
                        MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (block == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    /* zero-filled on first touch, ordinary memory from here on */
    memset(block, 0xAB, len);
    printf("mapped %zu bytes at %p\n", len, block);

    munmap(block, len);
    return 0;
}
$ gcc anon_mmap.c -o anon_mmap
$ ./anon_mmap
mapped 4194304 bytes at 0x7f2c4b1e0000

In most application code, plain malloc() is still the right call — the C library already decides when to use mmap internally. Reach for explicit anonymous mmap when you specifically need page alignment, want to control placement, or want MAP_SHARED anonymous memory that a fork()ed child inherits access to without going through a named object.

Sharing Memory Between Processes

POSIX shared memory uses shm_open() to get a file descriptor for a named memory object, then mmap() with MAP_SHARED to actually get at it. Two unrelated processes that open the same name see the same physical pages — writes from one are visible to the other immediately, with no copying and no IPC call in the fast path.

#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    int fd = shm_open("/ep_shared_counter", O_CREAT | O_RDWR, 0600);
    ftruncate(fd, 4096);

    int *counter = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                         MAP_SHARED, fd, 0);

    (*counter)++;              /* visible to every other mapper instantly */
    return 0;
}

Run this twice concurrently (with a small sleep between increment and exit so you can inspect it, or add a semaphore for real synchronization) and both instances are working on the exact same physical page — no pipe, socket, or message queue involved.

Mapping Device Memory From A Driver

The fourth use is the one that matters most for driver authors: letting a device node be mmap’d so an application can touch device memory directly, with no per-byte copy through read()/write(). The driver supplies an mmap file operation, and inside it typically calls remap_pfn_range() to wire the physical pages into the calling process’s page tables.

Here is a minimal original character driver, ep_mmapdrv, that allocates one page of kernel memory at probe time and lets userspace map it directly:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>

static void *ep_buf;

static int ep_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long size = vma->vm_end - vma->vm_start;

    if (size > PAGE_SIZE)
        return -EINVAL;

    return remap_pfn_range(vma, vma->vm_start,
                            virt_to_phys(ep_buf) >> PAGE_SHIFT,
                            size, vma->vm_page_prot);
}

static const struct file_operations ep_fops = {
    .owner = THIS_MODULE,
    .mmap  = ep_mmap,
};

static struct miscdevice ep_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "ep_mmapdrv",
    .fops  = &ep_fops,
};

static int __init ep_init(void)
{
    ep_buf = (void *)get_zeroed_page(GFP_KERNEL);
    if (!ep_buf)
        return -ENOMEM;
    return misc_register(&ep_dev);
}

static void __exit ep_exit(void)
{
    misc_deregister(&ep_dev);
    free_page((unsigned long)ep_buf);
}

module_init(ep_init);
module_exit(ep_exit);
MODULE_LICENSE("GPL");

From userspace, this device looks like any other mmap-able object:

int fd = open("/dev/ep_mmapdrv", O_RDWR);
unsigned char *p = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                         MAP_SHARED, fd, 0);
p[0] = 0x42;   /* written straight into the driver's page, no read()/write() */
$ sudo insmod ep_mmapdrv.ko
$ dmesg | tail -1
[  102.441823] misc ep_mmapdrv: ep_mmapdrv registered

Real hardware drivers follow the same shape — a frame buffer driver maps display memory this way so applications can write pixels directly, and a streaming capture driver maps a ring of DMA buffers so userspace can read frames without a copy. The mechanism is identical; only the physical address being remapped changes.

The Four Uses Of Mmap At A Glance

Use caseFlagsfdTypical caller
Private large allocationMAP_PRIVATE | MAP_ANONYMOUS-1malloc() internals, custom allocators
Shared memory between processesMAP_SHAREDfrom shm_open()IPC without message copying
File-backed mappingMAP_SHARED or MAP_PRIVATEopen() on a real fileLoaders, databases, memory-mapped I/O on files
Device memory mappingMAP_SHAREDopen() on a device nodeFrame buffers, DMA ring buffers, zero-copy drivers

Real-World Use Cases

Zero-copy video capture is the textbook embedded example: a V4L2-class driver allocates a set of DMA-capable buffers and lets the application mmap them directly, so incoming frames never pass through a userspace copy. Display drivers do the same thing in the other direction for frame buffers. Beyond drivers, memory-mapped files are how most database engines and dynamic linkers load large files efficiently — the kernel pages content in on demand instead of the application reading the whole file up front.

Common Mistakes And Troubleshooting

  • Forgetting MAP_FAILED is not NULL. A failed mmap() returns (void *)-1, not NULL — checking against the wrong value silently hides errors.
  • Mixing up MAP_SHARED and MAP_PRIVATE. Writes to a MAP_PRIVATE file mapping never reach the file or other mappers — a common source of “my changes disappeared” bugs.
  • Not rounding to PAGE_SIZE in a driver’s mmap fop. The VMA size handed to your driver is always page-aligned; validating it against your buffer size, as in the example above, prevents mapping past the end of allocated memory.
  • Using remap_pfn_range() on non-reserved, cacheable kernel memory without care. Always confirm the memory type and caching attributes you actually need before wiring pages into a user mapping.

Best Practices

  • Prefer malloc() for ordinary allocations; reach for explicit anonymous mmap only when you need page alignment, placement control, or shareability across fork().
  • Always check the actual mapped size a driver’s mmap fop receives against what you intend to expose — never trust the caller’s requested length blindly.
  • Use named POSIX shared memory (shm_open) for cooperating unrelated processes; use anonymous MAP_SHARED mappings only across a fork() where a name is unnecessary.

Performance And Security Considerations

Mmap-based zero-copy paths exist specifically for performance — avoiding a copy through the kernel buffer matters a great deal on high-throughput paths like video capture. Security-wise, a driver that exposes physical memory via mmap is handing userspace direct hardware access: always validate the requested size and offset, never let an unprivileged process map arbitrary physical addresses, and prefer dma_mmap_coherent()-style DMA-API helpers over raw remap_pfn_range() when the memory was allocated through the DMA API, since they handle cache coherency for you correctly.

Summary And Key Takeaways

  • mmap() is the single mechanism behind a process’s code/data mapping, large allocations, shared memory, and device memory access.
  • MAP_PRIVATE | MAP_ANONYMOUS gives page-aligned private memory with no backing file.
  • shm_open() plus MAP_SHARED gives two unrelated processes the same physical pages with zero copying.
  • A driver exposes device memory to userspace with an mmap file operation, typically built on remap_pfn_range() or the DMA API’s mmap helpers.

Conclusion

mmap() is one of those system calls that quietly does far more than most application developers realize, and for driver authors it is the standard way to deliver zero-copy performance. Once you can trace a mapping from the mmap() call, through the VMA, down to the physical pages a driver hands over with remap_pfn_range(), the rest of the kernel’s memory-management picture in this free linux kernel development course starts to click into place.

Frequently Asked Questions

What does mmap() return on failure?

(void *)-1, exposed as MAP_FAILED — never compare the return value against NULL.

What is the difference between MAP_SHARED and MAP_PRIVATE?

MAP_SHARED writes back to the underlying file/object and is visible to every other mapper; MAP_PRIVATE is copy-on-write and stays local to the calling process.

Why does malloc() sometimes use mmap internally?

Glibc’s allocator switches large allocations (over roughly 128 KiB by default) to anonymous mmap because such allocations do not fragment the heap the way repeated small brk() extensions can.

How does a device driver expose memory through mmap?

By implementing an mmap file operation that validates the requested VMA size and calls remap_pfn_range() (or a DMA-API mmap helper) to map the physical pages into the calling process.

Is mmap-based I/O always faster than read()/write()?

Not always — for small, infrequent transfers the syscall overhead of setting up a mapping can outweigh the benefit. It shines on large or repeated transfers where avoiding a copy matters, such as video frame capture.

Can I use mmap to share memory with a child process after fork()?

Yes — a MAP_SHARED anonymous mapping created before fork() is inherited and stays shared between parent and child, with no named object required.

JSON-LD FAQ Schema

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {"@type": "Question", "name": "What does mmap() return on failure?",
     "acceptedAnswer": {"@type": "Answer", "text": "(void *)-1, exposed as MAP_FAILED, never NULL."}},
    {"@type": "Question", "name": "What is the difference between MAP_SHARED and MAP_PRIVATE?",
     "acceptedAnswer": {"@type": "Answer", "text": "MAP_SHARED writes back and is visible to all mappers; MAP_PRIVATE is copy-on-write and stays local."}},
    {"@type": "Question", "name": "Why does malloc() sometimes use mmap internally?",
     "acceptedAnswer": {"@type": "Answer", "text": "Large allocations are switched to anonymous mmap to avoid heap fragmentation."}},
    {"@type": "Question", "name": "How does a device driver expose memory through mmap?",
     "acceptedAnswer": {"@type": "Answer", "text": "By implementing an mmap file operation that calls remap_pfn_range() or a DMA-API mmap helper."}},
    {"@type": "Question", "name": "Is mmap-based I/O always faster than read()/write()?",
     "acceptedAnswer": {"@type": "Answer", "text": "No, it helps most on large or repeated transfers where avoiding a copy matters."}},
    {"@type": "Question", "name": "Can I use mmap to share memory with a child process after fork()?",
     "acceptedAnswer": {"@type": "Answer", "text": "Yes, a MAP_SHARED anonymous mapping created before fork() stays shared with the child."}}
  ]
}

Continue The Free Linux Kernel Development Course

More hands-on Linux kernel, device driver, and embedded Linux lectures are on the way — all free, all on EmbeddedPathashala.

Browse The Full Course Next Lecture
PREV_LEC NEXT_LEC

1 Comment

Leave a Reply

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