shmctl() — Shared Memory Control Operations IPC_RMID · IPC_STAT · IPC_SET · SHM_LOCK · SHM_UNLOCK

 

shmctl() — Shared Memory Control Operations
IPC_RMID · IPC_STAT · IPC_SET · SHM_LOCK · SHM_UNLOCK
📂 Section 48.7
🔧 5 Operations
🎯 Interview Ready

What is shmctl()?

After creating and attaching a shared memory segment, you need a way to manage its lifecycle — delete it when done, inspect its current metadata, change its permissions, or lock its pages into RAM for performance. The shmctl() system call is the control interface for all of this.

Think of it as the “admin panel” for a System V shared memory segment.

Key Terms in This Topic

shmctl() IPC_RMID IPC_STAT IPC_SET SHM_LOCK SHM_UNLOCK shm_nattch CAP_IPC_LOCK RLIMIT_MEMLOCK page fault swap shmid_ds

shmctl() Function Signature and Overview

#include <sys/types.h>    /* for portability */
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
 * Returns: 0 on success, -1 on error (errno is set)
 *
 * shmid  — identifier returned by shmget()
 * cmd    — the operation to perform (see table below)
 * buf    — required for IPC_STAT (kernel writes into it)
 *           required for IPC_SET  (you write values into it)
 *           pass NULL for IPC_RMID, SHM_LOCK, SHM_UNLOCK
 */
    
IPC_RMID
Mark segment for deletion. Deleted when last process detaches.
IPC_STAT
Read metadata (size, perms, timestamps, attach count) into buf.
IPC_SET
Update owner, group, and permissions from buf.
SHM_LOCK
Lock segment pages into RAM. No swapping.
SHM_UNLOCK
Unlock pages. Kernel may swap them out again.

IPC_RMID — Deleting a Shared Memory Segment

IPC_RMID marks the segment for deletion. What happens immediately depends on the current attach count:

shm_nattch == 0 (no processes attached)

Deletion is immediate. The segment’s memory is freed and the shmid is invalidated right away.

shm_nattch > 0 (processes still attached)

The segment is marked for deletion (SHM_DEST flag is set in mode) but stays alive. It is finally removed when the last process calls shmdt() and nattch drops to 0. All currently-attached processes can still use it normally.

Linux-specific: On Linux, even after IPC_RMID is called, another process can still call shmat() on the segment if nattch > 0. Most other UNIX implementations forbid new attaches on a deleted segment. This Linux behavior is non-portable — do not rely on it in portable code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>

int main(void)
{
    int   shmid;
    char *addr;

    shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
    addr  = shmat(shmid, NULL, 0);
    strcpy(addr, "Hello");

    printf("Before IPC_RMID: shmid=%d  data='%s'\n", shmid, addr);

    /*
     * Best practice: call IPC_RMID immediately after all processes have
     * attached — even before doing any actual work with the segment.
     * This way, if the program crashes, the kernel still cleans up.
     * (Without IPC_RMID, a crash leaves an orphaned segment that must
     *  be removed manually with: ipcrm -m )
     *
     * Analogous to: fd = open(...); unlink(path); use(fd); close(fd);
     */
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl IPC_RMID");
        exit(EXIT_FAILURE);
    }

    /* Segment is still accessible through 'addr' — data is intact */
    printf("After IPC_RMID:  data='%s'  (still accessible!)\n", addr);

    strcpy(addr, "Bye");
    printf("Still writable:  data='%s'\n", addr);

    /* Detach — this triggers actual physical removal since IPC_RMID was set */
    shmdt(addr);

    /* Confirm: trying to attach again fails */
    if (shmat(shmid, NULL, 0) == (void *)-1)
        printf("Confirmed: segment gone after shmdt() (as expected)\n");

    return 0;
}
/*
 * Compile: gcc -o rmid_demo rmid_demo.c
 * Output:
 * Before IPC_RMID: shmid=3  data='Hello'
 * After IPC_RMID:  data='Hello'  (still accessible!)
 * Still writable:  data='Bye'
 * Confirmed: segment gone after shmdt() (as expected)
 */
    

/* PATTERN: mark for deletion immediately after all processes attach */
int shmid = shmget(key, size, IPC_CREAT | 0660);

char *p_addr = shmat(shmid, NULL, 0);   /* process 1 attaches */
/* ... fork or communicate shmid to process 2 ... */
char *q_addr = shmat(shmid, NULL, 0);   /* process 2 attaches (in another fork/process) */

shmctl(shmid, IPC_RMID, NULL);          /* mark NOW — both are attached so nothing dies yet */

/* Both p_addr and q_addr remain fully usable.
   Segment disappears automatically when both call shmdt(). */
    

IPC_STAT — Reading Segment Metadata

IPC_STAT fills a shmid_ds buffer you provide with a snapshot of the segment’s current metadata. Requires read permission on the segment.


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#include <sys/shm.h>

void show_info(int shmid, const char *label)
{
    struct shmid_ds ds;
    if (shmctl(shmid, IPC_STAT, &ds) == -1) { perror("IPC_STAT"); return; }

    printf("\n--- %s ---\n", label);
    printf("  Segment size  : %zu bytes\n",    ds.shm_segsz);
    printf("  Permissions   : %04o\n",          ds.shm_perm.mode & 0777);
    printf("  Owner UID/GID : %u/%u\n",         ds.shm_perm.uid, ds.shm_perm.gid);
    printf("  Creator PID   : %d\n",            (int)ds.shm_cpid);
    printf("  Last-op PID   : %d\n",            (int)ds.shm_lpid);
    printf("  Attach count  : %lu\n",           (unsigned long)ds.shm_nattch);
    printf("  Last shmat()  : %s",              ds.shm_atime ? ctime(&ds.shm_atime) : "never\n");
    printf("  Last shmdt()  : %s",              ds.shm_dtime ? ctime(&ds.shm_dtime) : "never\n");
    printf("  Last changed  : %s",              ctime(&ds.shm_ctime));
}

int main(void)
{
    int   shmid = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0640);
    char *addr;

    show_info(shmid, "Right after shmget");

    addr = shmat(shmid, NULL, 0);
    show_info(shmid, "After shmat");

    shmdt(addr);
    show_info(shmid, "After shmdt");

    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}
/*
 * Output snippets:
 * --- Right after shmget ---
 *   Segment size  : 8192 bytes
 *   Attach count  : 0
 *   Last shmat()  : never
 *
 * --- After shmat ---
 *   Attach count  : 1
 *   Last shmat()  : Wed Jun 10 10:30:00 2026
 *
 * --- After shmdt ---
 *   Attach count  : 0
 *   Last shmdt()  : Wed Jun 10 10:30:01 2026
 */
    

IPC_SET — Changing Permissions and Ownership

IPC_SET updates the owner UID, owner GID, and permission bits of the segment. You must first read the current values with IPC_STAT, modify the fields you want to change, then call IPC_SET. Only the owner, creator, or root can do this.


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/shm.h>

int main(void)
{
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    struct shmid_ds ds;

    /* Step 1: read current values */
    shmctl(shmid, IPC_STAT, &ds);
    printf("Before IPC_SET: mode = %04o\n", ds.shm_perm.mode & 0777);

    /* Step 2: modify the field(s) you want to change */
    ds.shm_perm.mode = 0660;   /* allow group read+write */
    /* ds.shm_perm.uid = new_owner_uid;  (to change owner) */
    /* ds.shm_perm.gid = new_group_gid;  (to change group) */

    /* Step 3: apply with IPC_SET */
    if (shmctl(shmid, IPC_SET, &ds) == -1) {
        perror("shmctl IPC_SET");
        exit(EXIT_FAILURE);
    }

    /* Step 4: verify */
    shmctl(shmid, IPC_STAT, &ds);
    printf("After  IPC_SET: mode = %04o\n", ds.shm_perm.mode & 0777);

    /*
     * Fields you CAN change with IPC_SET:
     *   shm_perm.uid   — owner user ID
     *   shm_perm.gid   — owner group ID
     *   shm_perm.mode  — lower 9 permission bits
     *
     * Fields you CANNOT change:
     *   shm_segsz  — size is fixed at shmget() time forever
     *   shm_cpid   — creator PID, kernel-maintained read-only
     *   shm_nattch — kernel-maintained attach count
     */

    shmctl(shmid, IPC_RMID, NULL);
    return 0;
}
/* Output:
 * Before IPC_SET: mode = 0600
 * After  IPC_SET: mode = 0660
 */
    

SHM_LOCK and SHM_UNLOCK — Preventing Swap-Out

Under memory pressure, the kernel may swap out memory pages to disk. When a process accesses a swapped-out page, a page fault occurs and the kernel must reload it from disk — adding unpredictable latency. For real-time or performance-critical applications this is unacceptable. SHM_LOCK prevents the kernel from ever swapping out pages of the segment.

Without SHM_LOCK
Pages live in RAM initially
↓ memory pressure
Pages swapped to disk
↓ process reads page
Page fault → disk I/O → millisecond delay!
vs
With SHM_LOCK
SHM_LOCK flag set on segment
↓ pages faulted in on first access
Pages stay in RAM permanently
↓ always
No page faults on subsequent access — guaranteed low latency!
Important subtlety: SHM_LOCK sets a property of the segment, not of the calling process. Even if the calling process detaches, the pages remain locked. They stay locked until SHM_UNLOCK is called or the segment is deleted. Also, calling SHM_LOCK does not immediately bring all pages into RAM — pages are locked in one-by-one as they are first accessed (faulted in). After being faulted in, they never swap out again.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/resource.h>

#define SHM_SIZE  (1024 * 1024)   /* 1 MB */

int main(void)
{
    int   shmid;
    char *addr;

    /*
     * Since Linux 2.6.10, unprivileged processes can use SHM_LOCK
     * if RLIMIT_MEMLOCK is large enough. Raise the soft limit first.
     */
    struct rlimit rl;
    getrlimit(RLIMIT_MEMLOCK, &rl);
    printf("RLIMIT_MEMLOCK: soft=%lu hard=%lu bytes\n",
           (unsigned long)rl.rlim_cur, (unsigned long)rl.rlim_max);

    rl.rlim_cur = rl.rlim_max;   /* raise soft to match hard (no root needed) */
    if (setrlimit(RLIMIT_MEMLOCK, &rl) == -1)
        perror("setrlimit (continuing anyway)");

    shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0660);
    addr  = shmat(shmid, NULL, 0);

    /* Lock the segment */
    if (shmctl(shmid, SHM_LOCK, NULL) == -1) {
        perror("SHM_LOCK failed — need CAP_IPC_LOCK or higher RLIMIT_MEMLOCK");
    } else {
        printf("SHM_LOCK: segment locked into RAM\n");

        /*
         * Touch every page to fault them all in.
         * After this no page in the segment will ever be swapped.
         * We access one byte per 4096-byte page to trigger the fault.
         */
        for (size_t i = 0; i < SHM_SIZE; i += 4096)
            addr[i] = 0;

        printf("All %zu pages faulted in and permanently resident\n",
               SHM_SIZE / 4096);

        /* Do latency-critical work here — no swap delays */
        memset(addr, 0xAB, SHM_SIZE);
        printf("Wrote 1 MB — no page-fault delays\n");

        /* Unlock when no longer needed */
        if (shmctl(shmid, SHM_UNLOCK, NULL) == -1)
            perror("SHM_UNLOCK");
        else
            printf("SHM_UNLOCK: pages may be swapped again\n");
    }

    shmctl(shmid, IPC_RMID, NULL);
    shmdt(addr);
    return 0;
}
    

/*
 * Privilege rules for SHM_LOCK / SHM_UNLOCK:
 *
 * Before Linux 2.6.10:
 *   Requires CAP_IPC_LOCK capability (root only).
 *
 * Since Linux 2.6.10 (unprivileged process may call if):
 *   1. Effective UID matches the owner OR creator UID of the segment, AND
 *   2. Total bytes to be locked <= RLIMIT_MEMLOCK resource limit.
 *
 * Check limit from shell:
 *   $ ulimit -l          (shows RLIMIT_MEMLOCK in KB, default often 64KB)
 *
 * Raise for current shell session:
 *   $ ulimit -l unlimited   (only if the hard limit permits)
 *
 * Note: SHM_LOCK/UNLOCK are Linux-specific. Not in SUSv3/POSIX.
 *       Not available on all UNIX implementations.
 */
    

Quick Reference — All shmctl() Commands
cmd buf Who Can Call Effect POSIX?
IPC_RMID NULL Owner, creator, or root Mark for deletion. Deleted immediately if nattch==0; otherwise when last shmdt() happens. ✅ Yes
IPC_STAT shmid_ds * (output) Read permission on segment Copy current shmid_ds into buf. Inspect size, perms, timestamps, nattch. ✅ Yes
IPC_SET shmid_ds * (input) Owner, creator, or root Update uid, gid, mode from buf. Cannot change size. ✅ Yes
SHM_LOCK NULL Root, or owner/creator with sufficient RLIMIT_MEMLOCK (Linux ≥ 2.6.10) Set lock flag on segment. Pages locked in RAM as they are faulted in. Property of segment, not process. ❌ Linux only
SHM_UNLOCK NULL Same as SHM_LOCK Remove lock flag. Kernel may now swap out pages. ❌ Linux only

Comprehensive Example — All Five Operations in One Program

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/shm.h>

static void stat_print(int shmid, const char *label)
{
    struct shmid_ds ds;
    shmctl(shmid, IPC_STAT, &ds);
    printf("[%s] mode=%04o  nattch=%lu  size=%zu\n",
           label,
           ds.shm_perm.mode & 0777,
           (unsigned long)ds.shm_nattch,
           ds.shm_segsz);
}

int main(void)
{
    int            shmid;
    char          *addr;
    struct shmid_ds ds;

    /* Create */
    shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0600);
    printf("Created: shmid=%d\n", shmid);

    /* IPC_STAT before attach */
    stat_print(shmid, "IPC_STAT before shmat");

    /* Attach */
    addr = shmat(shmid, NULL, 0);
    strcpy(addr, "Hello shmctl demo");
    stat_print(shmid, "IPC_STAT after shmat ");

    /* IPC_SET: widen permissions */
    shmctl(shmid, IPC_STAT, &ds);
    ds.shm_perm.mode = 0660;
    shmctl(shmid, IPC_SET, &ds);
    stat_print(shmid, "IPC_STAT after IPC_SET");

    /* SHM_LOCK */
    if (shmctl(shmid, SHM_LOCK, NULL) == 0) {
        printf("[SHM_LOCK ] locked into RAM\n");

        /* SHM_UNLOCK */
        if (shmctl(shmid, SHM_UNLOCK, NULL) == 0)
            printf("[SHM_UNLOCK] unlocked\n");
    } else {
        perror("[SHM_LOCK ]");
    }

    /* IPC_RMID */
    shmctl(shmid, IPC_RMID, NULL);
    printf("[IPC_RMID ] marked for deletion\n");
    stat_print(shmid, "IPC_STAT after IPC_RMID");

    /* Detach — triggers actual removal */
    shmdt(addr);
    printf("shmdt() called — segment fully removed\n");
    return 0;
}
    

Interview Questions — shmctl() Operations
Q1: What does IPC_RMID do? Is deletion immediate?

Answer: It marks the segment for deletion. If shm_nattch == 0 (no process has it attached), deletion is immediate. If one or more processes are attached, the kernel sets the SHM_DEST flag and defers actual removal until shm_nattch drops to 0 — i.e., until the last process calls shmdt().

Q2: Why should you call IPC_RMID immediately after all processes attach?

Answer: As a safety cleanup pattern. If the program crashes before reaching a cleanup routine, an orphaned System V shared memory segment remains in the system consuming memory. It must then be removed manually with ipcrm -m shmid. By calling IPC_RMID right after all processes attach, you ensure the kernel removes the segment automatically when all processes detach — even on abnormal termination. This is the same idiom as open() + unlink() for temporary files.

Q3: What fields of shmid_ds can be changed with IPC_SET?

Answer: Only three: shm_perm.uid (owner user ID), shm_perm.gid (owner group ID), and shm_perm.mode (permission bits). The segment size (shm_segsz), creator PID (shm_cpid), attach count (shm_nattch), and timestamps are all kernel-maintained and cannot be set via IPC_SET.

Q4: What is the difference between SHM_LOCK and mlock()?

Answer: Both lock memory pages into RAM to prevent swapping but with different scopes. SHM_LOCK is a property of the shared memory segment — it persists regardless of which process holds the lock, and remains active even after the calling process detaches. mlock() is a property of the calling process’s address range — it applies to a specific virtual memory range in that process and is released when the process unmaps or exits.

Q5: Does SHM_LOCK immediately bring all segment pages into RAM?

Answer: No. SHM_LOCK only sets a lock flag on the segment. Individual pages are locked in RAM only as they are first accessed (page faulted in). To guarantee all pages are resident, touch every page after calling SHM_LOCK — e.g., write one byte per 4096-byte page. Once faulted in with the lock active, those pages will not be swapped out again.

Q6: Who can call SHM_LOCK on Linux?

Answer: Before kernel 2.6.10: only processes with CAP_IPC_LOCK capability (typically root). Since kernel 2.6.10: an unprivileged process can also call it if its effective UID matches the owner or creator UID of the segment, AND the locked bytes do not exceed RLIMIT_MEMLOCK. SHM_LOCK is Linux-specific and not in POSIX/SUSv3.

Q7: Can a process attach a segment already marked for deletion with IPC_RMID on Linux?

Answer: Yes — on Linux specifically, if shm_nattch > 0 (at least one process is still attached), another process can still call shmat() on the segment even after IPC_RMID. On most other UNIX systems this would fail. This Linux-specific behavior is non-portable and should not be relied upon in portable code.

Q8: How do you find how many processes currently have a segment attached from code?

Answer: Call shmctl(shmid, IPC_STAT, &ds) and read ds.shm_nattch. From the command line, ipcs -m shows the nattch column for every System V shared memory segment on the system. This field is incremented by the kernel on each shmat() and decremented on each shmdt().

Next: The shmid_ds Structure — Every Field Explained

Deep-dive into all eight fields, timestamps, mode flags, and a complete monitoring utility.

→ shmid_ds Data Structure ← Pointers in Shared Memory

Leave a Reply

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