QUESTION
Why are changes made through MAP_PRIVATE not written to the original file?
A write fault in a MAP_SHARED mapping attaches the page-cache page as writable and marks it dirty, making the change visible to other shared mappers and file I/O. A MAP_PRIVATE mapping may initially read the file page, but a write creates an anonymous private copy.
msync requests dirty-page writeback within the mapping type and filesystem policy. CPU-cache coherence, visibility to another process, and storage durability are guarantees at different layers.
STRUCTURE
Structure diagram
Process A · MAP_SHARED
- VA 0x7000
- write 'S'
- PTE → page cache
File page cache
- inode index 0
- dirty folio
- msync/writeback
Process B · MAP_PRIVATE
- read → page cache
- write fault
- PTE → anonymous page
Private page B
- content 'P'
- Private_Dirty
- not reflected in file
A MAP_SHARED write dirties a page-cache page, while a MAP_PRIVATE write creates a process-private anonymous page.
CALL PATH
Call path
The mapping flag determines which backing object owns a write after a page fault. Look not at whether virtual addresses match, but at whether the page cache or an anonymous page becomes dirty.
This diagram is not for memorizing function names. Follow which return value, file descriptor, memory mapping, or wait queue is passed to the next stage.
SOURCE COORDINATES
Linux 6.18.37 LTS source locations
Go beyond the glibc function to the files where the syscall implementation meets kernel objects. Each link points to the original file at the same tag.
| File | Function / structure | What to inspect |
|---|---|---|
| mm/mmap.c | mmap_region() | Attach a file and sharing flags to the VMA |
| mm/filemap.c | filemap_fault(), filemap_map_pages() | Attach a file page-cache page to the PTE |
| mm/memory.c | do_wp_page(), wp_page_copy() | Create a COW page on a private-mapping write |
COMPLETE PROGRAM
Complete runnable example
The code below is not pseudocode with explanatory lines omitted. It is a minimal example that can be built and run as a file.
cc -std=c17 -Wall -Wextra -O2 mmap_file.c -o mmap_file01#define _POSIX_C_SOURCE 200809L
02#include <fcntl.h>
03#include <stdio.h>
04#include <string.h>
05#include <sys/mman.h>
06#include <unistd.h>
07
08int main(int argc, char **argv)
09{
10 if (argc != 2)
11 return 2;
12 long page = sysconf(_SC_PAGESIZE);
13 int fd = open(argv[1], O_RDWR | O_CREAT | O_CLOEXEC, 0644);
14 if (fd < 0 || ftruncate(fd, page) < 0)
15 return 1;
16
17 char *shared = mmap(NULL, (size_t)page, PROT_READ | PROT_WRITE,
18 MAP_SHARED, fd, 0);
19 char *private = mmap(NULL, (size_t)page, PROT_READ | PROT_WRITE,
20 MAP_PRIVATE, fd, 0);
21 if (shared == MAP_FAILED || private == MAP_FAILED)
22 return 1;
23
24 memcpy(shared, "shared", 7);
25 if (msync(shared, (size_t)page, MS_SYNC) < 0)
26 return 1;
27 memcpy(private, "private", 8);
28 printf("shared='%s' private='%s'\n", shared, private);
29 munmap(private, (size_t)page);
30 munmap(shared, (size_t)page);
31 close(fd);
32 return 0;
33}
CODE NOTES
Code notes
ftruncate(fd, page)Ensures the file is long enough before mapping it. Accessing a page beyond EOF can generate SIGBUS.
MAP_SHARED, fd, 0Attaches the first page's page-cache backing as a shared mapping. A write is visible to another mapping of the same inode page.
MAP_PRIVATE, fd, 0Reads may use the file page, but a write fault branches to a private anonymous copy.
msync(sharedRequests completion of writeback for the shared dirty range. This does not automatically guarantee directory-entry durability or protection against storage-device power loss.
memcpy(privateOnly the private mapping changes; the string 'private' is not written to the file or the shared mapping.
DETAILS
Detailed behavior
Match file size to mapping size
Although mmap can create a page-granular range, it does not create valid data beyond file EOF. Bytes after EOF in the final partial page may read as zero, but accessing the next page is subject to SIGBUS.
If another process truncates the file, a process with an existing mapping can also receive SIGBUS on a later access.
Separate visibility from synchronization
On a cache-coherent system, another process can see a CPU store to a MAP_SHARED page, but data-structure consistency is not guaranteed. A protocol using atomics, mutexes, sequence counters, or similar mechanisms is required.
msync is not a thread memory-ordering primitive.
mmap I/O still pays page-fault and writeback costs
Reducing read/write syscall count does not remove first-access faults, dirty throttling, reclaim, or filesystem writeback. Measure latency through faults and storage interactions, not syscall count alone.
It may benefit random access and zero-copy parsing, but introduces costs for handling truncate, SIGBUS, and address-space pressure.
OBJECTS
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
file-backed VMA | Attached to the inode address_space by mmap and retained until munmap | vm_pgoff, shared/private flag |
page cache folio | Created by file read/fault and passes through dirty/writeback/reclaim | index, dirty, writeback |
private COW page | Created on a MAP_PRIVATE write fault and belongs only to the process mm | anonymous, dirty, RSS |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| SIGBUS | The file is truncated while mapped, or access goes beyond EOF | Inspect si_addr and the current file size |
| Private changes are absent from the file | Normal COW behavior of MAP_PRIVATE | smaps Anonymous/Private_Dirty |
| msync returns EINVAL | The address/range is not page-aligned, or the flags are invalid | Check mapping boundaries and page size |
LAB
Verify it yourself
- After the program runs, hexdump shared.dat and verify that only the shared change was recorded.
- Have another process map the file with MAP_SHARED and distinguish visibility before and after msync from persistence to storage.
- Have another process call ftruncate(0) while the mapping exists, then inspect the SIGBUS handler and the limits of safe recovery.
./mmap_file shared.datstrace -e trace=openat,ftruncate,mmap,msync,munmap,close ./mmap_file shared.datPRIMARY REFERENCES