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.
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) */
};
| 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. |
| 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;
}
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;
}
- SUSv3 specifies
sem_nsemsasunsigned 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 */
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
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.
