semctl()
semun
Intermediate
semctl() is the Swiss Army knife of System V semaphores. It performs all control operations: initializing semaphore values, querying values, changing ownership, and deleting sets.
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../* union semun arg */);
/* Returns value depending on cmd; -1 on error */
The fourth argument is a union semun whose member you fill depending on the command used. Important: the application must define this union — it is not defined in any header on Linux.
/* Must be defined by the application — not in any header */
union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT, IPC_SET */
unsigned short *array; /* for GETALL, SETALL */
struct seminfo *__buf; /* for IPC_INFO, SEM_INFO */
};
| Command | arg Used | Description | Return Value |
|---|---|---|---|
| SETVAL | arg.val | Set value of semaphore semnum | 0 |
| GETVAL | none | Get current value of semaphore semnum | semval |
| SETALL | arg.array | Set all semaphores from array | 0 |
| GETALL | arg.array | Get all semaphore values into array | 0 |
| GETPID | none | Get PID of last process to do semop() on semnum | PID |
| GETNCNT | none | Count of processes waiting for semval to increase | count |
| GETZCNT | none | Count of processes waiting for semval == 0 | count |
| IPC_STAT | arg.buf | Retrieve semid_ds metadata into arg.buf | 0 |
| IPC_SET | arg.buf | Set permissions/ownership from arg.buf | 0 |
| IPC_RMID | none | Delete the semaphore set immediately | 0 |
| IPC_INFO | arg.__buf | Get system-wide semaphore limits | max index |
| SEM_INFO | arg.__buf | Get actual resource usage (Linux-specific) | max index |
| SEM_STAT | arg.buf | Like IPC_STAT but uses kernel index not semid | semid |
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void set_semval(int semid, int semnum, int val)
{
union semun arg;
arg.val = val;
if (semctl(semid, semnum, SETVAL, arg) == -1) {
perror("semctl SETVAL");
exit(EXIT_FAILURE);
}
printf("Set sem[%d] = %d\n", semnum, val);
}
int get_semval(int semid, int semnum)
{
int val = semctl(semid, semnum, GETVAL);
if (val == -1) {
perror("semctl GETVAL");
exit(EXIT_FAILURE);
}
printf("sem[%d] current value = %d\n", semnum, val);
return val;
}
int main(void)
{
int semid = semget(IPC_PRIVATE, 3, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
set_semval(semid, 0, 1); /* binary semaphore */
set_semval(semid, 1, 5); /* counting semaphore */
set_semval(semid, 2, 0); /* starts locked */
get_semval(semid, 1); /* prints 5 */
semctl(semid, 0, IPC_RMID);
return 0;
}
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main(void)
{
int semid;
union semun arg;
unsigned short init_vals[4] = {1, 1, 3, 0};
unsigned short read_vals[4];
int i;
semid = semget(IPC_PRIVATE, 4, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
/* Initialize all 4 semaphores at once */
arg.array = init_vals;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("SETALL");
exit(EXIT_FAILURE);
}
/* Read all values back */
arg.array = read_vals;
if (semctl(semid, 0, GETALL, arg) == -1) {
perror("GETALL");
exit(EXIT_FAILURE);
}
for (i = 0; i < 4; i++)
printf("sem[%d] = %u\n", i, read_vals[i]);
semctl(semid, 0, IPC_RMID);
return 0;
}
The semid_ds structure holds metadata about a semaphore set:
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Time of last semop() */
time_t sem_ctime; /* Time of last change */
unsigned long sem_nsems; /* Number of semaphores in set */
};
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void print_seminfo(int semid)
{
union semun arg;
struct semid_ds ds;
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("IPC_STAT");
return;
}
printf("Semaphore set info:\n");
printf(" Number of semaphores : %lu\n", ds.sem_nsems);
printf(" Owner UID : %d\n", ds.sem_perm.uid);
printf(" Permissions (octal) : %04o\n", ds.sem_perm.mode & 0777);
if (ds.sem_otime != 0)
printf(" Last semop : %s", ctime(&ds.sem_otime));
else
printf(" Last semop : never\n");
}
int main(void)
{
int semid = semget(IPC_PRIVATE, 2, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); }
print_seminfo(semid);
semctl(semid, 0, IPC_RMID);
return 0;
}
This is one of the most important and tricky aspects of System V semaphores. semget() (create) and the initialization via semctl(SETVAL) are two separate system calls. Between them, another process may already start using the semaphore with its default value of 0.
| Process A | Process B | Problem |
|---|---|---|
| semget(key, 1, IPC_CREAT|0600) → creates, semval=0 | ||
| semget(key, 0, 0) → opens existing | B has semid before it’s initialized! | |
| semop(-1) → blocks on semval=0 | B is stuck waiting! | |
| semctl(SETVAL, 1) → sets to 1 | B wakes up — but was it safe? |
The standard fix: use the sem_otime field. Right after semget(), check if sem_otime is non-zero. If it is, another process already initialized it. If zero, we need to initialize it ourselves.
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/*
* Create-or-open a semaphore set with one semaphore,
* initializing it to init_val if we create it.
* Uses sem_otime trick to avoid init race.
*/
int create_or_open_sem(key_t key, int init_val)
{
int semid;
union semun arg;
struct semid_ds ds;
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0600);
if (semid != -1) {
/* We created it — initialize now */
arg.val = init_val;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl SETVAL");
exit(EXIT_FAILURE);
}
printf("Created and initialized semaphore %d\n", semid);
return semid;
}
if (errno == EEXIST) {
/* Another process created it — open and wait for init */
semid = semget(key, 0, 0600);
if (semid == -1) { perror("semget open"); exit(EXIT_FAILURE); }
/* Poll sem_otime until non-zero (init complete) */
for (;;) {
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("IPC_STAT"); exit(EXIT_FAILURE);
}
if (ds.sem_otime != 0) break; /* initialized */
/* small sleep to avoid busy-wait */
usleep(10000); /* 10ms */
}
printf("Opened existing semaphore %d\n", semid);
return semid;
}
perror("semget unexpected");
exit(EXIT_FAILURE);
}
Why does sem_otime work? The kernel only sets sem_otime when the first semop() completes. But we rely on the fact that after SETVAL, a subsequent semop() by the creator will set sem_otime. A cleaner but equivalent approach: do a dummy semop() (add 0) immediately after SETVAL so sem_otime gets set.
The kernel enforces limits on semaphores. These can be queried with semctl(0, 0, IPC_INFO, arg) and configured via /proc/sys/kernel/sem.
| Limit | Symbol | Description | Default (Linux) |
|---|---|---|---|
| Max semaphores per set | SEMMSL | Max value for nsems in semget() | 32000 |
| Max semaphore sets | SEMMNI | System-wide max number of semaphore sets | 32000 |
| Max total semaphores | SEMMNS | System-wide max total semaphores across all sets | 1024000000 |
| Max semaphore value | SEMVMX | Maximum value a semaphore can hold | 32767 |
| Max ops per semop() | SEMOPM | Max semaphores per semop() call | 500 |
| Max undo entries | SEMMNU | System-wide max SEM_UNDO structures | SEMMNS |
/* Check current limits on Linux: */
/* cat /proc/sys/kernel/sem
Output: SEMMSL SEMMNS SEMOPM SEMMNI
e.g.: 32000 1024000000 500 32000
*/
/* Change limits (as root): */
/* echo "32000 1024000000 500 32000" > /proc/sys/kernel/sem */
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int main(void)
{
union semun arg;
struct seminfo si;
arg.__buf = &si;
if (semctl(0, 0, IPC_INFO, arg) == -1) {
perror("semctl IPC_INFO");
exit(EXIT_FAILURE);
}
printf("System V Semaphore Limits:\n");
printf(" SEMMSL (max sems per set) : %d\n", si.semmsl);
printf(" SEMMNS (max sems total) : %d\n", si.semmns);
printf(" SEMOPM (max ops per semop) : %d\n", si.semopm);
printf(" SEMMNI (max sem sets) : %d\n", si.semmni);
printf(" SEMVMX (max semaphore value) : %d\n", si.semvmx);
return 0;
}
#include <sys/sem.h>
#include <stdio.h>
void print_sem_status(int semid, int semnum)
{
int val, ncnt, zcnt, pid;
val = semctl(semid, semnum, GETVAL);
ncnt = semctl(semid, semnum, GETNCNT); /* waiting to decrement */
zcnt = semctl(semid, semnum, GETZCNT); /* waiting for zero */
pid = semctl(semid, semnum, GETPID); /* last semop() process */
if (val == -1 || ncnt == -1 || zcnt == -1) {
perror("semctl");
return;
}
printf("sem[%d]: value=%d, waiting_for_nonzero=%d, "
"waiting_for_zero=%d, last_pid=%d\n",
semnum, val, ncnt, zcnt, pid);
}
Q1. Why must the application define union semun rather than including it from a header?
POSIX specifies that the application is responsible for defining union semun. The union varies across implementations (different members may exist on different systems), so no standard header defines it. On Linux, it is mentioned in the semctl(2) man page as an example the programmer must copy.
Q2. Why is there a race condition between semget() and semctl(SETVAL)?
These are two separate system calls. Between the call to semget() (which creates the set with all semaphores at 0) and the call to semctl() (which sets the initial value), another process can obtain the semid and try to use the semaphore with an incorrect initial value of 0.
Q3. How does the sem_otime trick solve the initialization race?
sem_otime is set by the kernel only when a semop() is performed. If a process opens an existing semaphore set and finds sem_otime == 0, it knows the creator has not yet performed any semop() and may still be in the middle of initialization. It spins (with sleep) until sem_otime becomes non-zero, at which point initialization is guaranteed complete.
Q4. What does IPC_RMID do if processes are currently blocked on the semaphore?
It removes the semaphore set immediately. All processes blocked in semop() on that semaphore set are woken up and semop() returns -1 with errno set to EIDRM.
Q5. What is SEMVMX and why does it matter for SEM_UNDO?
SEMVMX is the maximum allowed semaphore value (32767 on Linux). If a SEM_UNDO adjustment on process exit would push a semaphore value above SEMVMX, the kernel clamps it at SEMVMX instead of failing — so cleanup may not be perfect in extreme cases.
Q6. How do you list all existing semaphore sets on a Linux system?
From the shell: ipcs -s lists all semaphore sets with their keys, IDs, owners, and permissions. Programmatically, use semctl() with IPC_INFO to get the max index, then loop calling semctl() with SEM_STAT to retrieve each active set.
See how to implement clean binary semaphore primitives on top of System V semaphores.
