shm_unlink() & Complete POSIX shm Examples

shm_unlink() & Complete POSIX shm Examples
Chapter 54 β€” Part 5 of 5 | The Linux Programming Interface
shm_unlink
Remove Object
Producer
Writer Pattern
Consumer
Reader Pattern

πŸ“š Chapter 54 Series
Part 1: Introduction & Overview Part 2: shm_open() Part 3: ftruncate() & fstat() Part 4: mmap() with POSIX shm Part 5: shm_unlink() & Examples

πŸ”‘ Key Concepts in This Part
shm_unlink() Deferred Deletion Kernel Persistence Producer–Consumer POSIX Semaphore + shm Cleanup Best Practice Shared Memory Leaks /dev/shm management

shm_unlink() β€” Removing a Shared Memory Object

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.

πŸ“‹ shm_unlink() β€” Function Signature
#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.

⏳ Deferred Deletion β€” How unlink() Really Works

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.

t=0
Process A calls shm_open("/myshm", ...) and mmap(). Process B also mmaps same object.
t=1
Process A calls shm_unlink("/myshm"). Name removed from /dev/shm. But physical memory still alive β€” B is using it.
t=2
A new process cannot open the object (name is gone). Process B still reads/writes to its existing mapping β€” works fine.
t=3
Process B calls munmap() and exits. Now the physical memory is freed. Object truly gone.

πŸ’» Code Example: shm_unlink() Behavior Demonstration
/*
 * 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;
}
Expected output:

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.

🧹 Cleanup Best Practices

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;
}

πŸ’» Complete Example: Producer–Consumer with POSIX shm + Semaphore

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;
}
How to run:

# 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

πŸ“Š Complete POSIX Shared Memory API Summary
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

🎯 Interview Questions β€” shm_unlink() & Complete Picture
Q1. What is the difference between munmap() and shm_unlink()?
Answer: 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).
Q2. What happens if you call shm_unlink() while other processes still have the object mapped?
Answer: The name is removed from /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.
Q3. What is a shared memory leak, and how do you prevent it?
Answer: A shared memory leak occurs when a process creates a POSIX shm object with 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.
Q4. How would you implement a producer–consumer IPC using POSIX shared memory?
Answer: Define a shared struct in a header file. Producer: 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.
Q5. How does POSIX shared memory compare to a pipe for IPC?
Answer: A pipe is byte-stream, unidirectional, and automatically disappears when closed. Shared memory is faster (no copying), bidirectional, random-access, but requires explicit synchronization and explicit cleanup. Use pipes for simple sequential streaming; use shared memory when you need low-latency, high-throughput transfer of structured data.
Q6. What is the correct order of operations for a process that creates and uses POSIX shared memory?
Answer: (1) 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.
Q7. Why should you NOT use pointers inside a shared memory struct?
Answer: Because each process maps the shared memory at a different virtual address. A pointer stored by Process A (pointing into A’s virtual space) is meaningless or dangerous when read by Process B at a different base address. Instead, store offsets from the start of the shared region, or use fixed-size indices into arrays within the shared region.
Q8. Can two processes on different machines share POSIX shared memory?
Answer: No. POSIX shared memory is strictly local to one machine β€” it is backed by the machine’s RAM (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.

πŸŽ“ Chapter 54 β€” Complete Summary

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

Series Complete!
You have covered all of Chapter 54 β€” POSIX Shared Memory

← Part 4: mmap() Visit EmbeddedPathashala β†’

Leave a Reply

Your email address will not be published. Required fields are marked *