Linux syscalls - mmap / munmap
mmap() and munmap() are POSIX-standard system calls used to map files or device objects into a process's virtual memory address space.
Instead of copying data back and forth between kernel buffers and user-space memory using repeated read() and write() calls, mmap() creates a direct mapping between a region of virtual memory and a file (or anonymous memory). Once mapped, accessing memory addresses via pointers reads and writes data directly to/from the underlying backing store through the kernel's virtual memory manager (page cache).
mmap() Function
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
Parameters
-
addr: A hint for the starting address in virtual memory. PassingNULLlets the kernel select the optimal page-aligned address (recommended in almost all cases). -
length: The number of bytes to map (automatically rounded up to the system page size, e.g., 4096 bytes). -
prot: Memory protection flags (bitwise OR):PROT_READ: Pages may be read.PROT_WRITE: Pages may be written.PROT_EXEC: Pages may be executed.PROT_NONE: Pages may not be accessed.
-
flags: Controls the visibility and mapping type. Standard POSIX flags include:MAP_SHARED: Writes to the region are visible to other processes mapping the same region and are committed to the underlying file.MAP_PRIVATE: Writes create a private Copy-On-Write (COW) duplicate page. Changes are not visible to other processes and are not written to the underlying file.
-
fd: Open file descriptor pointing to the file being mapped. -
offset: Starting byte offset in the file (must be a multiple of the system page size).
Return Value
Returns a pointer to the mapped memory area on success, or MAP_FAILED ((void *) -1) on error (setting errno).
munmap() Function
#include <sys/mman.h>
int munmap(void *addr, size_t length);
Unmaps pages starting at address addr for length bytes. Subsequent accesses to the unmapped address range trigger a SIGSEGV (segmentation fault).
Main Use Cases in POSIX Programming
- Memory-Mapped File I/O: Reading or writing large files without buffer copies (
read/writesyscall overhead). - Shared Memory (IPC): Combining
MAP_SHAREDwithshm_open()or regular files to allow multiple processes to share zero-copy memory buffers. - Dynamic Memory Allocation: Heap allocators (such as
malloc) allocate large chunks of memory from the kernel using anonymousmmap()calls instead of moving the heap break pointer (brk/sbrk).
How mmap Differs in Linux vs. Pure POSIX
While Linux conforms to the POSIX specification for mmap() and munmap(), Linux extends mmap() with non-standard flags, kernel-level optimizations, and architecture-specific behaviors.
A. Non-Standard Linux Extension Flags
Linux introduces several flags not present in the POSIX standard:
| Linux Flag | Purpose / Description |
|---|---|
MAP_ANONYMOUS (or MAP_ANON) |
Creates an anonymous mapping not backed by any file. The fd argument is ignored (traditionally set to -1), and memory pages are initialized to zero. (POSIX historically used /dev/zero for this). |
MAP_POPULATE |
Prefaults page tables for the mapping (read-ahead). Instead of relying on lazy page faulting, the kernel eagerly loads pages into RAM at call time. |
MAP_LOCKED |
Locks the mapped pages into physical RAM (similar to mlock()), preventing them from being swapped out to disk. |
MAP_NORESERVE |
Disables swap space reservation for this mapping (allows overcommitting memory). |
MAP_HUGETLB |
Allocates memory using HugeTLB pages (e.g., 2MB or 1GB pages instead of 4KB) to reduce Translation Lookaside Buffer (TLB) misses in high-performance applications. |
MAP_FIXED_NOREPLACE |
Safe version of POSIX MAP_FIXED. Fails with EEXIST if the requested target virtual address range is already mapped, preventing accidental memory stomping. |
B. Linux System Call vs. glibc Function Wrappers
On modern Linux architectures, the low-level kernel system call interface differs slightly from the C library signature:
- Page-Offset Syscall (
sys_mmap2):
- On 32-bit architectures, the kernel provides
mmap2()where theoffsetparameter is passed in 4KB page units rather than raw bytes, allowing 32-bit systems to map offsets up to 16 TiB within files.
- Standard 64-bit Syscall (
sys_mmap):
- On 64-bit architectures (
x86_64,aarch64),sys_mmapaccepts byte offsets directly.
glibc translates standard byte-offset mmap() calls into the correct underlying architecture-specific system call automatically.
C. Kernel File Desynchronization & msync()
POSIX specifies that modifying a MAP_SHARED region modifies the file, but doesn't guarantee when dirty pages are written to physical disk storage.
To explicitly flush memory modifications back to storage across POSIX systems, developers call:
int msync(void *addr, size_t length, int flags); // MS_SYNC or MS_ASYNC
On Linux, munmap() or process termination will automatically queue dirty pages for asynchronous writing by the page cache flusher threads (pdflush/writeback), but calling msync() with MS_SYNC remains mandatory if immediate durability is required.
4. Practical C Example (POSIX File Mapping)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
int main(void) {
int fd = open("example.txt", O_RDWR);
if (fd == -1) {
perror("open failed");
return 1;
}
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("fstat failed");
close(fd);
return 1;
}
// Map the file into memory
char *map = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
perror("mmap failed");
close(fd);
return 1;
}
// File descriptor can be closed immediately after mapping
close(fd);
// Read first byte directly from pointer
printf("First char: %c\n", map[0]);
// Unmap memory
if (munmap(map, sb.st_size) == -1) {
perror("munmap failed");
return 1;
}
return 0;
}
mmap vs read/write
To understand mmap, you have to compare it to the "Standard I/O" way of doing things.
The Standard Way (read / write)
Imagine you want to read a 1GB file.
- Syscall: You call
read(). - Context Switch: The CPU switches to Kernel Mode.
- Disk Read: The Kernel reads data from the disk into the Kernel Page Cache.
- Copy: The Kernel copies that data from the Page Cache into your application's buffer (User Space).
- Return: The CPU switches back to User Mode.
- Cost: High CPU usage due to copying memory and context switching.
The mmap Way
- Syscall: You call
mmap(). - Mapping: The Kernel modifies the process's Page Table. It marks a range of virtual memory addresses as "pointing" to that file on the disk.
- Return: The call returns a pointer. No data has been read yet.
- Access: You try to read
pointer[0]. - Page Fault: The CPU realizes that memory address is empty. It pauses your program and triggers a "Page Fault."
- Lazy Load: The Kernel catches the fault, transparently reads the necessary block from the disk into the Page Cache, maps it to your pointer, and resumes your program.
- Benefit: Zero-copy. The application reads directly from the Kernel's Page Cache.
The Two Main Types of mmap
When you create a map, you must choose how it behaves using flags:
A. File-Backed Mapping (MAP_SHARED vs MAP_PRIVATE)
MAP_SHARED:- Changes you make to the memory are written back to the disk.
- Other processes mapping the same file see your changes instantly.
- Use Case: Inter-Process Communication (IPC) or high-performance database writes.
MAP_PRIVATE(Copy-on-Write):- You can read the file, but if you write to the memory, the OS creates a private copy of that specific page just for you.
- The file on disk is never modified.
- Use Case: Loading code libraries (
.sofiles) or initializing a process.
B. Anonymous Mapping (MAP_ANONYMOUS)
- This doesn't involve a file at all. It just asks the Kernel for a big chunk of raw RAM, filled with zeros.
- Fun Fact: This is actually how
malloc()works! If you askmallocfor a tiny amount of RAM, it uses the heap (brk). If you ask for a huge amount (e.g., 100MB),mallocusesmmapto grab a fresh block from the OS.
Why use mmap? (The Pros)
- Speed (Zero Copy): You avoid the CPU overhead of copying data from kernel space to user space.
- Lazy Loading: If you
mmapa 1TB file but only read the last byte, the OS only reads the last 4KB page from the disk. The rest of the file never touches RAM. - Random Access: Jumping around a file is as easy as moving a pointer/index (
data[5000]). You don't have tolseek()back and forth. - IPC (Inter-Process Communication): If two programs
mmapthe same file withMAP_SHARED, they can talk to each other extremely fast by just reading/writing memory.
The Downsides (The Cons)
- Page Fault Overhead:
mmapis great for large files, but for small files, the overhead of setting up the page tables and handling page faults is slower than a simpleread(). - Lack of Control: With
write(), you know exactly when the I/O happens. Withmmap, the OS decides when to flush dirty pages back to the disk (unless you force it withmsync). - The "SIGBUS" Error: If you
mmapa file, and someone else truncates (shortens) that file while you are using it, and you try to access the part that was cut off, your program crashes with aSIGBUSsignal.
Summary Analogy
read(): You go to the library (Disk), verify your ID (Syscall), the librarian finds the book, photocopies the page you want (Buffer Copy), and hands you the copy.mmap(): The librarian gives you a magic pair of glasses. You look at an empty desk, but the glasses make the book appear there. When you try to read a specific page, the librarian quietly slips that real page onto the desk before your eyes focus. You read the original page directly.