QUESTION
Does mapping the same physical page make a structure safe to share without synchronization?
Shared memory only makes bytes visible through the same backing page; it does not automatically order reads and writes across CPUs or preserve object consistency. If a ready flag becomes visible before the writer completes the payload, a reader can observe a half-updated structure.
Use a process-shared sem_t, pthread mutex/condition variable, futex, or atomic protocol to order publication and consumption. The synchronization object itself must reside in the MAP_SHARED range and be initialized with a pshared attribute.
STRUCTURE
Structure diagram
Process A
- VA 0x7f10…
- message write
- sem_post release
Shared pages
- offset 0: sem_t
- offset 64: message
- tmpfs/memfd backing
Process B
- VA 0x6a20…
- sem_wait acquire
- message read
Naming / lifetime
- shm_open name
- fd references
- mapping remains after shm_unlink
Each process can use a different mapping address while sharing the same backing page. Store offsets instead of raw pointers and order publication with a semaphore.
CALL PATH
Call path
Define who ends the lifetimes of shared data, the name, fds, mappings, and the synchronization object independently.
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/shmem.c | shmem_file_setup(), shmem_mmap() | tmpfs/shared-memory backing and page faults |
| kernel/futex/waitwake.c | futex_wait(), futex_wake() | Sleep/wakeup when a semaphore contends |
| mm/mmap.c | mmap_region() | Attach the same file as MAP_SHARED VMAs |
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 -pthread shared_sem.c -o shared_sem01#define _DEFAULT_SOURCE
02#include <errno.h>
03#include <fcntl.h>
04#include <semaphore.h>
05#include <stdio.h>
06#include <string.h>
07#include <sys/mman.h>
08#include <sys/wait.h>
09#include <unistd.h>
10
11struct shared_data {
12 sem_t ready;
13 char message[128];
14};
15
16int main(void)
17{
18 struct shared_data *data = mmap(NULL, sizeof(*data), PROT_READ | PROT_WRITE,
19 MAP_SHARED | MAP_ANONYMOUS, -1, 0);
20 if (data == MAP_FAILED || sem_init(&data->ready, 1, 0) < 0)
21 return 1;
22 pid_t pid = fork();
23 if (pid == 0) {
24 while (sem_wait(&data->ready) < 0)
25 if (errno != EINTR)
26 _exit(2);
27 dprintf(STDOUT_FILENO, "child read: %s\n", data->message);
28 _exit(0);
29 }
30
31 strcpy(data->message, "published through shared memory");
32 sem_post(&data->ready);
33 waitpid(pid, NULL, 0);
34 sem_destroy(&data->ready);
35 return munmap(data, sizeof(*data)) != 0;
36}
CODE NOTES
Code notes
struct shared_dataPlaces the synchronization object and protected payload in the same shared mapping. A normal heap pointer from one process has no meaning in another.
MAP_SHARED | MAP_ANONYMOUSAn anonymous shared mapping created before fork gives parent and child the same backing pages. Unrelated processes should use shm_open or memfd.
sem_init(&data->ready, 1, 0)Creates a process-shared semaphore with pshared=1 and initial value 0.
strcpy(data->messageCompletes the payload before publishing with sem_post. The semaphore operation provides the required memory ordering.
sem_destroyDestroys the object only after agreement that no other process/thread will wait or post. Destroying a semaphore with waiters is undefined behavior.
DETAILS
Detailed behavior
Removing a name and removing an object are different
shm_unlink removes the name used by new shm_open calls, but existing fds and mappings remain valid, much like unlink on a normal file.
If a creator publishes the name before initialization is complete, an opener can see an incomplete header; use a version/magic/ready protocol.
A shared pointer may have different addresses
Unrelated processes can map the same shared object at different virtual addresses. A raw pointer stored inside the mapping is invalid in the other process.
Use a base-relative offset, index, or fixed-width handle, with range validation.
Include owner crashes in the protocol
If a process decrements a semaphore and dies while working, the credit is not restored automatically. For a mutex, consider a robust process-shared mutex and EOWNERDEAD recovery.
Place a generation, state, and checksum in the shared header and elect one recovery owner.
OBJECTS
Objects and lifetimes
| Object | Creation and release | Values to inspect |
|---|---|---|
shared backing object | Created by shm_open/memfd or an anonymous fork mapping and released after the last reference | size, seals, name/link |
shared mapping | Created separately by mmap in each process and removed by munmap/exit | base address, protection |
sem_t | Initialized with sem_init in a shared page and destroyed after agreed final use | pshared, count, waiter |
FAILURE PATH
Failure conditions and common misconceptions
| Observed symptom | Likely causes | How to verify |
|---|---|---|
| A reader sees a corrupted structure | Publication synchronization is missing | Inspect writer ordering and the acquire primitive |
| A pointer crashes in another process | A raw virtual address was shared | Check offset/index encoding |
| sem_wait blocks forever | The producer crashed or the credit protocol is wrong | timeout, owner heartbeat, recovery |
LAB
Verify it yourself
- Move sem_post before the message write, observe the incorrect publication order in a stress loop, then restore it.
- Convert the example to shm_open so two unrelated processes find data by offset despite different mapping bases.
- Use PTHREAD_PROCESS_SHARED with a PTHREAD_MUTEX_ROBUST mutex and implement EOWNERDEAD recovery after an owner crash.
./shared_semstrace -f -e trace=openat,ftruncate,mmap,futex,wait4,munmap,unlink ./shared_semPRIMARY REFERENCES