POSIX shared memory objects have kernel persistence: they survive until you explicitly delete them with shm_unlink() or reboot the system. If you forget to call shm_unlink(), the object remains in /dev/shm consuming memory indefinitely.
This is unlike anonymous mmap() regions, which disappear when the process exits. POSIX shm objects must be explicitly cleaned up β just like files.
#include <sys/mman.h>
int shm_unlink(const char *name);
/* Returns: 0 on success, -1 on error */
The name argument is the same name string passed to shm_open(). This function is the POSIX shm analog of unlink() for files, and it works the same way: it removes the name from the filesystem, but the object is not actually destroyed until all file descriptors and memory mappings referring to it are closed/unmapped.
Calling shm_unlink() removes the name (the entry in /dev/shm), but the underlying shared memory object is not freed immediately if some processes still have it mapped. It is freed only when the last mapping is removed.
shm_open("/myshm", ...) and mmap(). Process B also mmaps same object.shm_unlink("/myshm"). Name removed from /dev/shm. But physical memory still alive β B is using it.munmap() and exits. Now the physical memory is freed. Object truly gone./*
* shm_unlink_demo.c
* Shows deferred deletion behavior:
* - Create and map a shared memory object
* - Unlink it while still mapped
* - Show that the mapping is still usable after unlink
* - Show that new open attempts fail after unlink
*
* Compile: gcc shm_unlink_demo.c -o shm_unlink_demo -lrt
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#define SHM_NAME "/ep_unlink_demo"
#define SHM_SIZE 512
int main(void)
{
int fd;
char *addr;
/* Create and map */
fd = shm_open(SHM_NAME, O_CREAT | O_EXCL | O_RDWR, 0600);
if (fd == -1) { perror("shm_open create"); exit(EXIT_FAILURE); }
ftruncate(fd, SHM_SIZE);
addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd);
strcpy(addr, "Data before unlink");
printf("Before unlink: '%s'\n", addr);
/* Unlink the name β removes entry from /dev/shm */
if (shm_unlink(SHM_NAME) == -1) {
perror("shm_unlink");
exit(EXIT_FAILURE);
}
printf("shm_unlink() called. Name removed from /dev/shm.\n");
/* Mapping is still valid! We can still read/write */
strcpy(addr, "Data AFTER unlink β still works!");
printf("After unlink: '%s'\n", addr);
/* Try to open it again β should fail because name is gone */
fd = shm_open(SHM_NAME, O_RDONLY, 0);
if (fd == -1) {
printf("Re-open after unlink failed (expected): %s\n", strerror(errno));
} else {
printf("ERROR: re-open should have failed!\n");
close(fd);
}
/* Now unmap β this is when memory is truly freed */
munmap(addr, SHM_SIZE);
printf("munmap() called. Memory now truly freed.\n");
return 0;
}
Before unlink: 'Data before unlink'
shm_unlink() called. Name removed from /dev/shm.
After unlink: 'Data AFTER unlink β still works!'
Re-open after unlink failed (expected): No such file or directory
munmap() called. Memory now truly freed.
Forgetting to call shm_unlink() is a common source of shared memory leaks. Here are patterns to ensure cleanup happens reliably:
/*
* Pattern 1: Use atexit() to register cleanup
* Guarantees shm_unlink is called even if the program exits early.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#define SHM_NAME "/ep_safe_cleanup"
#define SHM_SIZE 1024
static char *g_addr = NULL;
void cleanup(void)
{
if (g_addr != NULL) {
munmap(g_addr, SHM_SIZE);
g_addr = NULL;
}
shm_unlink(SHM_NAME);
printf("Cleanup done via atexit().\n");
}
int main(void)
{
int fd;
atexit(cleanup); /* Register cleanup EARLY */
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }
ftruncate(fd, SHM_SIZE);
g_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (g_addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd);
/* Do work... */
printf("Doing work with shared memory...\n");
return 0; /* atexit handler will call cleanup() */
}
/*
* Pattern 2: Handle SIGINT/SIGTERM for graceful cleanup on Ctrl+C
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define SHM_NAME "/ep_signal_cleanup"
#define SHM_SIZE 1024
static volatile int g_running = 1;
void sig_handler(int sig)
{
(void)sig;
g_running = 0;
}
int main(void)
{
int fd;
char *addr;
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }
ftruncate(fd, SHM_SIZE);
addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd);
printf("Running... Press Ctrl+C to stop cleanly.\n");
while (g_running) {
/* main loop */
sleep(1);
}
/* Cleanup */
munmap(addr, SHM_SIZE);
shm_unlink(SHM_NAME);
printf("Cleaned up on signal. Exiting.\n");
return 0;
}
This is the most realistic pattern: shared memory for data transfer + POSIX named semaphore for synchronization. The producer writes items; the semaphore signals the consumer.
/*
* shm_producer.c
* Writes integers into shared memory, signals consumer via semaphore.
* Compile: gcc shm_producer.c -o shm_producer -lrt -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#define SHM_NAME "/ep_pc_shm"
#define SEM_NAME "/ep_pc_sem"
#define NUM_ITEMS 10
/* Layout of shared memory */
struct Channel {
int value; /* The data being transferred */
int done; /* Producer sets this to 1 at end */
};
int main(void)
{
int fd;
struct Channel *ch;
sem_t *sem;
/* Create shared memory */
fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0660);
if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }
ftruncate(fd, sizeof(struct Channel));
ch = mmap(NULL, sizeof(struct Channel),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ch == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd);
/* Create semaphore (initial value 0 β consumer waits) */
sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0660, 0);
if (sem == SEM_FAILED) { perror("sem_open"); exit(EXIT_FAILURE); }
ch->done = 0;
printf("[Producer] Sending %d items...\n", NUM_ITEMS);
for (int i = 1; i <= NUM_ITEMS; i++) {
ch->value = i * 10; /* Write data to shared memory */
printf("[Producer] Sent: %d\n", ch->value);
sem_post(sem); /* Signal consumer β one item ready */
usleep(200000); /* 200ms between items */
}
ch->done = 1;
sem_post(sem); /* Wake consumer one more time to check done flag */
printf("[Producer] All done. Cleaning up...\n");
sem_close(sem);
sem_unlink(SEM_NAME);
munmap(ch, sizeof(struct Channel));
shm_unlink(SHM_NAME);
return 0;
}
/*
* shm_consumer.c
* Waits on semaphore, reads values from shared memory.
* Compile: gcc shm_consumer.c -o shm_consumer -lrt -lpthread
* Run in a second terminal BEFORE the producer, or right after.
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <unistd.h>
#define SHM_NAME "/ep_pc_shm"
#define SEM_NAME "/ep_pc_sem"
struct Channel {
int value;
int done;
};
int main(void)
{
int fd;
struct Channel *ch;
sem_t *sem;
/* Wait for producer to create shm (retry loop) */
int retries = 10;
do {
fd = shm_open(SHM_NAME, O_RDONLY, 0);
if (fd != -1) break;
printf("[Consumer] Waiting for producer...\n");
sleep(1);
} while (--retries > 0);
if (fd == -1) { perror("shm_open"); exit(EXIT_FAILURE); }
ch = mmap(NULL, sizeof(struct Channel),
PROT_READ, MAP_SHARED, fd, 0);
if (ch == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); }
close(fd);
/* Open the semaphore (producer created it) */
sem = sem_open(SEM_NAME, 0);
if (sem == SEM_FAILED) { perror("sem_open"); exit(EXIT_FAILURE); }
printf("[Consumer] Receiving items...\n");
while (1) {
sem_wait(sem); /* Block until producer posts */
if (ch->done) {
printf("[Consumer] Producer signaled done. Exiting.\n");
break;
}
printf("[Consumer] Received: %d\n", ch->value);
}
sem_close(sem);
munmap(ch, sizeof(struct Channel));
return 0;
}
# Compile both
gcc shm_producer.c -o shm_producer -lrt -lpthread
gcc shm_consumer.c -o shm_consumer -lrt -lpthread
# Terminal 1: Start consumer first (it will wait)
./shm_consumer
# Terminal 2: Start producer
./shm_producer
| Function | Purpose | Key Note |
|---|---|---|
shm_open() |
Create or open a shared memory object | Returns fd; sets FD_CLOEXEC automatically |
ftruncate() |
Set the size of the object | Must be called before mmap(); new bytes = 0 |
mmap() |
Map the object into virtual address space | Use MAP_SHARED for IPC visibility |
munmap() |
Unmap the region from this process | Does NOT delete the object |
fstat() |
Get metadata (size, permissions, owner) | Works on the shm fd |
fchmod() |
Change permissions | Same as for files |
fchown() |
Change ownership | Requires appropriate privilege |
shm_unlink() |
Remove the shared memory object | Deferred: freed only when last mapping closes |
munmap() removes the mapping from the calling process’s virtual address space only β the shared memory object still exists in /dev/shm. shm_unlink() deletes the object’s name, preventing any new process from opening it. The actual memory is freed only when all processes that have it mapped call munmap() (or exit)./dev/shm immediately (no new opens are possible). However, processes that already have mappings can continue to use them normally. The underlying memory is freed only when the last mapping is unmapped β this is called deferred deletion, and it follows the same “last close frees” rule as regular files.shm_open() but never calls shm_unlink(). The object persists in /dev/shm consuming RAM until the system reboots. Prevention: always pair shm_open() + O_CREAT with a shm_unlink() in an atexit() handler or signal handler, and monitor /dev/shm in production systems.shm_open(O_CREAT|O_RDWR) β ftruncate() β mmap(MAP_SHARED). Consumer: shm_open(O_RDONLY) β mmap(PROT_READ, MAP_SHARED). Use a POSIX named semaphore (or a mutex with PTHREAD_PROCESS_SHARED) for synchronization. The producer writes data and posts the semaphore; the consumer waits on the semaphore and reads data.shm_open(O_CREAT | O_RDWR, ...) β get fd, (2) ftruncate(fd, size) β set size, (3) mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0) β map, (4) close(fd) β fd not needed after mmap, (5) use the mapped region, (6) munmap(addr, size) β unmap, (7) shm_unlink(name) β delete object.tmpfs). For cross-machine communication you need network IPC mechanisms such as sockets, message queues over a network, or shared file systems (NFS). For single-machine, multi-process: POSIX shm is ideal.POSIX shared memory solves two problems with older IPC techniques: System V shared memory’s non-standard API, and the need for a disk file with file-mapped shared memory. Key takeaways:
- shm_open() β creates or opens a shared memory object, returns a file descriptor with
FD_CLOEXEC - ftruncate() β must be called after creation because new objects have zero size; extended bytes are zero-initialized
- mmap(MAP_SHARED) β maps the object into virtual address space; writes are immediately visible to all sharing processes
- munmap() β removes the mapping from this process; does not delete the object
- shm_unlink() β removes the name; deferred deletion when last mapping closes
- Linux stores objects in tmpfs at
/dev/shmβ kernel persistence, lost on reboot - Always combine shared memory with a semaphore or mutex for correct synchronization
- Never store absolute pointers in shared memory β use offsets
