System V Semaphores Part 3: The semid_ds Data Structure

 

System V Semaphores
Chapter 47 — Part 3: The semid_ds Data Structure
47.4
TLPI Chapter Section
semid_ds
Kernel Metadata
IPC_STAT
How to Access It

What is semid_ds?

Every System V semaphore set has an associated kernel data structure called semid_ds. It holds metadata about the set — who owns it, when it was last used, how many semaphores it has, and what permissions are set.

You access this structure using semctl() with IPC_STAT (read) or IPC_SET (partial write). Understanding its fields is essential for writing robust semaphore-based IPC code.

The semid_ds Structure — Definition
struct semid_ds {
    struct ipc_perm sem_perm;  /* Ownership and permission info */
    time_t          sem_otime; /* Time of last semop() call    */
    time_t          sem_ctime; /* Time of last change (semctl) */
    unsigned long   sem_nsems; /* Number of semaphores in set  */
};

The inner struct ipc_perm holds ownership and permission details:

struct ipc_perm {
    key_t          __key;  /* Key passed to semget()         */
    uid_t          uid;    /* Owner's effective user ID      */
    gid_t          gid;    /* Owner's effective group ID     */
    uid_t          cuid;   /* Creator's effective user ID    */
    gid_t          cgid;   /* Creator's effective group ID   */
    unsigned short mode;   /* Permission bits (lower 9 bits) */
    unsigned short __seq;  /* Sequence number (internal use) */
};

semid_ds Field Reference
Field Type Updated by Meaning
sem_perm.uid uid_t IPC_SET, creator Current owner UID
sem_perm.gid gid_t IPC_SET, creator Current owner GID
sem_perm.cuid uid_t semget() only Creator UID — never changes
sem_perm.cgid gid_t semget() only Creator GID — never changes
sem_perm.mode unsigned short semget(), IPC_SET Permission bits (use & 0777)
sem_otime time_t semop() Time of last semop() call. 0 if no semop yet.
sem_ctime time_t semget(), SETVAL, SETALL, IPC_SET Time of last change to the set (creation or metadata change)
sem_nsems unsigned long semget() only Number of semaphores in the set. Fixed after creation.

sem_otime vs sem_ctime — Understanding the Difference

Event sem_otime updated? sem_ctime updated?
semget() — set created No (stays 0) Yes
semop() — semaphore operated on Yes No
semctl(SETVAL) No Yes
semctl(SETALL) No Yes
semctl(IPC_SET) No Yes

Practical use of sem_otime: A process can use sem_otime == 0 to detect that a newly created semaphore set has not yet been initialized by the creator. This is a classic pattern to avoid using uninitialized semaphores in multi-process startup.

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

union semun {
    int              val;
    struct semid_ds *buf;
    unsigned short  *array;
};

/*
 * Wait until the semaphore set has been initialized by another process.
 * Convention: creator signals readiness by calling semop() once,
 * which sets sem_otime to non-zero.
 *
 * A more robust approach is to use sem_otime != 0 as the readiness flag.
 */
void wait_for_init(int semid)
{
    union semun arg;
    struct semid_ds ds;
    arg.buf = &ds;

    printf("Waiting for semaphore initialization...\n");

    while (1) {
        if (semctl(semid, 0, IPC_STAT, arg) == -1) {
            perror("IPC_STAT");
            return;
        }

        if (ds.sem_otime != 0) {
            printf("Semaphore initialized (sem_otime = %ld)\n",
                   (long)ds.sem_otime);
            break;
        }

        /* Not yet initialized — sleep and retry */
        usleep(100000);  /* 100ms */
    }
}

int main(void)
{
    key_t key = ftok("/tmp/demo", 'S');
    int semid = semget(key, 0, 0660);
    if (semid == -1) { perror("semget"); return 1; }

    wait_for_init(semid);
    /* Now safe to use the semaphore */
    return 0;
}

Using sem_nsems — Dynamic Array Allocation

When you don’t know how many semaphores a set contains (e.g., opening a set created by another program), use IPC_STAT to get sem_nsems before allocating memory for GETALL/SETALL.

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

union semun {
    int              val;
    struct semid_ds *buf;
    unsigned short  *array;
};

/* Print all semaphore values for an unknown-size set */
void dump_semaphore_set(int semid)
{
    union semun arg;
    struct semid_ds ds;
    unsigned short *vals;
    int nsems;

    /* Step 1: find out how many semaphores are in the set */
    arg.buf = &ds;
    if (semctl(semid, 0, IPC_STAT, arg) == -1) {
        perror("IPC_STAT");
        return;
    }
    nsems = (int)ds.sem_nsems;
    printf("Set %d has %d semaphore(s)\n", semid, nsems);

    /* Step 2: allocate array of correct size */
    vals = malloc(nsems * sizeof(unsigned short));
    if (!vals) {
        perror("malloc");
        return;
    }

    /* Step 3: read all values with GETALL */
    arg.array = vals;
    if (semctl(semid, 0, GETALL, arg) == -1) {
        perror("GETALL");
        free(vals);
        return;
    }

    /* Step 4: print */
    for (int i = 0; i < nsems; i++)
        printf("  sem[%d] = %u\n", i, vals[i]);

    free(vals);
}

int main(void)
{
    int semid = semget(IPC_PRIVATE, 4, IPC_CREAT | 0600);
    if (semid == -1) { perror("semget"); exit(1); }

    /* Initialize with SETALL */
    union semun arg;
    unsigned short init[4] = {10, 20, 30, 40};
    arg.array = init;
    semctl(semid, 0, SETALL, arg);

    dump_semaphore_set(semid);

    semctl(semid, 0, IPC_RMID);
    return 0;
}

Portability Notes — sem_nsems Type
Type difference across platforms:

  • SUSv3 specifies sem_nsems as unsigned short
  • Linux 2.2 and most UNIX systems use unsigned short
  • Linux 2.4 and later changed it to unsigned long
  • Always cast to a wide type when printing: (unsigned long)ds.sem_nsems
/* Portable way to print sem_nsems */
struct semid_ds ds;
union semun arg;
arg.buf = &ds;
semctl(semid, 0, IPC_STAT, arg);

/* Cast to unsigned long to handle both short and long variants */
printf("Number of semaphores: %lu\n", (unsigned long)ds.sem_nsems);

/* Wrong on Linux 2.4+: */
/* printf("%hu\n", ds.sem_nsems);  -- may truncate or produce warning */

Complete Program — Semaphore Set Inspector

A complete utility that shows all information available via IPC_STAT for any semaphore set by its semid:

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

union semun {
    int              val;
    struct semid_ds *buf;
    unsigned short  *array;
};

void inspect_semaphore_set(int semid)
{
    union semun arg;
    struct semid_ds ds;
    unsigned short *vals;
    int nsems;

    arg.buf = &ds;
    if (semctl(semid, 0, IPC_STAT, arg) == -1) {
        perror("semctl IPC_STAT");
        exit(EXIT_FAILURE);
    }

    nsems = (int)ds.sem_nsems;

    printf("========================================\n");
    printf("Semaphore Set Inspector\n");
    printf("========================================\n");
    printf("semid           : %d\n", semid);
    printf("Semaphores      : %d\n", nsems);
    printf("\n--- Ownership ---\n");
    printf("Owner UID       : %u\n", ds.sem_perm.uid);
    printf("Owner GID       : %u\n", ds.sem_perm.gid);
    printf("Creator UID     : %u\n", ds.sem_perm.cuid);
    printf("Creator GID     : %u\n", ds.sem_perm.cgid);
    printf("Permissions     : %04o\n", ds.sem_perm.mode & 0777);

    printf("\n--- Timestamps ---\n");
    if (ds.sem_otime == 0)
        printf("Last semop()    : Never (semaphore not yet used)\n");
    else
        printf("Last semop()    : %s", ctime(&ds.sem_otime));

    printf("Last change     : %s", ctime(&ds.sem_ctime));

    printf("\n--- Current Values ---\n");
    vals = malloc(nsems * sizeof(unsigned short));
    if (vals) {
        arg.array = vals;
        if (semctl(semid, 0, GETALL, arg) == 0) {
            for (int i = 0; i < nsems; i++) {
                int pid   = semctl(semid, i, GETPID);
                int ncnt  = semctl(semid, i, GETNCNT);
                int zcnt  = semctl(semid, i, GETZCNT);
                printf("  sem[%d]: value=%-4u  lastpid=%-6d  ncnt=%-4d  zcnt=%d\n",
                       i, vals[i], pid, ncnt, zcnt);
            }
        }
        free(vals);
    }
    printf("========================================\n");
}

int main(int argc, char *argv[])
{
    int semid;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <semid>\n", argv[0]);
        fprintf(stderr, "Use 'ipcs -s' to list semaphore sets\n");
        exit(EXIT_FAILURE);
    }

    semid = atoi(argv[1]);
    inspect_semaphore_set(semid);

    return 0;
}
/*
 * Compile: gcc -o sem_inspect sem_inspect.c
 * Use:     ipcs -s          # list all semaphore sets
 *          ./sem_inspect 0  # inspect set with semid=0
 */

Interview Questions — semid_ds

Q1. What is the difference between sem_otime and sem_ctime?

sem_otime records the time of the last successful semop() call on any semaphore in the set. sem_ctime records the last time the semaphore set’s metadata was changed — this is updated by semget() (creation), semctl(SETVAL), semctl(SETALL), and semctl(IPC_SET). Notably, sem_otime starts at 0 and only becomes non-zero after a semop() is performed.
Q2. What is the classic use of sem_otime == 0 in IPC programming?

It serves as a readiness flag. The creator creates the semaphore set, initializes values with SETVAL/SETALL (which updates sem_ctime but NOT sem_otime), then does a dummy semop() to signal readiness (setting sem_otime to non-zero). Other processes poll IPC_STAT in a loop waiting for sem_otime != 0 before using the semaphores. This avoids race conditions where the opener tries to use a not-yet-initialized semaphore.
Q3. Which fields of semid_ds can be modified by IPC_SET?

Only sem_perm.uid, sem_perm.gid, and sem_perm.mode can be changed via IPC_SET. You cannot change sem_nsems (number of semaphores), sem_otime, sem_ctime, or the creator IDs (cuid/cgid) through IPC_SET. The creator fields are permanently set at creation time.
Q4. Why must you call IPC_STAT before GETALL?

GETALL writes values into a caller-allocated unsigned short array. The array must be exactly sem_nsems elements in size. If you underallocate, you get a buffer overflow; if you use a hardcoded size that doesn’t match the actual set, you risk corruption or incorrect reads. Using IPC_STAT first to retrieve sem_nsems allows dynamic and safe allocation.
Q5. What Linux-specific type difference exists for sem_nsems?

SUSv3 defines sem_nsems as unsigned short, and older kernels including Linux 2.2 follow this. Linux 2.4 and later changed it to unsigned long. Code that prints sem_nsems with %hu (unsigned short format) may produce warnings or incorrect output on modern Linux. Always cast to unsigned long and use %lu for portability.
Q6. What is the ipc_perm structure and what does it contain?

ipc_perm is a generic structure shared by all three System V IPC objects (semaphores, message queues, shared memory). It contains: the original key used to create the IPC object (__key), current owner’s uid/gid, creator’s uid/gid (never changed after creation), permission mode bits, and an internal sequence number used to prevent reuse of old identifiers.

You’ve completed the Chapter 47 Series!

You now understand semget(), semctl(), the semun union, and the semid_ds structure — the complete foundation for System V Semaphore programming.

← Part 1: semget() ← Part 2: semctl()

Leave a Reply

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