On this page

Shared Memory and Semaphores

Coverage: shmget/shmat (SysV) → mmap(MAP_SHARED) → POSIX semaphore → file locking → memfd_create → Comparison and Selection Applicable to: Linux user space

Overview

Pipes and sockets transfer data via copying, whereas shared memory allows multiple processes to access the same physical memory—zero-copy. This is the highest-performance IPC method, but it requires additional synchronization mechanisms (semaphores, file locks, or lockless data structures).

mmap(MAP_SHARED): POSIX Shared Memory

// Style 1: Anonymous shared memory (shared after fork)
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
                  MAP_SHARED|MAP_ANONYMOUS, -1, 0);
// After fork: parent and child processes see the same physical page (not COW!)

// Style 2: File-backed shared memory
int fd = open("/dev/shm/myregion", O_CREAT|O_RDWR, 0600);
ftruncate(fd, size);
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
// Unrelated processes: open the same file → mmap → share

// All processes see the same physical page → changes are visible to each other → synchronization required!

memfd_create: Anonymous File Descriptor

int fd = memfd_create("myregion", MFD_CLOEXEC);
ftruncate(fd, size);
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

// Advantages:
//   - No filesystem mount required → no need for /dev/shm
//   - Can be sealed (prevent modification): fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW)
//   - Can be passed to another process: sendmsg(SCM_RIGHTS) → fd passing

// systemd uses memfd for all internal IPC
int shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
void *addr = shmat(shmid, NULL, 0);
// ... read/write ...
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);

// Why it is obsolete: global key conflicts, risk of resource leaks, lack of flexibility

POSIX Semaphores

#include <semaphore.h>

// Anonymous semaphore (for threads or shared memory):
sem_t sem;
sem_init(&sem, 1, 1);   // pshared=1 → in shared memory
sem_wait(&sem);          // P (decrement by 1, block)
sem_post(&sem);          // V (increment by 1, possibly wake waiters)
sem_destroy(&sem);

// Named semaphore (for unrelated processes):
sem_t *sem = sem_open("/mysem", O_CREAT, 0600, 1);
sem_wait(sem);
sem_post(sem);
sem_close(sem);
sem_unlink("/mysem");

Inside sem_wait: futex

// glibc sem_wait implementation (simplified):
sem_wait(sem_t *sem) {
    while (1) {
        if (atomic_fetch_sub(sem->count, 1) > 0)
            return;  // Success! Zero system calls

        // count becomes negative → need to wait
        atomic_fetch_add(sem->count, 1);  // undo
        futex_wait(&sem->count, val);
    }
}
// Same as pthread_mutex: zero syscalls when uncontended → the power of futex

File Locking: Inter-process Mutual Exclusion

// fcntl record lock: lock a region of a file
struct flock fl = {
    .l_type   = F_WRLCK,  // Exclusive lock
    .l_whence = SEEK_SET,
    .l_start  = 0,
    .l_len    = 0,        // 0 = to end of file
};
fcntl(fd, F_SETLKW, &fl);  // Block and wait

// Advantages: Kernel manages automatically → process crash → lock automatically released
// Suitable for: Daemon single-instance, database file protection

Selection Guide

Scenario                              Recommended Solution
─────────────────────────────────────────────
Parent-child unidirectional data        pipe
Parent-child bidirectional data         socketpair
Unrelated processes sharing large data  mmap(MAP_SHARED) + POSIX sem
Daemon single-instance + inter-process lock  file lock (F_SETLK)
Thread synchronization (same process)   pthread_mutex (futex)
Counting semaphore between threads/     POSIX sem (futex)
reliable credential passing             AF_UNIX + SCM_CREDENTIALS

References

  • man: mmap(2), shm_overview(7), sem_overview(7), fcntl(2) (record locks), memfd_create(2)
  • Source code: ipc/sem.c (SysV), ipc/shm.c, mm/mmap.c
  • LWN: "memfd_create and sealing"

Keywords: mmap MAP_SHARED, memfd_create, POSIX semaphore, sem_wait, futex, file lock, F_SETLK, shared memory