#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
/* Returns: non-negative value on success (depends on cmd), -1 on error */
/* The semun union — MUST be declared by the programmer */
union semun {
int val; /* used with SETVAL */
struct semid_ds *buf; /* used with IPC_STAT, IPC_SET */
unsigned short *array; /* used with GETALL, SETALL */
struct seminfo *__buf; /* used with IPC_INFO (Linux only) */
};
semctl() is the Swiss Army knife of semaphore management. It handles initialization, querying, modification, and deletion of semaphore sets depending on the cmd argument passed.
| Command | semnum | arg used | What it does | Returns |
|---|---|---|---|---|
| SETVAL | sem index | arg.val |
Set one semaphore to a specific value | 0 |
| GETVAL | sem index | not needed | Get current value of one semaphore | semaphore value |
| SETALL | ignored | arg.array |
Set ALL semaphores in the set at once | 0 |
| GETALL | ignored | arg.array |
Get ALL semaphore values into array | 0 |
| IPC_RMID | ignored | not needed | Delete the entire semaphore set | 0 |
| IPC_STAT | ignored | arg.buf |
Copy metadata into semid_ds struct |
0 |
| IPC_SET | ignored | arg.buf |
Change ownership/permissions from semid_ds |
0 |
| GETPID | sem index | not needed | Get PID of last process to call semop() | PID |
| GETNCNT | sem index | not needed | Get count of processes waiting to decrease sem | count |
| GETZCNT | sem index | not needed | Get count of processes waiting for value = 0 | count |
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Set semaphore[semnum] to a value */
int sem_set_value(int semid, int semnum, int value) {
union semun arg;
arg.val = value;
return semctl(semid, semnum, SETVAL, arg);
}
/* Get current value of semaphore[semnum] */
int sem_get_value(int semid, int semnum) {
/* No arg needed for GETVAL */
return semctl(semid, semnum, GETVAL);
}
/* Demonstrate SETVAL and GETVAL */
void demo_setval_getval(int semid) {
int val;
/* Set semaphore 0 to 5 */
if (sem_set_value(semid, 0, 5) == -1) {
perror("SETVAL"); return;
}
printf("Set semaphore[0] = 5\n");
/* Read it back */
val = sem_get_value(semid, 0);
if (val == -1) { perror("GETVAL"); return; }
printf("GETVAL semaphore[0] = %d\n", val);
/* Set to 0 (mark resource as unavailable) */
if (sem_set_value(semid, 0, 0) == -1) {
perror("SETVAL to 0"); return;
}
printf("Set semaphore[0] = 0 (all processes waiting will stay blocked)\n");
/* WARNING: setting value to 0 while others are waiting */
/* Use GETNCNT to check before setting */
int waiters = semctl(semid, 0, GETNCNT);
printf("Processes waiting to decrement: %d\n", waiters);
}
SETVAL or SETALL, the kernel automatically clears the semadj values (used for undo operations) for all processes in the system. This prevents stale undo adjustments from interfering.When you have a multi-semaphore set, SETALL and GETALL are more efficient than calling SETVAL/GETVAL for each semaphore individually:
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
#define NUM_SEMS 4
/* Initialize all semaphores in one call */
int sem_init_all(int semid, unsigned short values[], int count) {
union semun arg;
arg.array = values;
/* semnum is ignored for SETALL */
return semctl(semid, 0, SETALL, arg);
}
/* Read all semaphore values */
int sem_get_all(int semid, unsigned short values[], int count) {
union semun arg;
arg.array = values;
return semctl(semid, 0, GETALL, arg);
}
void demo_setall_getall(int semid) {
unsigned short init_vals[NUM_SEMS] = {1, 0, 5, 10};
unsigned short read_vals[NUM_SEMS];
int i;
/* Initialize all 4 semaphores at once */
if (sem_init_all(semid, init_vals, NUM_SEMS) == -1) {
perror("SETALL");
return;
}
printf("Initialized all %d semaphores\n", NUM_SEMS);
/* Read them all back */
if (sem_get_all(semid, read_vals, NUM_SEMS) == -1) {
perror("GETALL");
return;
}
printf("Current semaphore values:\n");
for (i = 0; i < NUM_SEMS; i++) {
printf(" sem[%d] = %u\n", i, read_vals[i]);
}
}
Each semaphore set has an associated semid_ds structure maintained by the kernel. IPC_STAT copies it into a user-space buffer so you can inspect metadata about the set:
/* The semid_ds structure (simplified) */
struct semid_ds {
struct ipc_perm sem_perm; /* ownership and permissions */
time_t sem_otime; /* time of last semop() call */
time_t sem_ctime; /* time of last semctl() change */
unsigned short sem_nsems; /* number of semaphores in this set */
};
/* The ipc_perm structure (for sem_perm field) */
struct ipc_perm {
key_t __key; /* key passed to semget() */
uid_t uid; /* effective UID of owner */
gid_t gid; /* effective GID of owner */
uid_t cuid; /* effective UID of creator */
gid_t cgid; /* effective GID of creator */
unsigned short mode; /* permissions (lower 9 bits) */
unsigned short __seq; /* sequence number */
};
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Print metadata about a semaphore set */
void print_sem_info(int semid) {
union semun arg;
struct semid_ds ds;
char timebuf[32];
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("semctl IPC_STAT");
return;
}
printf("=== Semaphore Set Info (semid=%d) ===\n", semid);
printf(" Number of semaphores : %d\n", ds.sem_nsems);
printf(" Owner UID : %d\n", ds.sem_perm.uid);
printf(" Owner GID : %d\n", ds.sem_perm.gid);
printf(" Permissions (octal) : %04o\n", ds.sem_perm.mode & 0777);
if (ds.sem_otime != 0) {
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
localtime(&ds.sem_otime));
printf(" Last semop() : %s\n", timebuf);
} else {
printf(" Last semop() : never\n");
}
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S",
localtime(&ds.sem_ctime));
printf(" Last semctl() change : %s\n", timebuf);
}
/* KEY USE OF sem_otime: detect if initialization is done */
/* sem_otime = 0 means no semop() has ever been called */
/* A pattern: creator marks init complete by doing semop */
/* Other processes poll IPC_STAT waiting for sem_otime != 0 */
int wait_for_init(int semid) {
union semun arg;
struct semid_ds ds;
int retries = 0;
arg.buf = &ds;
/* Poll until sem_otime becomes non-zero (creator finished init) */
while (retries < 100) {
if (semctl(semid, 0, IPC_STAT, arg) == -1) return -1;
if (ds.sem_otime != 0) {
printf("Semaphore initialized (detected via sem_otime)\n");
return 0;
}
usleep(10000); /* sleep 10ms between polls */
retries++;
}
return -1; /* timed out */
}
sem_otime to the current time every time semop() is called. Initially it is 0. This fact is used as a synchronization mechanism: the creator calls semop() as its last initialization step, and other processes poll IPC_STAT waiting for sem_otime to become non-zero before proceeding.#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Change permissions of an existing semaphore set */
int sem_change_perm(int semid, unsigned short new_mode) {
union semun arg;
struct semid_ds ds;
arg.buf = &ds;
/* First read current settings */
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("IPC_STAT"); return -1;
}
/* Modify only the permission bits */
ds.sem_perm.mode = new_mode;
/* Write back — only uid, gid, and mode can be changed */
if (semctl(semid, 0, IPC_SET, arg) == -1) {
perror("IPC_SET"); return -1;
}
printf("Changed semaphore set permissions to %04o\n", new_mode);
return 0;
}
/* Usage example */
void example_ipc_set(int semid) {
/* Restrict: only owner can access */
sem_change_perm(semid, 0600);
/* Open up to group */
sem_change_perm(semid, 0660);
/* Note: only the creator/owner or superuser can call IPC_SET */
}
IPC_RMID removes the entire semaphore set from the kernel. Any processes blocked in semop() on this set will be immediately woken up with an error (EIDRM).
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static int g_semid = -1;
/* Signal handler — clean up semaphore on Ctrl+C */
void cleanup_handler(int sig) {
if (g_semid != -1) {
/* semnum is ignored, arg is not needed */
if (semctl(g_semid, 0, IPC_RMID) == -1)
perror("semctl IPC_RMID in handler");
else
printf("\nSemaphore set %d deleted\n", g_semid);
}
exit(0);
}
int main(void) {
union semun { int val; struct semid_ds *buf; unsigned short *array; } arg;
/* Create semaphore */
g_semid = semget(IPC_PRIVATE, 1, 0600);
if (g_semid == -1) { perror("semget"); exit(1); }
arg.val = 1;
semctl(g_semid, 0, SETVAL, arg);
/* Register cleanup handler */
signal(SIGINT, cleanup_handler);
signal(SIGTERM, cleanup_handler);
printf("Semaphore set created: semid=%d\n", g_semid);
printf("Press Ctrl+C to delete and exit\n");
/* ... application logic ... */
pause(); /* wait for signal */
return 0;
}
semop() waiting on a deleted semaphore set is immediately woken up and semop() returns -1 with errno = EIDRM (identifier removed). Always check for this error in robust code.These commands let you inspect the state of individual semaphores:
#include <sys/sem.h>
#include <stdio.h>
void diagnose_semaphore(int semid, int semnum) {
int val, ncnt, zcnt, pid;
/* Current value */
val = semctl(semid, semnum, GETVAL);
/* Processes waiting to DECREASE (blocked in semop with negative sem_op) */
ncnt = semctl(semid, semnum, GETNCNT);
/* Processes waiting for value to BECOME 0 (blocked in semop with sem_op=0) */
zcnt = semctl(semid, semnum, GETZCNT);
/* PID of last process to call semop() on this semaphore */
pid = semctl(semid, semnum, GETPID);
printf("=== semaphore[%d] diagnostics ===\n", semnum);
printf(" Current value : %d\n", val);
printf(" Waiting to decrement : %d processes\n", ncnt);
printf(" Waiting for zero : %d processes\n", zcnt);
printf(" Last semop() caller : PID %d\n", pid);
if (ncnt > 0)
printf(" WARNING: %d process(es) are blocked waiting to acquire!\n", ncnt);
if (val == 0 && ncnt == 0)
printf(" Status: semaphore is free (value=0, no one waiting)\n");
if (val > 0 && ncnt == 0)
printf(" Status: resource available (value=%d)\n", val);
}
/* Example: print all semaphores in a set */
void diagnose_all(int semid, int nsems) {
int i;
printf("\n=== Full semaphore set diagnostic (semid=%d) ===\n", semid);
for (i = 0; i < nsems; i++) {
diagnose_semaphore(semid, i);
printf("\n");
}
}
The most reliable pattern for initializing semaphores in a multi-process environment uses sem_otime as a “ready” flag:
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
#define KEY_PATH "/tmp/robust_sem"
#define KEY_ID 1
#define NUM_SEMS 2
/*
* Open or create and initialize a semaphore set robustly.
* Returns semid on success, -1 on error.
*
* Algorithm:
* 1. Try IPC_CREAT | IPC_EXCL — if we succeed, we are the creator
* 2. If we are creator: initialize, then do a semop() to set sem_otime
* 3. If EEXIST: just open, then poll sem_otime != 0 to confirm init done
*/
int open_sem_robust(unsigned short init_vals[], int nsems) {
key_t key;
int semid;
union semun arg;
struct semid_ds ds;
key = ftok(KEY_PATH, KEY_ID);
if (key == -1) { perror("ftok"); return -1; }
/* Try to create exclusively */
semid = semget(key, nsems, IPC_CREAT | IPC_EXCL | 0600);
if (semid != -1) {
/* === WE ARE THE CREATOR === */
printf("[%d] Created semaphore set semid=%d\n", getpid(), semid);
/* Initialize all semaphore values */
arg.array = init_vals;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("SETALL");
semctl(semid, 0, IPC_RMID);
return -1;
}
/*
* Stamp sem_otime to signal other processes that init is complete.
* We do this by performing a no-op semop (adding 0 to semaphore 0).
* This sets sem_otime to a non-zero timestamp.
*/
struct sembuf noop = {0, 0, 0}; /* wait-for-zero on sem[0]=0 */
/* Actually just do +0 trick differently — add 0 to trigger otime */
/* Better: do a real operation that immediately succeeds */
/* If sem[0] = 1 (mutex), we can do nothing meaningful here */
/* Instead just flag via IPC_STAT sem_ctime — or do a tiny semop */
/* Simplest: set a special "initialized" marker semaphore to 1 */
/* Here we use sem[nsems-1] as the ready flag */
struct sembuf mark = {nsems - 1, 1, 0}; /* add 1 to last sem */
if (semop(semid, &mark, 1) == -1) {
perror("semop mark");
semctl(semid, 0, IPC_RMID);
return -1;
}
printf("[%d] Initialization complete (sem_otime stamped)\n", getpid());
} else if (errno == EEXIST) {
/* === ALREADY EXISTS — just open it === */
semid = semget(key, 0, 0600);
if (semid == -1) { perror("semget open"); return -1; }
printf("[%d] Opened existing semid=%d, waiting for init...\n",
getpid(), semid);
/* Poll until creator has stamped sem_otime */
arg.buf = &ds;
int retries = 0;
while (retries++ < 500) {
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
if (errno == EINVAL) continue; /* set was deleted */
perror("IPC_STAT"); return -1;
}
if (ds.sem_otime != 0) {
printf("[%d] Init detected via sem_otime\n", getpid());
return semid;
}
usleep(5000); /* 5ms */
}
fprintf(stderr, "[%d] Timed out waiting for init\n", getpid());
return -1;
} else {
perror("semget");
return -1;
}
return semid;
}
union semun that can be an int, a pointer to semid_ds, or a pointer to unsigned short array depending on the command. It is unusual because Linux does not define this union in any system header — the programmer must declare it in their own code. POSIX specifies its members but leaves the definition to the application.semop() call returns -1 with errno set to EIDRM (identifier removed). Robust programs always check for this error and handle it gracefully.sem_otime starts at 0 when a set is created. The kernel updates it whenever semop() is called. The creator performs a semop() as its last initialization step, which stamps sem_otime to a non-zero value. Other processes poll IPC_STAT in a loop checking for sem_otime != 0. When they see a non-zero timestamp, they know initialization is complete and it is safe to use the semaphore.SETVAL sets a single semaphore (specified by semnum) to a specific integer value (from arg.val). SETALL sets all semaphores in the set simultaneously using an array of unsigned short values (from arg.array); the semnum argument is ignored. SETALL is more efficient when initializing multiple semaphores.GETNCNT returns the number of processes currently blocked waiting to decrease the semaphore value (i.e., blocked in semop() with a negative sem_op). GETZCNT returns the number waiting for the value to become zero. These are used for diagnostics, performance monitoring, and detecting deadlocks — if GETNCNT is very high, it indicates contention or a bug where a semaphore is never being released.