shmid_ds — The Shared Memory Metadata Structure Every field explained: Size · Timestamps · PIDs · Permissions · Attach Count

 

shmid_ds — The Shared Memory Metadata Structure
Every field explained: Size · Timestamps · PIDs · Permissions · Attach Count
📂 Section 48.8
🗃️ shmid_ds
🎯 Interview Ready

What is shmid_ds?

Every System V shared memory segment has a kernel-maintained metadata block called shmid_ds. The kernel updates this structure automatically whenever processes attach, detach, or modify the segment. You read it with shmctl(shmid, IPC_STAT, &buf) and partially update it with shmctl(shmid, IPC_SET, &buf).

Understanding shmid_ds is essential for monitoring, auditing, and debugging shared memory in multi-process applications.

Key Terms in This Topic

shmid_ds ipc_perm shm_segsz shm_atime shm_dtime shm_ctime shm_cpid shm_lpid shm_nattch shmatt_t SHM_DEST SHM_LOCKED

The shmid_ds Structure — Full Definition

/* Defined in <sys/shm.h> */

struct shmid_ds {
    struct ipc_perm  shm_perm;    /* Ownership and permissions          */
    size_t           shm_segsz;   /* Size of segment in bytes           */
    time_t           shm_atime;   /* Time of last shmat() call          */
    time_t           shm_dtime;   /* Time of last shmdt() call          */
    time_t           shm_ctime;   /* Time of last shmctl() change       */
    pid_t            shm_cpid;    /* PID of the creator process         */
    pid_t            shm_lpid;    /* PID of process that last called    */
                                  /*   shmat() or shmdt()               */
    shmatt_t         shm_nattch;  /* Number of currently attached       */
                                  /*   processes                        */
};

/* The nested ipc_perm structure (also used by msgid_ds and semid_ds) */
struct ipc_perm {
    key_t            __key;    /* Key passed to shmget()                */
    uid_t            uid;      /* Effective UID of owner (changeable)   */
    gid_t            gid;      /* Effective GID of owner (changeable)   */
    uid_t            cuid;     /* Effective UID of creator (fixed)      */
    gid_t            cgid;     /* Effective GID of creator (fixed)      */
    unsigned short   mode;     /* Permission bits + SHM_DEST/SHM_LOCKED */
    unsigned short   __seq;    /* Sequence number (kernel-internal)     */
};

/* shmatt_t: unsigned integer type for the attach count field */
    

Field-by-Field Reference
Field Type Set By IPC_SET? Meaning
shm_perm.uid uid_t shmget / IPC_SET ✅ Yes Effective UID of current owner. Initially set to creator’s EUID.
shm_perm.gid gid_t shmget / IPC_SET ✅ Yes Effective GID of current owner.
shm_perm.cuid uid_t shmget only ❌ No EUID of the creator. Fixed forever after creation.
shm_perm.mode unsigned short shmget / IPC_SET ✅ Yes (lower 9 bits) Permission bits (rwxrwxrwx). Upper bits hold SHM_DEST and SHM_LOCKED flags — always mask with 0777 when reading permissions.
shm_segsz size_t shmget only ❌ No Size in bytes as given to shmget(). Fixed forever. Physical allocation is rounded up to next page boundary.
shm_atime time_t shmat() ❌ No Timestamp of the most recent shmat() call. 0 until first attach.
shm_dtime time_t shmdt() ❌ No Timestamp of the most recent shmdt() call. 0 until first detach.
shm_ctime time_t shmget / IPC_SET ❌ No Timestamp of segment creation or last IPC_SET. Not updated by shmat/shmdt.
shm_cpid pid_t shmget only ❌ No PID of the process that called shmget(). Set once, never changes.
shm_lpid pid_t shmat / shmdt ❌ No PID of the process that most recently called shmat() or shmdt(). 0 until first attach.
shm_nattch shmatt_t shmat++ / shmdt– ❌ No Count of processes currently attached. IPC_RMID fires actual deletion when this reaches 0.

Special Flag Bits in shm_perm.mode

The mode field is 16 bits. The lower 9 bits are the standard Unix permission bits. The upper bits contain kernel state flags. Always mask with 0777 when reading permissions to avoid misinterpreting the flag bits.


/*
 * shm_perm.mode bit layout:
 *
 *  bits 15..11  reserved
 *  bit  10      SHM_LOCKED — set while SHM_LOCK is active
 *  bit  9       SHM_DEST   — set after IPC_RMID is called (pending deletion)
 *  bits 8..0    rwxrwxrwx  — standard Unix permission bits
 */

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

/* Linux flag constants (define if not in your headers) */
#ifndef SHM_DEST
#define SHM_DEST   01000   /* 0x0200 — segment pending deletion */
#endif
#ifndef SHM_LOCKED
#define SHM_LOCKED 02000   /* 0x0400 — segment locked in RAM    */
#endif

void check_flags(int shmid, const char *label)
{
    struct shmid_ds ds;
    shmctl(shmid, IPC_STAT, &ds);
    unsigned short m = ds.shm_perm.mode;
    printf("[%s] perms=%04o  SHM_DEST=%s  SHM_LOCKED=%s\n",
           label,
           m & 0777,
           (m & SHM_DEST)   ? "YES" : "no",
           (m & SHM_LOCKED) ? "YES" : "no");
}

int main(void)
{
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0644);
    check_flags(shmid, "fresh segment           ");

    /* Attach so segment survives IPC_RMID */
    char *addr = shmat(shmid, NULL, 0);

    shmctl(shmid, IPC_RMID, NULL);
    check_flags(shmid, "after IPC_RMID (attached)");

    if (shmctl(shmid, SHM_LOCK, NULL) == 0) {
        check_flags(shmid, "after SHM_LOCK          ");
        shmctl(shmid, SHM_UNLOCK, NULL);
        check_flags(shmid, "after SHM_UNLOCK        ");
    }

    shmdt(addr);
    return 0;
}
/*
 * Output:
 * [fresh segment           ] perms=0644  SHM_DEST=no    SHM_LOCKED=no
 * [after IPC_RMID (attached)] perms=0644  SHM_DEST=YES   SHM_LOCKED=no
 * [after SHM_LOCK          ] perms=0644  SHM_DEST=YES   SHM_LOCKED=YES
 * [after SHM_UNLOCK        ] perms=0644  SHM_DEST=YES   SHM_LOCKED=no
 */
    

The Three Timestamps — When Each is Updated

shmget() shm_ctime = now
shm_atime = 0
shm_dtime = 0
shmat() shm_atime = now
shm_lpid = current PID
shm_nattch++
shmdt() shm_dtime = now
shm_lpid = current PID
shm_nattch–
IPC_SET shm_ctime = now

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

static void ts(time_t t, const char *name)
{
    if (t == 0) printf("  %-30s : never\n", name);
    else        printf("  %-30s : %s", name, ctime(&t));
}

static void dump_times(int shmid, const char *label)
{
    struct shmid_ds ds;
    shmctl(shmid, IPC_STAT, &ds);
    printf("\n[%s]\n", label);
    ts(ds.shm_ctime, "shm_ctime (created/changed)");
    ts(ds.shm_atime, "shm_atime (last shmat)     ");
    ts(ds.shm_dtime, "shm_dtime (last shmdt)     ");
    printf("  %-30s : %lu\n", "shm_nattch", (unsigned long)ds.shm_nattch);
    printf("  %-30s : %d / %d\n", "cpid / lpid",
           (int)ds.shm_cpid, (int)ds.shm_lpid);
}

int main(void)
{
    int shmid = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
    dump_times(shmid, "After shmget");

    char *addr = shmat(shmid, NULL, 0);
    sleep(1);   /* make timestamps distinct */
    dump_times(shmid, "After shmat");

    shmdt(addr);
    sleep(1);
    dump_times(shmid, "After shmdt");

    /* IPC_SET updates shm_ctime */
    struct shmid_ds ds;
    shmctl(shmid, IPC_STAT, &ds);
    ds.shm_perm.mode = 0640;
    shmctl(shmid, IPC_SET, &ds);
    dump_times(shmid, "After IPC_SET");

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

Using shm_nattch to Coordinate Multi-Process Work

shm_nattch is incremented by every shmat() and decremented by every shmdt(). A manager process can poll this field to know when all workers have finished and detached.


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define NUM_WORKERS 4

int main(void)
{
    int  shmid;
    int *counter;   /* shared integer counter */

    shmid   = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0660);
    counter = shmat(shmid, NULL, 0);
    *counter = 0;

    printf("[Manager] Spawning %d workers\n", NUM_WORKERS);

    for (int i = 0; i < NUM_WORKERS; i++) {
        if (fork() == 0) {
            int *w = shmat(shmid, NULL, 0);
            sleep(1 + i % 3);    /* simulate variable workload */
            (*w)++;
            printf("[Worker %d] incremented counter, detaching\n", i);
            shmdt(w);
            exit(0);
        }
    }

    /* Poll shm_nattch until all workers have detached (nattch == 1, only manager) */
    struct shmid_ds ds;
    do {
        sleep(1);
        shmctl(shmid, IPC_STAT, &ds);
        printf("[Manager] shm_nattch = %lu\n", (unsigned long)ds.shm_nattch);
    } while (ds.shm_nattch > 1);

    printf("[Manager] All workers done. counter = %d\n", *counter);

    for (int i = 0; i < NUM_WORKERS; i++) wait(NULL);

    shmctl(shmid, IPC_RMID, NULL);
    shmdt(counter);
    return 0;
}
/*
 * Output:
 * [Manager] Spawning 4 workers
 * [Manager] shm_nattch = 5
 * [Worker 0] incremented counter, detaching
 * [Worker 1] incremented counter, detaching
 * [Manager] shm_nattch = 3
 * [Worker 2] incremented counter, detaching
 * [Worker 3] incremented counter, detaching
 * [Manager] shm_nattch = 1
 * [Manager] All workers done. counter = 4
 */
    

shm_segsz vs Actual Physical Memory Used

shm_segsz records the exact byte count you passed to shmget(). But the kernel allocates memory in whole pages (typically 4096 bytes). So a 1-byte segment still occupies one full page in RAM.


#include <stdio.h>
#include <sys/shm.h>
#include <unistd.h>

int main(void)
{
    long   page_size = sysconf(_SC_PAGESIZE);
    size_t sizes[]   = {1, 100, 4095, 4096, 4097, 8192, 10000};

    printf("Page size: %ld bytes\n\n", page_size);
    printf("%-12s  %-18s  %s\n",
           "Requested", "shm_segsz stores", "Physical pages used");
    printf("%-12s  %-18s  %s\n",
           "---------", "----------------", "-------------------");

    for (int i = 0; i < 7; i++) {
        int shmid = shmget(IPC_PRIVATE, sizes[i], IPC_CREAT | 0600);
        struct shmid_ds ds;
        shmctl(shmid, IPC_STAT, &ds);

        /* Round up to next page boundary */
        size_t actual = (ds.shm_segsz + page_size - 1)
                        & ~(page_size - 1);
        long   pages  = actual / page_size;

        printf("%-12zu  %-18zu  %ld page(s) = %zu bytes\n",
               sizes[i], ds.shm_segsz, pages, actual);

        shmctl(shmid, IPC_RMID, NULL);
    }
    return 0;
}
/*
 * Output (4096-byte pages):
 * Page size: 4096 bytes
 *
 * Requested     shm_segsz stores    Physical pages used
 * ---------     ----------------    -------------------
 * 1             1                   1 page(s) = 4096 bytes
 * 100           100                 1 page(s) = 4096 bytes
 * 4095          4095                1 page(s) = 4096 bytes
 * 4096          4096                1 page(s) = 4096 bytes
 * 4097          4097                2 page(s) = 8192 bytes
 * 8192          8192                2 page(s) = 8192 bytes
 * 10000         10000               3 page(s) = 12288 bytes
 */
    

Complete Utility: Full shmid_ds Dump Tool

/*
 * shm_dump.c — display all shmid_ds fields for a given shmid
 * Usage: ./shm_dump <shmid>
 * Compile: gcc -o shm_dump shm_dump.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>

#ifndef SHM_DEST
#define SHM_DEST   01000
#endif
#ifndef SHM_LOCKED
#define SHM_LOCKED 02000
#endif

static void print_ts(const char *label, time_t t)
{
    if (t == 0)
        printf("  %-28s : (never)\n", label);
    else {
        char buf[64];
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", localtime(&t));
        printf("  %-28s : %s\n", label, buf);
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        return 1;
    }

    int shmid = atoi(argv[1]);
    struct shmid_ds ds;

    if (shmctl(shmid, IPC_STAT, &ds) == -1) {
        perror("shmctl IPC_STAT");
        fprintf(stderr, "Segment shmid=%d does not exist or no permission\n", shmid);
        return 1;
    }

    long page = sysconf(_SC_PAGESIZE);
    size_t phys = (ds.shm_segsz + page - 1) & ~(page - 1);

    printf("==========================================================\n");
    printf("  shmid_ds Dump  —  shmid = %d\n", shmid);
    printf("==========================================================\n");
    printf("  PERMISSIONS & OWNERSHIP\n");
    printf("  %-28s : %u\n",    "Owner  UID", ds.shm_perm.uid);
    printf("  %-28s : %u\n",    "Owner  GID", ds.shm_perm.gid);
    printf("  %-28s : %u\n",    "Creator UID", ds.shm_perm.cuid);
    printf("  %-28s : %u\n",    "Creator GID", ds.shm_perm.cgid);
    printf("  %-28s : %04o\n",  "Permissions", ds.shm_perm.mode & 0777);
    printf("  %-28s : %s\n",    "SHM_DEST (IPC_RMID called)",
           (ds.shm_perm.mode & SHM_DEST)   ? "YES" : "no");
    printf("  %-28s : %s\n",    "SHM_LOCKED (SHM_LOCK active)",
           (ds.shm_perm.mode & SHM_LOCKED) ? "YES" : "no");
    printf("----------------------------------------------------------\n");
    printf("  SEGMENT INFO\n");
    printf("  %-28s : %zu bytes\n", "Logical size (shm_segsz)", ds.shm_segsz);
    printf("  %-28s : %zu bytes (%zu page(s))\n",
           "Physical allocation", phys, phys / page);
    printf("  %-28s : %lu\n",
           "Attached count (nattch)", (unsigned long)ds.shm_nattch);
    printf("----------------------------------------------------------\n");
    printf("  PROCESS INFO\n");
    printf("  %-28s : %d\n", "Creator PID (shm_cpid)", (int)ds.shm_cpid);
    printf("  %-28s : %d\n", "Last-op PID (shm_lpid)", (int)ds.shm_lpid);
    printf("----------------------------------------------------------\n");
    printf("  TIMESTAMPS\n");
    print_ts("Created/changed (ctime)", ds.shm_ctime);
    print_ts("Last shmat()    (atime)", ds.shm_atime);
    print_ts("Last shmdt()    (dtime)", ds.shm_dtime);
    printf("==========================================================\n");
    return 0;
}

/*
 * Sample run:
 *   $ ./make_shm &        # program that creates and holds a segment, prints shmid
 *   Created shmid=5
 *   $ ./shm_dump 5
 *   ==========================================================
 *     shmid_ds Dump  —  shmid = 5
 *   ==========================================================
 *     PERMISSIONS & OWNERSHIP
 *     Owner  UID              : 1000
 *     Owner  GID              : 1000
 *     Creator UID             : 1000
 *     Creator GID             : 1000
 *     Permissions             : 0660
 *     SHM_DEST ...            : no
 *     SHM_LOCKED ...          : no
 *   ----------------------------------------------------------
 *     SEGMENT INFO
 *     Logical size            : 8192 bytes
 *     Physical allocation     : 8192 bytes (2 page(s))
 *     Attached count          : 1
 *   ----------------------------------------------------------
 *     PROCESS INFO
 *     Creator PID             : 12345
 *     Last-op PID             : 12345
 *   ----------------------------------------------------------
 *     TIMESTAMPS
 *     Created/changed         : 2026-06-10 10:30:00
 *     Last shmat()            : 2026-06-10 10:30:00
 *     Last shmdt()            : (never)
 *   ==========================================================
 */
    

Reading shmid_ds From the Command Line — ipcs

/* List all shared memory segments (basic view) */
$ ipcs -m
/*
 * ------ Shared Memory Segments --------
 * key        shmid  owner  perms  bytes   nattch  status
 * 0x00000000 0      ravi   660    4096    2
 * 0x00000000 1      ravi   660    8192    0       dest
 *
 * "dest" in status = IPC_RMID was called (SHM_DEST flag set)
 */

/* Show timestamps (shm_atime, shm_dtime, shm_ctime) */
$ ipcs -m -t

/* Show PID info (shm_cpid, shm_lpid) */
$ ipcs -m -p

/* Show everything at once */
$ ipcs -m -a

/* Remove a specific orphaned segment (e.g. after crash) */
$ ipcrm -m 0         /* by shmid */
$ ipcrm -M 0x1234    /* by key   */

/* Remove all orphaned shm segments owned by current user (cleanup script) */
$ ipcs -m | awk 'NR>3 && $3==ENVIRON["USER"] {print $2}' | xargs -I{} ipcrm -m {}
    

Interview Questions — shmid_ds Structure
Q1: What is the shmid_ds structure and what is it used for?

Answer: It is the kernel-maintained metadata block for a System V shared memory segment. It records the segment’s size, owner/creator UIDs and GIDs, permission mode, the three operation timestamps (attach/detach/change), creator PID, last-operation PID, and current attach count. Applications read it with IPC_STAT to monitor or audit the segment and use IPC_SET to change owner/group/permissions.

Q2: What is the difference between shm_cpid and shm_lpid?

Answer: shm_cpid is the PID of the process that called shmget() to create the segment — set once at creation, never changes. shm_lpid is the PID of the process that most recently called shmat() or shmdt() — updated on every attach or detach. In a busy system, shm_lpid changes frequently.

Q3: What does shm_nattch represent? How is it used for resource management?

Answer: It is the count of processes currently attached to the segment. The kernel increments it on each shmat() and decrements it on each shmdt(). For resource management: after IPC_RMID is called, the segment is not physically deleted until shm_nattch drops to 0. A manager process can poll this field to synchronize — waiting until all workers have detached before proceeding with cleanup or shutdown.

Q4: What is the difference between shm_atime and shm_ctime?

Answer: shm_atime (“access time”) is updated every time any process calls shmat(). shm_ctime (“change time”) is updated when the segment is created and when IPC_SET changes its metadata. shm_ctime does not change on attach or detach operations.

Q5: Why is shm_segsz not always equal to the actual physical memory consumed?

Answer: shm_segsz stores the exact requested byte count from shmget(). The kernel allocates in whole pages (typically 4096 bytes), so the actual physical footprint is rounded up. A 1-byte segment uses a full 4096-byte page. The actual allocation is: (shm_segsz + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1).

Q6: Can the same ipc_perm structure appear in other System V IPC objects?

Answer: Yes. ipc_perm is the common ownership/permissions block shared by all three System V IPC mechanisms: it is the first member of shmid_ds (shared memory), msqid_ds (message queues), and semid_ds (semaphore sets). This common structure enables uniform permission checking across all three mechanisms.

Q7: How can you check from code whether a segment has been marked for deletion?

Answer:

struct shmid_ds ds;
shmctl(shmid, IPC_STAT, &ds);
if (ds.shm_perm.mode & SHM_DEST)
    printf("Segment is marked for deletion\n");

The SHM_DEST flag (octal 01000) is set in the mode field when IPC_RMID has been called. From the command line, ipcs -m shows “dest” in the status column for such segments.

Q8: A program calls shmctl(IPC_STAT) and gets EACCES. What does this mean?

Answer: The calling process does not have read permission on the segment. IPC_STAT requires at minimum read permission (the ‘r’ bit set for the appropriate user/group/other category based on the process’s effective UID/GID vs the segment’s owner/group). Either the permissions must be changed by the owner (via IPC_SET) or the process must run with appropriate credentials.

Chapter 48 Series Complete!

You now understand the virtual memory layout of shared memory processes, safe pointer storage, all shmctl() operations, and the complete shmid_ds metadata structure.

← Back: shmctl() Operations EmbeddedPathashala Home

Leave a Reply

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