How to Use POSIX Shared Memory in C
Shared memory is an efficient Inter-Process Communication (IPC)
mechanism that enables separate processes to access the same physical
memory segment. By mapping a common region of physical memory into the
distinct virtual address spaces of multiple processes, data can be
exchanged directly without the overhead of copying information through
the operating system kernel. In C, POSIX APIs provide a standardized,
file-descriptor-based interface—utilizing functions such as
shm_open, ftruncate, and mmap—to
create, size, and map these shared segments across process
boundaries.
Understanding Shared Memory Architecture
Operating systems enforce memory isolation by default. Every process operates within its own private virtual address space, translated into physical RAM through page tables managed by the Memory Management Unit (MMU) and the OS kernel. Under normal circumstances, one process cannot read or write to the virtual address space of another.
Shared memory bypasses this restriction by mapping the exact same physical pages of RAM into the page tables of two or more independent processes. While each process may assign a different virtual address to this region, the underlying physical storage is identical. As a result, writes executed by one process are instantly visible to all other processes mapping that segment, eliminating the data copying inherent to pipes, message queues, and sockets.
Key POSIX Shared Memory APIs
POSIX shared memory objects are referenced by names (typically
starting with a forward slash, such as /my_shm) and handled
via standard file descriptor paradigms. The core functions include:
shm_open(const char *name, int oflag, mode_t mode): Creates or opens a POSIX shared memory object. It returns an integer file descriptor, similar toopen(). Objects exist in a virtual file system (often mounted at/dev/shmon Linux).ftruncate(int fd, off_t length): Sets the size of the shared memory object in bytes. Newly created shared memory objects initially have a size of zero.mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset): Maps the shared memory file descriptor directly into the calling process's virtual address space, returning a pointer to the mapped memory.munmap(void *addr, size_t length): Unmaps the designated address range from the process's address space.shm_unlink(const char *name): Removes the shared memory object name from the system. The physical memory is released once all processes close and unmap the region.
Mapping Shared Memory Across Processes
Establishing a shared memory region requires a coordinated workflow between a producing process and a consuming process.
Step 1: Writing Process (Creating and Mapping)
The creating process generates the shared object, assigns its storage capacity, and maps it into its address space to write data.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/example_shm"
#define SHM_SIZE 4096
int main(void) {
// 1. Create the shared memory object
int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open failed");
return 1;
}
// 2. Configure the size
if (ftruncate(fd, SHM_SIZE) == -1) {
perror("ftruncate failed");
close(fd);
return 1;
}
// 3. Map the object into virtual memory
char *ptr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap failed");
close(fd);
return 1;
}
// 4. Write data to the shared memory region
const char *message = "Hello from the producing process!";
strncpy(ptr, message, SHM_SIZE);
printf("Data written to shared memory: %s\n", ptr);
// Keep memory accessible until unmapped
munmap(ptr, SHM_SIZE);
close(fd);
return 0;
}Step 2: Reading Process (Opening and Mapping)
The receiving process opens the existing object by its name and maps it into its own address space to read the contents.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/example_shm"
#define SHM_SIZE 4096
int main(void) {
// 1. Open the existing shared memory object
int fd = shm_open(SHM_NAME, O_RDONLY, 0666);
if (fd == -1) {
perror("shm_open failed");
return 1;
}
// 2. Map the object into this process's address space
char *ptr = mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) {
perror("mmap failed");
close(fd);
return 1;
}
// 3. Read the data
printf("Data read from shared memory: %s\n", ptr);
// 4. Clean up resources
munmap(ptr, SHM_SIZE);
close(fd);
// 5. Remove the shared object name from the system
shm_unlink(SHM_NAME);
return 0;
}Important Considerations
- Synchronization: POSIX shared memory does not
supply intrinsic synchronization. If multiple processes read and write
simultaneously, race conditions and memory corruption will occur.
Processes typically coordinate access using POSIX named semaphores
(
sem_open) or process-shared mutexes (pthread_mutexattr_setpshared). - Flags in
mmap: TheMAP_SHAREDflag is critical. UsingMAP_PRIVATEinstead creates a copy-on-write mapping, meaning changes remain local to that process and are not propagated to other processes sharing the file descriptor. - Compilation: When compiling C programs using POSIX
shared memory routines, link against the real-time library by passing
the
-lrtflag to the compiler (e.g.,gcc producer.c -o producer -lrt).