The most surprising thing about mmap is that it’s not just for files; it’s also the primary mechanism Linux uses to give processes their initial memory and to handle anonymous memory allocations like malloc.

Let’s see it in action. Imagine you have a file, data.bin, and you want to read its contents without loading the whole thing into memory at once.

# Create a dummy file
echo "This is some data for mmap." > data.bin
echo "More data here." >> data.bin

# Use `xxd` to see the bytes
xxd data.bin

Now, let’s map this file into a process’s address space using a simple C program.

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

int main() {
    const char *filename = "data.bin";
    int fd;
    struct stat sb;
    char *mapped_data;

    // Open the file
    fd = open(filename, O_RDONLY);
    if (fd == -1) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }

    // Get file status (size)
    if (fstat(fd, &sb) == -1) {
        perror("Error getting file size");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // Map the file into memory
    // PROT_READ: Pages can be read.
    // MAP_PRIVATE: Creates a private copy-on-write mapping.
    mapped_data = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (mapped_data == MAP_FAILED) {
        perror("Error mapping file");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // Now you can access the file content as if it were an array of characters
    printf("File content:\n%s\n", mapped_data);

    // Unmap the memory
    if (munmap(mapped_data, sb.st_size) == -1) {
        perror("Error unmapping file");
    }

    // Close the file descriptor
    close(fd);

    return 0;
}

Compile and run this:

gcc -o mmap_example mmap_example.c
./mmap_example

You’ll see the exact content of data.bin printed. The magic is that the operating system hasn’t necessarily loaded all of data.bin into RAM yet. It’s just set up page table entries that point to the file on disk. When your program accesses mapped_data[i], a page fault occurs, and the kernel loads the relevant page from data.bin into a physical memory frame.

This mmap call is the foundation for how Linux handles file I/O efficiently. Instead of explicit read() calls that copy data from kernel buffers to user buffers, mmap allows direct access to the file’s content as if it were already in memory. The kernel manages the loading and unloading of pages from RAM based on demand.

mmap also powers anonymous mappings. When you call malloc in C, or when the kernel needs to create memory for a new process’s stack or heap, it often uses mmap with the MAP_ANONYMOUS flag. This tells the kernel to allocate memory that isn’t backed by any specific file. For malloc, the kernel might allocate a large chunk of anonymous memory via mmap and then subdivide it as needed.

The mmap system call signature is: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

  • addr: Preferred starting address in memory. NULL lets the kernel choose.
  • length: Size of the memory region to map.
  • prot: Protection flags (e.g., PROT_READ, PROT_WRITE, PROT_EXEC).
  • flags: Mapping flags (e.g., MAP_PRIVATE, MAP_SHARED, MAP_ANONYMOUS).
    • MAP_PRIVATE: Changes are not visible to other processes mapping the same file and are not written back to the file. This is what our example used.
    • MAP_SHARED: Changes are visible to other processes mapping the same file and are written back to the file.
    • MAP_ANONYMOUS: The mapping is not backed by any file. It’s initialized to zero.
  • fd: File descriptor of the file to map. Ignored if MAP_ANONYMOUS is set.
  • offset: Offset in the file where the mapping should start. Must be a multiple of the system’s page size.

The prot and flags are crucial. PROT_WRITE combined with MAP_SHARED is how multiple processes can share and modify a file in memory. If you try to write to a MAP_PRIVATE mapping where only PROT_READ was set, you’ll get a segmentation fault. The kernel handles the copy-on-write mechanism for MAP_PRIVATE mappings; if you write to a page, the kernel creates a private copy of that page for your process.

When you use mmap with MAP_SHARED and PROT_WRITE, you’re essentially giving multiple processes direct access to the same physical memory pages that correspond to a file. If one process writes to a shared page, the changes are immediately reflected for any other process that has mapped the same region of the file with MAP_SHARED. This is incredibly powerful for inter-process communication (IPC) and for building high-performance data stores, as it bypasses the overhead of explicit read/write system calls and kernel buffer copies.

The offset parameter must be a multiple of the system’s page size. You can find this using sysconf(_SC_PAGE_SIZE). If you provide an offset that isn’t page-aligned, mmap will fail. This is because the kernel manages memory in page-sized chunks, and mappings are always aligned to these boundaries.

The next concept you’ll encounter is managing shared memory regions between processes using mmap with MAP_SHARED and MAP_ANONYMOUS.

Want structured learning?

Take the full Linux & Systems Programming course →