Signal / Event / IPC · Linux userspace / kernel ABI

POSIX shared memory and process-shared semaphores

Uses shm_open to attach a name to an inode-like object and coordinates data publication with a semaphore inside MAP_SHARED memory.

Series
26 / 38
Build
cc -std=c17 -Wall -Wextra -O2 -pthread shared_sem.c -o shared_sem
Run
./shared_sem
Kernel
Linux 6.18.37 LTS

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 diagram

Figure 1. Different VAs referring to the same shared page

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

Figure 2. From userspace code to observable results
shm_open open object by name
ftruncate establish shared size
MAP_SHARED map in each process
sem_post/wait publish/acquire
unlink remove name

Define who ends the lifetimes of shared data, the name, fds, mappings, and the synchronization object independently.

Figure 3. Major points along the kernel-internal path
tmpfs inode /dev/shm object
mmap page cache mapping
sem fast path user atomic
futex wait/wake enter kernel on contention
munmap/unlink release references

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.

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.

FileFunction / structureWhat 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 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.

Buildcc -std=c17 -Wall -Wextra -O2 -pthread shared_sem.c -o shared_sem
01#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

Source line 11struct shared_data

Places the synchronization object and protected payload in the same shared mapping. A normal heap pointer from one process has no meaning in another.

Source line 19MAP_SHARED | MAP_ANONYMOUS

An anonymous shared mapping created before fork gives parent and child the same backing pages. Unrelated processes should use shm_open or memfd.

Source line 20sem_init(&data->ready, 1, 0)

Creates a process-shared semaphore with pshared=1 and initial value 0.

Source line 31strcpy(data->message

Completes the payload before publishing with sem_post. The semaphore operation provides the required memory ordering.

Source line 34sem_destroy

Destroys the object only after agreement that no other process/thread will wait or post. Destroying a semaphore with waiters is undefined behavior.

Detailed behavior

01

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.

02

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.

03

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 and lifetimes

ObjectCreation and releaseValues to inspect
shared backing objectCreated by shm_open/memfd or an anonymous fork mapping and released after the last referencesize, seals, name/link
shared mappingCreated separately by mmap in each process and removed by munmap/exitbase address, protection
sem_tInitialized with sem_init in a shared page and destroyed after agreed final usepshared, count, waiter

Failure conditions and common misconceptions

Observed symptomLikely causesHow to verify
A reader sees a corrupted structurePublication synchronization is missingInspect writer ordering and the acquire primitive
A pointer crashes in another processA raw virtual address was sharedCheck offset/index encoding
sem_wait blocks foreverThe producer crashed or the credit protocol is wrongtimeout, owner heartbeat, recovery

Verify it yourself

  1. Move sem_post before the message write, observe the incorrect publication order in a stress loop, then restore it.
  2. Convert the example to shm_open so two unrelated processes find data by offset despite different mapping bases.
  3. Use PTHREAD_PROCESS_SHARED with a PTHREAD_MUTEX_ROBUST mutex and implement EOWNERDEAD recovery after an owner crash.
Run./shared_sem
Tracestrace -f -e trace=openat,ftruncate,mmap,futex,wait4,munmap,unlink ./shared_sem

Primary sources