When two or more processes need to exchange large amounts of data quickly, shared memory is the fastest IPC mechanism available. Unlike pipes or message queues, shared memory avoids any kernel copying of data โ processes read and write directly to a common region of memory.
POSIX shared memory was introduced by the POSIX.1b realtime standard as a cleaner alternative to the older System V shared memory API. The goal was to provide shared memory that fits naturally into the familiar UNIX model of filenames and file descriptors.
System V shared memory (covered in Chapter 48) works, but it has two well-known friction points:
Section 49.4.2 of TLPI showed that two processes can share memory by mapping the same file with MAP_SHARED. This works, but it has one annoying requirement: you must create an actual disk file even if you never want persistent storage. Creating a disk file means:
- Extra step of creating and naming a backing file
- File I/O overhead (disk reads/writes during setup)
- Cleanup burden โ you must delete the file when done
POSIX shared memory eliminates the disk file entirely. It lives in a RAM-based filesystem called tmpfs.
Using POSIX shared memory always follows the same two-step pattern:
This two-step design is intentional and historical. When POSIX added shared memory, mmap() already existed. The committee simply decided: instead of calling open() on a disk file, call shm_open() to get a file descriptor for a RAM-resident object, then pass that descriptor to mmap() as usual.
The table below maps corresponding calls between the two APIs:
| Operation | System V | POSIX |
|---|---|---|
| Create / Open | shmget() |
shm_open() |
| Attach / Map | shmat() |
mmap() |
| Detach / Unmap | shmdt() |
munmap() |
| Control / Remove | shmctl() |
shm_unlink() |
| Identifier type | Integer ID (shmid) | File descriptor (fd) |
The POSIX standard does not specify how shared memory objects should be stored internally. Linux uses a dedicated tmpfs filesystem mounted at /dev/shm.
Objects in /dev/shm survive even if no process currently has them open. But they are lost on system reboot because tmpfs is RAM-based.
Total POSIX shm is limited by the tmpfs size (default often 256 MB). The superuser can resize it: mount -o remount,size=512m /dev/shm
Below is a minimal complete example showing the two-step process. We create a 4096-byte shared memory object, map it, write a string, then read it back.
/*
* posix_shm_intro.c
* Demonstrates the two-step POSIX shm pattern:
* Step 1: shm_open() - get a file descriptor
* Step 2: mmap() - map into address space
*
* Compile: gcc posix_shm_intro.c -o posix_shm_intro -lrt
* Run: ./posix_shm_intro
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h> /* O_CREAT, O_RDWR */
#include <sys/mman.h> /* shm_open, mmap */
#include <sys/stat.h> /* mode constants */
#include <unistd.h> /* ftruncate */
#define SHM_NAME "/ep_demo"
#define SHM_SIZE 4096
int main(void)
{
int fd;
void *addr;
/* ---- STEP 1: shm_open() ---- */
/* Create (O_CREAT) and open for read-write (O_RDWR).
* O_EXCL would fail if it already exists โ omitted here for simplicity.
* 0600 = owner read+write permissions.
*/
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
if (fd == -1) {
perror("shm_open");
exit(EXIT_FAILURE);
}
printf("shm_open() succeeded. fd = %d\n", fd);
/* A new object starts at zero length.
* We must call ftruncate() to set the desired size BEFORE mmap().
*/
if (ftruncate(fd, SHM_SIZE) == -1) {
perror("ftruncate");
exit(EXIT_FAILURE);
}
printf("ftruncate() set size to %d bytes\n", SHM_SIZE);
/* ---- STEP 2: mmap() ---- */
/* MAP_SHARED means writes are visible to other processes
* that map the same object.
*/
addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
printf("mmap() succeeded. Mapped at %p\n", addr);
/* The file descriptor can be closed after mmap().
* The mapping stays valid.
*/
close(fd);
/* Write to shared memory like normal memory */
snprintf((char *)addr, SHM_SIZE, "Hello from EmbeddedPathashala!");
printf("Wrote: %s\n", (char *)addr);
/* Read it back */
printf("Read back: %s\n", (char *)addr);
/* Cleanup */
munmap(addr, SHM_SIZE);
shm_unlink(SHM_NAME); /* Remove the shared memory object */
printf("Done. Shared memory removed.\n");
return 0;
}
shm_open() succeeded. fd = 3
ftruncate() set size to 4096 bytes
mmap() succeeded. Mapped at 0x7f3a2b000000
Wrote: Hello from EmbeddedPathashala!
Read back: Hello from EmbeddedPathashala!
Done. Shared memory removed.
On older Linux systems you must link with
-lrt (realtime library) for shm_open() and shm_unlink(). On modern glibc they are in libc but the flag does not hurt.mmap() or other standard file-descriptor-based calls, so you need shmget/shmat/shmctl โ a completely separate API./dev/shm. Shared memory objects appear as files under that directory.shm_unlink() or the system is rebooted. Even if all processes that have it open exit, the object remains accessible in /dev/shm โ unlike a pipe, which disappears when all file descriptors to it are closed.shm_open() to create or open the shared memory object; this returns a file descriptor. Step 2 โ pass that file descriptor to mmap() with MAP_SHARED to map the object into the process’s virtual address space.shm_open() is analogous to shmget() (create/obtain), and mmap() is analogous to shmat() (attach/map). Similarly, munmap() โ shmdt(), and shm_unlink() โ shmctl(IPC_RMID,...).mmap() has been called, the mapping remains valid even if you close the file descriptor. However, you may want to keep it open if you need to call fstat() or ftruncate() later to inspect or resize the object.