A service needs to search a 2 GiB index file. One approach repeatedly calls read() to copy file bytes into user-space buffers. Another approach asks the operating system to make the file appear inside the process's virtual address space.
After that mapping is established, the service can access file contents with ordinary loads:
The pointer is a virtual address. When the process first touches a nonresident part of the file, a page fault lets the kernel associate the virtual page with the corresponding file contents. Later accesses use normal memory instructions.
The Unix interface that creates this relationship is mmap(), short for memory map.
mmap()creates a virtual address range whose contents come from a file or an anonymous memory object.
Memory mapping unifies virtual memory and I/O. It does not load an entire file immediately, bypass the operating system's caches, or guarantee that a store is already durable on storage.
mmap() InterfaceOn Linux and POSIX-style systems, the function has this form:
Its arguments describe six decisions:
A common read-only mapping is:
On success, mapping is the start of a page-aligned virtual region chosen by the kernel.
On failure:
The failure value is not NULL. Address zero is normally unavailable to applications, but the API specifically defines MAP_FAILED as the error result.
The initial mmap() call creates address-space metadata and page-table state needed to represent the range. It usually does not read every file page or allocate one physical frame for every mapped page.
A file-backed mapping connects:
Suppose:
The relationship is:
The file does not need to appear at the same virtual address in another process. A second process can map the same file bytes at a different address:
Virtual addresses belong to each process. The file offset identifies the common backing content.
The mapping length is fixed when the mapping is created. Growing the file later does not automatically extend the process's virtual range. A larger virtual mapping requires a new or adjusted mapping operation.
Creating a file mapping establishes where file contents belong in the virtual address space. Physical residency is handled separately.
Suppose the process first reads an address in mapped file page 20:
If the file page is already resident in the operating system's file cache, the fault can be resolved without storage I/O and is normally minor.
If the contents must be read from storage, the fault is major and the thread waits for I/O.
The exact file-cache implementation belongs to the operating system, but one relationship is important here: ordinary buffered file I/O and memory-mapped I/O commonly interact with the same cached file contents. mmap() does not normally create a completely separate private file cache.
Mapping a 2 GiB file can therefore increase virtual size by 2 GiB while resident memory grows only for pages actually brought into memory.
The protection argument describes what the process may do through the mapping.
Common flags are:
They can be combined:
The file descriptor's open mode and file permissions must support the requested mapping. A shared writable file mapping normally requires a descriptor opened for writing.
Protection is enforced at page granularity by the MMU. A mapping created with PROT_READ cannot be made writable by casting its pointer:
PROT_WRITE grants hardware permission to store through the mapping. Whether those stores are private or affect shared file contents depends on the mapping flags.
MAP_PRIVATE: Private ChangesMAP_PRIVATE creates a private mapping.
The process initially observes file contents directly, with the file page reaching its private virtual mapping.
If the process writes, the operating system preserves private-memory semantics using copy-on-write:
Example:
The store changes what this process sees through its private mapping. It is not a request to write 'X' to file offset zero.
Other processes mapping the file do not receive the private modification.
MAP_PRIVATE should not be treated as a guaranteed immutable snapshot of the file. External changes to the backing file and the behavior of still-unmodified private pages are subject to operating-system and API semantics. If an application requires a stable snapshot, it needs an explicit snapshot or consistency protocol.
MAP_SHARED: Shared ChangesMAP_SHARED creates a mapping whose modifications are associated with the shared backing object.
A store updates the shared cached file page:
Step 3 happens without any system call by either process, and step 4 happens on the kernel's schedule rather than at the moment of the store.
Two processes do not need the same virtual address:
MAP_SHARED provides shared bytes, not synchronization. If two processes update the same data concurrently without a lock, atomic protocol, or other coordination, they can create races and corrupt higher-level structure.
Visibility and durability are also different. Another process can observe a shared modification in memory before the kernel has written the dirty page to persistent storage.
MAP_PRIVATE and MAP_SHARED Compared| Property | MAP_PRIVATE | MAP_SHARED |
|---|---|---|
| Initial bytes come from file | Yes | Yes |
| Writes visible through same process mapping | Yes | Yes |
| Writes visible to other shared mappers | No private changes | Yes |
| Writes update backing file | No | Eventually, subject to synchronization |
| Typical write mechanism | Copy-on-write private page | Dirty shared file page |
| Interprocess synchronization required | Not for private changes | Yes for shared mutable data |
The flag expresses the mapping's modification semantics. It does not decide read, write, or execute permission; that is the role of PROT_*.
These combinations are therefore meaningful:
Loading simulation...
mmap() can create memory with no file providing its initial contents.
On Linux:
For an anonymous mapping:
The virtual pages normally receive physical frames on first access. The process sees zero-initialized memory and can write it like heap storage.
Allocators use anonymous mappings for large allocations and arenas. Thread stacks and runtime-managed heaps can also be built from anonymous virtual regions.
An anonymous mapping is still page-based virtual memory. It has permissions, can remain partly nonresident, and is removed with munmap() or process exit.
Anonymous MAP_SHARED mappings can be inherited across fork() and used for interprocess communication. They share memory without a regular file pathname, but cooperating processes still need synchronization.
The file offset passed to mmap() must be aligned to the system's page-size requirements. On common Linux systems, it must be a multiple of the base page size.
Suppose:
Offset 10000 is not page-aligned.
Align downward:
The desired byte lies:
into the mapping.
To expose desired_length bytes beginning at file offset 10000:
The original mapping base and complete mapped length must be retained for munmap().
The returned address is page-aligned. A requested length does not generally need to be a page multiple, but the kernel manages the covered range in whole pages internally.
This can expose padding within the final mapped page. Code must still obey the actual file size and intended data length rather than treating every address up to the rounded page boundary as valid file data.
A writable mapping does not safely create file bytes beyond the file's current size merely because the virtual range exists.
For a new 4 KiB shared mapping, first make the file large enough:
If a process accesses a mapped page beyond the backing file's valid range, Linux commonly raises SIGBUS. A similar failure can occur when another process truncates a file underneath an existing mapping.
This creates a risk absent from an ordinary private heap pointer:
Applications that map mutable files need a protocol preventing unsafe truncation while readers or writers hold mappings.
Growing the file also does not grow an existing virtual mapping. The file size and mapping length are related but independently managed.
After a successful mmap(), the process can close the file descriptor without destroying the mapping:
The kernel retains the backing-object reference needed by the mapping.
The mapping remains until:
munmap() for the rangeexec() replaces the process address spaceClosing the descriptor affects future descriptor operations, not the already-created mapping.
The reverse is also true. Calling munmap() removes the virtual range but does not close the original descriptor if it remains open.
These are separate resources:
munmap()Remove a mapping with:
The address must identify the mapping at a suitable page boundary. The range can cover an entire mapping or a page-aligned subrange, depending on system rules.
After successful unmapping:
munmap() invalidates the process's virtual access. It does not necessarily erase cached file contents from physical memory.
Partial unmapping can split one virtual region into two:
Frequent mapping and unmapping has costs: kernel metadata updates, page-table work, and TLB invalidation. Mapping every tiny request independently is rarely efficient.
A store through MAP_SHARED first changes a memory-resident page:
Another process mapping the same page can observe the changed bytes before storage write-back completes.
The msync() interface asks the kernel to synchronize a mapped range:
Common modes include:
On Linux, the starting address passed to msync() must be page-aligned.
For a shared writable mapping:
msync() is not a mutex and does not make a multi-field update atomic. It addresses synchronization with backing storage, not coordination between concurrent writers.
Durability has more layers than copying dirty bytes from memory toward a file. Filesystem metadata, file size, storage-device caches, and the ordering of related updates can matter after a crash. A correct persistent format needs an explicit durability and recovery protocol rather than assuming every memory store is instantly durable.
mmap() Versus read()Both interfaces can access the same file contents, but they present different programming models.
read()The application controls explicit I/O boundaries and receives errors at system-call sites.
The application avoids an explicit copy from the kernel's cached file page into a separate user buffer for each read. It can navigate file structures with pointer-like indexing.
This does not mean mmap() is always faster.
mmap() can be attractive for:
read() can be attractive for:
Performance depends on access pattern, page-fault cost, copying cost, storage behavior, read-ahead, cache pressure, page-table overhead, and concurrency.
Measure the actual workload rather than choosing mmap() solely because it removes explicit read() calls.
mmap() and the Page CacheMemory mapping is sometimes confused with direct I/O.
A typical file-backed mapping uses the operating system's cached file pages:
The process reads from a physical page that represents file content. If ordinary buffered I/O later accesses the same file range, the kernel can use the same coherent cached data rather than maintaining unrelated copies.
This has useful consequences:
read().read() can observe changes made through a coherent shared mapping.It also means a giant mapping is not free merely because pages load lazily. Touching the entire mapping can populate large amounts of the file in memory and displace other useful cached or anonymous pages.
This Linux program creates a one-page file, maps it with MAP_SHARED, writes text through the mapping, synchronizes it, and unmaps it.
Compile and run:
The file is one page long because ftruncate() established its size before mapping. The message occupies the beginning, and the remaining bytes are zeros.
The example deliberately keeps the descriptor open until cleanup, although it could close it after successful mmap() and retain the mapping.
Passing NULL as the first argument lets the kernel choose a suitable virtual address:
This is the normal choice.
A non-null address without fixed-placement flags is generally a hint. The kernel can choose another suitable location.
MAP_FIXED requests exact placement and can replace existing mappings that overlap the requested range. Used incorrectly, it can destroy a stack, library, heap, or other live region and cause immediate or delayed corruption.
Exact placement is needed only for specialized low-level software. General application code should let the operating system select the address.
A pointer stored before mmap() cannot predict where the kernel will place the new region. Always use the returned pointer.
mmap() introduces failures at both setup time and access time.
The call returns MAP_FAILED for conditions such as:
Inspect errno after failure.
The call can succeed, yet a later access can fail:
Such failures commonly arrive as signals such as SIGBUS or SIGSEGV, not as a return value from the source-level load or store.
This changes error handling. A read() has an explicit error-return point. A mapped load looks ordinary in source code even though it can trigger I/O and a signal.
Applications using memory-mapped persistent data need file-lifetime rules, truncation coordination, bounds checks, and an error strategy appropriate to this asynchronous-looking fault path.
mmap() avoids some copying and can exploit demand paging, but it adds other costs:
Mapping a very small file for one quick sequential read can cost more than one read() call.
Mapping a huge sparse file consumes virtual address space cheaply, but touching pages across it can create high page-table, TLB, fault, and cache costs.
Frequent mmap() and munmap() calls from many threads can also create address-space lock contention and multicore translation shootdowns.
The API changes where costs occur:
Neither cost model is universally smaller.
mmap() creates a virtual address range backed by a file or anonymous memory. File-backed pages are normally supplied on demand through the operating system's cached file contents, allowing ordinary loads and stores to act as I/O operations.
MAP_PRIVATE gives the process private copy-on-write modifications, while MAP_SHARED associates writes with shared file-backed pages visible to other mappers. Protection flags independently control reading, writing, and execution. Offsets must be page-aligned, files must be sized before mapped writes beyond their current end, and mappings remain valid independently of the original file descriptor.
Memory visibility is not storage durability: shared stores dirty cached pages, and msync() requests synchronization with the backing file but does not replace concurrency or crash-consistency protocols. mmap() can avoid explicit buffer copies and support efficient random access, yet page faults, TLB pressure, mapping updates, write-back, and access-time signals mean it is not universally faster or simpler than read().
5 quizzes