Removing POSIX Shared Memory shm_unlink(), munmap(), Cleanup Patterns

 

Removing POSIX Shared Memory
Chapter 54 — Part 4: shm_unlink(), munmap(), Cleanup Patterns
Topic
Cleanup & Removal
Key Call
shm_unlink()
Persistence
Kernel-level

Why Cleanup Matters for Shared Memory

POSIX shared memory objects have kernel persistence — they outlive the processes that create them. A shared memory object created today will still be on the system after your program crashes, exits normally, or is killed with a signal. On Linux, it lives in /dev/shm/ and occupies physical RAM until explicitly removed.

If you forget to remove shared memory objects, they accumulate over time, consuming RAM. This is a common source of resource leaks in embedded and daemon applications. Proper cleanup involves two separate steps: munmap() (per-process) and shm_unlink() (system-wide removal).

Key Terms
shm_unlink() munmap() kernel persistence /dev/shm deferred deletion SIGINT handler atexit() resource leak

munmap() vs shm_unlink() — What Each Does
Operation Scope Effect on Physical Memory Effect on Object Name
munmap(addr, size) This process only Removes mapping from this process’s VMA. Physical pages still exist. No effect on name. Object still accessible via shm_open() by others.
shm_unlink(name) System-wide Deferred: pages freed only after ALL processes munmap or exit. Removes name from /dev/shm. New shm_open() calls fail with ENOENT.
Process A
mapped addr_a
/demo_shm
Physical Pages
/dev/shm/demo_shm
Process B
mapped addr_b
munmap(addr_a) → only A loses mapping munmap(addr_b) → B loses mapping
+ shm_unlink → name gone

shm_unlink() API
#include <sys/mman.h>

int shm_unlink(const char *name);
/* Returns: 0 on success, -1 on error */

shm_unlink() works exactly like unlink() on regular files: it removes the name from the filesystem. If no process currently has the object mapped, the underlying memory is freed immediately. If one or more processes still have it mapped, the memory is freed only after the last mapping is removed.

pshm_unlink.c — Standalone Unlink Program

From TLPI Listing 54-4. A simple utility to remove a named shared memory object:

/* pshm_unlink.c
 * Removes a POSIX shared memory object by name.
 * Usage: ./pshm_unlink shm-name
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, char *argv[])
{
    if (argc != 2 || strcmp(argv[1], "--help") == 0) {
        fprintf(stderr, "Usage: %s shm-name\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if (shm_unlink(argv[1]) == -1) {
        perror("shm_unlink");
        exit(EXIT_FAILURE);
    }

    printf("Removed shared memory object: %s\n", argv[1]);
    exit(EXIT_SUCCESS);
}
# Create and then remove
./pshm_create -c /test_shm 1024
ls -l /dev/shm/test_shm

./pshm_unlink /test_shm
ls -l /dev/shm/           # test_shm is gone

Deferred Deletion: Unlink While Still Mapped

Like unlink() on regular files, calling shm_unlink() while the object is still mapped does not immediately free the memory. The object is marked for deletion, but existing mappings remain valid. The pages are freed only after the last munmap().

This technique is useful: unlink right after creating the object so it is automatically cleaned up even if your program crashes.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define SHM_NAME "/deferred_demo"
#define SIZE     1024

int main(void)
{
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
    if (fd == -1) { perror("shm_open"); exit(1); }

    ftruncate(fd, SIZE);

    char *addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) { perror("mmap"); exit(1); }
    close(fd);

    /* Unlink the name NOW — even before using the memory.
     * The mapping is still valid.
     * The object will be truly deleted when munmap() is called. */
    shm_unlink(SHM_NAME);
    printf("Name unlinked — but mapping still valid.\n");

    /* Use the memory normally */
    strcpy(addr, "deferred deletion example");
    printf("Data in memory: %s\n", addr);

    /* When we unmap, the physical memory is finally freed */
    munmap(addr, SIZE);
    printf("munmap() called — physical memory now freed.\n");

    /* Trying to access addr here would be undefined behavior */
    return 0;
}

Cleanup on Signals: Unlink on SIGINT

In long-running programs or daemons, you must handle signals like SIGINT (Ctrl+C) and SIGTERM to clean up shared memory before exit:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <signal.h>

#define SHM_NAME "/daemon_shm"
#define SHM_SIZE 4096

static char *g_addr = NULL;

void cleanup_handler(int signum)
{
    (void)signum;
    printf("\nCaught signal, cleaning up...\n");

    if (g_addr) {
        munmap(g_addr, SHM_SIZE);
        g_addr = NULL;
    }
    shm_unlink(SHM_NAME);
    printf("Shared memory removed.\n");
    exit(0);
}

int main(void)
{
    /* Register signal handlers for cleanup */
    signal(SIGINT,  cleanup_handler);
    signal(SIGTERM, cleanup_handler);

    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
    if (fd == -1) { perror("shm_open"); exit(1); }
    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(1); }
    close(fd);

    printf("Shared memory created. Press Ctrl+C to clean up.\n");

    int counter = 0;
    while (1) {
        snprintf(g_addr, SHM_SIZE, "count=%d", counter++);
        sleep(1);
    }

    return 0;
}

Cleanup with atexit()

For normal exits (not signals), register a cleanup function with atexit():

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

#define SHM_NAME "/atexit_demo"
#define SHM_SIZE 512

static char *g_addr  = NULL;
static size_t g_size = 0;

void shm_cleanup(void)
{
    if (g_addr) {
        munmap(g_addr, g_size);
        g_addr = NULL;
    }
    shm_unlink(SHM_NAME);
    printf("atexit: shared memory cleaned up.\n");
}

int main(void)
{
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
    if (fd == -1) { perror("shm_open"); exit(1); }
    ftruncate(fd, SHM_SIZE);

    g_size = SHM_SIZE;
    g_addr = mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (g_addr == MAP_FAILED) { perror("mmap"); exit(1); }
    close(fd);

    /* Register cleanup — called when main() returns or exit() is called */
    atexit(shm_cleanup);

    /* Use shared memory */
    snprintf(g_addr, SHM_SIZE, "Application data here");
    printf("Data: %s\n", g_addr);

    /* shm_cleanup() called automatically on return */
    return 0;
}

Checking for Leftover Shared Memory Objects

On Linux, all POSIX shared memory objects are visible in /dev/shm/:

# List all shared memory objects
ls -l /dev/shm/

# Check size and permissions
stat /dev/shm/demo_shm

# Manually remove a leftover object
rm /dev/shm/demo_shm
# OR equivalently:
./pshm_unlink /demo_shm

Note: On non-Linux POSIX systems, the location may differ. The portable way is always shm_unlink(), not rm.

Complete Pattern: Create → Use → Cleanup

This is the full recommended pattern combining all steps:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

#define SHM_NAME "/complete_demo"

int main(void)
{
    int    fd;
    char  *addr;
    size_t size = 256;

    /* 1. Create */
    fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0600);
    if (fd == -1) { perror("shm_open"); exit(1); }

    /* 2. Size */
    if (ftruncate(fd, size) == -1) { perror("ftruncate"); exit(1); }

    /* 3. Map */
    addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) { perror("mmap"); exit(1); }

    /* 4. Close fd (mapping stays) */
    close(fd);

    /* 5. Use */
    snprintf(addr, size, "IPC message: value=%d", 42);
    printf("Written: %s\n", addr);

    /* 6. Unmap */
    if (munmap(addr, size) == -1) { perror("munmap"); exit(1); }

    /* 7. Unlink (system-wide cleanup) */
    if (shm_unlink(SHM_NAME) == -1) { perror("shm_unlink"); exit(1); }

    printf("Shared memory removed successfully.\n");
    return 0;
}

Interview Questions & Answers

Q1. What is the difference between munmap() and shm_unlink()?

munmap() removes the mapping from the calling process’s address space only — it is a per-process operation. shm_unlink() removes the object’s name from the filesystem system-wide, preventing any new process from opening it. The physical memory is freed only after all processes have unmapped the object (either via munmap() or by exiting). Both operations are needed for complete cleanup.

Q2. What happens to shared memory if a process crashes without calling shm_unlink()?

The object persists. Because POSIX shared memory has kernel persistence, a crash does not remove the object. It continues to exist in /dev/shm/ and consume RAM until explicitly removed with shm_unlink() or the system reboots. This is why cleanup in signal handlers and atexit() functions is important.

Q3. Can you call shm_unlink() and then still use the existing mapping?

Yes. This is the deferred deletion pattern, analogous to how unlink() on regular files works. After shm_unlink(), the name is removed (no new shm_open() calls can find it), but all currently-mapped processes retain valid access through their existing pointers until they call munmap().

Q4. What error does shm_unlink() return if the object does not exist?

It returns -1 with errno set to ENOENT (No such file or directory). Always check the return value of shm_unlink(), especially during error cleanup where the object may not have been successfully created.

Q5. How can you ensure shared memory is cleaned up even if the program is killed by SIGKILL?

SIGKILL cannot be caught or ignored — there is no way to run cleanup code when a process is killed with SIGKILL. The best strategy is the deferred deletion pattern: call shm_unlink() immediately after creating and mapping the object. This ensures the name is removed right away, and the memory is freed automatically when the process (and thus its mappings) is destroyed by the kernel.

Q6. If process A calls shm_unlink() and process B still has the object mapped and tries to write to it, what happens?

Process B’s existing mapping remains fully valid — it can continue to read and write through its pointer. shm_unlink() only removes the name. The physical pages backing the mapping are not freed until process B also unmaps. Any attempt by a third process to open the object by name after shm_unlink() will fail with ENOENT.

Q7. What is a practical way to detect shared memory leaks on Linux?

Run ls -l /dev/shm/ or df /dev/shm to see all current shared memory objects and how much space they consume. Each file in /dev/shm/ corresponds to a POSIX shared memory object. Stale files there after application crashes indicate cleanup was not performed.

Continue Learning

Next: Synchronizing shared memory access with POSIX semaphores

Part 5: Synchronization → ← Part 3: Reading

Leave a Reply

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