When you write to a memory-mapped region, two questions arise: (1) Do other processes see your change? (2) Does the change get written back to the file on disk? The answer depends entirely on whether you used MAP_SHARED or MAP_PRIVATE.
| Property | MAP_SHARED | MAP_PRIVATE |
| Changes visible to other processes? | Yes — all processes mapping the same file see each other’s writes | No — changes are process-private |
| Changes written back to file? | Yes — kernel eventually writes dirty pages to the file | No — the file is never modified |
| Copy-on-Write on write? | No — writes go directly to the shared page | Yes — kernel creates a private copy of the page on first write |
| Typical use case | IPC shared memory, memory-mapped I/O, database files | Loading .so libraries, reading config files, loading ELF text segments |
Before a write, the MAP_PRIVATE page is shared with the file’s page cache. On the first write, the kernel:
| Process A Virtual Page |
→ | Shared Physical Page (from file page cache) |
| Process A Virtual Page |
→ | NEW Private Physical Page (modified copy) |
| File Page Cache | → | Original Physical Page (unchanged) |
Writer process maps the file and writes data:
/* writer.c */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#define FILE_PATH "shared.dat"
#define MAP_SIZE 64
int main(void) {
int fd;
char *addr;
fd = open(FILE_PATH, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) { perror("open"); exit(1); }
if (ftruncate(fd, MAP_SIZE) == -1) { perror("ftruncate"); exit(1); }
addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
strncpy(addr, "Hello from writer!", MAP_SIZE);
printf("Writer wrote: %s\n", addr);
/* Give reader a moment */
sleep(2);
printf("Writer sees now: %s\n", addr); /* may see reader's reply */
munmap(addr, MAP_SIZE);
return 0;
}
Reader process maps the same file and reads:
/* reader.c */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#define FILE_PATH "shared.dat"
#define MAP_SIZE 64
int main(void) {
int fd;
char *addr;
sleep(1); /* Wait for writer */
fd = open(FILE_PATH, O_RDWR);
if (fd == -1) { perror("open"); exit(1); }
addr = mmap(NULL, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
printf("Reader sees: %s\n", addr);
strncpy(addr, "Reader replied!", MAP_SIZE);
munmap(addr, MAP_SIZE);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
int main(void) {
int fd;
struct stat sb;
char *addr;
/* Create a file with known content */
fd = open("private_test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
write(fd, "ORIGINAL CONTENT", 16);
fstat(fd, &sb);
addr = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, /* private: changes do NOT go to file */
fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
printf("Before write: %.*s\n", (int)sb.st_size, addr);
/* Modify the mapping */
memcpy(addr, "MODIFIED CONTENT", 16);
printf("After write (in-memory): %.*s\n", (int)sb.st_size, addr);
munmap(addr, sb.st_size);
/* Now re-read the file — it should still have "ORIGINAL CONTENT" */
fd = open("private_test.txt", O_RDONLY);
char buf[17] = {0};
read(fd, buf, 16);
close(fd);
printf("File on disk (unchanged): %s\n", buf);
return 0;
}
/* Output:
Before write: ORIGINAL CONTENT
After write (in-memory): MODIFIED CONTENT
File on disk (unchanged): ORIGINAL CONTENT
*/
For MAP_SHARED mappings, the kernel will eventually write dirty pages back to the file, but there is no guarantee of when. If you need the file on disk to reflect your changes at a specific moment, use msync().
#include <sys/mman.h>
int msync(void *addr, /* Start of region to sync */
size_t length, /* Length of region */
int flags); /* MS_SYNC or MS_ASYNC or MS_INVALIDATE */
/* Returns 0 on success, -1 on error */
| Flag | Behavior |
MS_SYNC |
Write dirty pages to file and wait until I/O completes (blocking) |
MS_ASYNC |
Schedule the write but don’t wait (non-blocking) |
MS_INVALIDATE |
Invalidate cached pages so next access re-reads from file (for MAP_SHARED across processes) |
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
typedef struct {
int record_id;
char name[32];
int value;
} Record;
int main(void) {
int fd;
Record *rec;
size_t size = sizeof(Record);
fd = open("database.dat", O_RDWR | O_CREAT | O_TRUNC, 0644);
ftruncate(fd, size);
rec = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (rec == MAP_FAILED) { perror("mmap"); exit(1); }
close(fd);
/* Write record */
rec->record_id = 42;
strncpy(rec->name, "temperature", 31);
rec->value = 37;
/* msync ensures data is on disk before we proceed */
if (msync(rec, size, MS_SYNC) == -1) {
perror("msync");
exit(1);
}
printf("Record flushed to disk: id=%d name=%s value=%d\n",
rec->record_id, rec->name, rec->value);
munmap(rec, size);
return 0;
}
When a process calls fork(), the child inherits all the parent’s mappings. The behavior depends on the mapping type:
| Mapping Type | Behavior After fork() |
| MAP_PRIVATE (file or anonymous) | Parent and child share pages copy-on-write. Writes by either create a private copy — the other process does not see it. |
| MAP_SHARED (file) | Parent and child both write to the same file pages. Writes by one are visible to the other immediately. |
| MAP_SHARED | MAP_ANONYMOUS | This is the canonical way to create shared memory between parent and child: create the mapping before fork(), then both can communicate through it. |
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
int *shared;
/* Create shared anonymous mapping BEFORE fork() */
shared = mmap(NULL, sizeof(int),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
if (shared == MAP_FAILED) { perror("mmap"); exit(1); }
*shared = 0;
pid_t pid = fork();
if (pid == 0) {
/* Child increments the shared counter */
for (int i = 0; i < 1000; i++) {
(*shared)++; /* NOTE: not atomic — demo only */
}
exit(0);
} else {
/* Parent also increments */
for (int i = 0; i < 1000; i++) {
(*shared)++;
}
wait(NULL);
printf("Final counter (approx 2000): %d\n", *shared);
munmap(shared, sizeof(int));
}
return 0;
}
A: No. With MAP_PRIVATE, the kernel uses copy-on-write. On the first write, a private copy of the page is created for the process. All subsequent writes go to this private page. The file on disk and the file’s page cache remain unchanged.
A: Yes, because both processes’ virtual pages point to the same physical page in the kernel’s page cache. Writing to the shared page is immediately visible through the other process’s mapping. There is no buffering between them.
A: The kernel writes dirty pages to disk lazily (for performance), but it gives no timing guarantees. If the system crashes before the flush, data is lost. msync(MS_SYNC) blocks until the data is safely on disk, making it suitable for transactional or journaling applications. It is analogous to fsync() but for mapped regions.
A: Create a MAP_SHARED | MAP_ANONYMOUS mapping before calling fork(). Both parent and child inherit the mapping and see each other’s writes because it is a shared mapping. Unlike System V shared memory, no key or ID is needed — the mapping is inherited directly across fork().
A: The text (code) segment is loaded with MAP_PRIVATE. Since the .so code is read-only for most processes, they all initially share the same physical pages from the page cache (efficient). If a process somehow modifies a code page (unusual), copy-on-write gives it a private copy without affecting other processes. The data segment of a .so may use MAP_PRIVATE too, so each process gets its own initialized data.
