What does semctl() do?
semctl() is the Swiss Army knife for semaphore management. After creating a semaphore set with semget(), you use semctl() to perform control operations such as initializing semaphore values, reading semaphore values, querying metadata, and removing the semaphore set entirely.
It operates either on the entire set or on a single semaphore within the set depending on the operation (cmd) you choose.
#include <sys/types.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
/*
* semid — semaphore set identifier from semget()
* semnum — index of semaphore within the set (0-based)
* Ignored for set-wide operations
* cmd — operation to perform (IPC_RMID, SETVAL, GETVAL, etc.)
* arg — optional 4th argument; type is union semun
* Required only by some commands
*
* Returns: nonneg integer on success (meaning depends on cmd)
* -1 on error
*/
union semun which you must define yourself.SUSv3 (the POSIX standard) deliberately does NOT include the definition of union semun in any header file. Every program that uses semctl() must define it. This is one of the most common gotchas for beginners.
/* semun.h — include this in every program that uses semctl() */
#ifndef SEMUN_H
#define SEMUN_H
#include <sys/types.h>
#include <sys/sem.h>
union semun {
int val; /* Used by SETVAL */
struct semid_ds *buf; /* Used by IPC_STAT, IPC_SET */
unsigned short *array; /* Used by GETALL, SETALL */
#if defined(__linux__)
struct seminfo *__buf; /* Linux-specific: used by IPC_INFO */
#endif
};
#endif /* SEMUN_H */
| semun field | Type | Used by cmd |
| val | int | SETVAL — set one semaphore to this integer |
| buf | struct semid_ds * | IPC_STAT (read metadata), IPC_SET (write metadata) |
| array | unsigned short * | GETALL (read all values), SETALL (write all values) |
| __buf | struct seminfo * | IPC_INFO (Linux only — system limits) |
/* How to use the union: always set the correct field */
union semun arg;
/* For SETVAL: set sem[0] = 1 */
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
/* For SETALL: set all semaphores in the set */
unsigned short vals[3] = {1, 0, 3};
arg.array = vals;
semctl(semid, 0, SETALL, arg); /* semnum ignored for SETALL */
/* For IPC_STAT: read metadata */
struct semid_ds ds;
arg.buf = &ds;
semctl(semid, 0, IPC_STAT, arg);
IPC_RMID immediately removes the semaphore set from the kernel. Any process blocked in semop() on a semaphore in this set is unblocked and semop() returns EIDRM error.
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
void remove_semaphore(int semid)
{
/*
* IPC_RMID does not need a semnum or arg.
* Convention: pass semnum=0, no arg needed
* For portability, some code passes a dummy union semun
*/
if (semctl(semid, 0, IPC_RMID) == -1) {
perror("semctl IPC_RMID");
exit(EXIT_FAILURE);
}
printf("Semaphore set %d removed.\n", semid);
}
int main(void)
{
key_t key = ftok("/tmp/demo", 'X');
int semid = semget(key, 1, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(1); }
printf("Created semid=%d\n", semid);
/* ... use the semaphore ... */
/* Always clean up */
remove_semaphore(semid);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <time.h>
/* Include your semun definition */
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
void print_sem_info(int semid)
{
union semun arg;
struct semid_ds ds;
arg.buf = &ds;
/* IPC_STAT: copies kernel's semid_ds into ds */
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("semctl IPC_STAT");
return;
}
printf("=== Semaphore Set Info ===\n");
printf("Owner UID : %d\n", ds.sem_perm.uid);
printf("Owner GID : %d\n", ds.sem_perm.gid);
printf("Creator UID : %d\n", ds.sem_perm.cuid);
printf("Permissions (octal): %03o\n", ds.sem_perm.mode & 0777);
printf("Number of sems : %lu\n", (unsigned long)ds.sem_nsems);
printf("Last semop() time : %s", ctime(&ds.sem_otime));
printf("Last change time : %s", ctime(&ds.sem_ctime));
}
void change_permissions(int semid)
{
union semun arg;
struct semid_ds ds;
arg.buf = &ds;
/* First read current metadata */
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("IPC_STAT");
return;
}
/* Modify the permission bits */
ds.sem_perm.mode = (ds.sem_perm.mode & ~0777) | 0640;
/* Write back with IPC_SET */
if (semctl(semid, 0, IPC_SET, arg) == -1) {
perror("IPC_SET");
return;
}
printf("Permissions changed to 0640\n");
}
int main(void)
{
key_t key = IPC_PRIVATE;
int semid = semget(key, 2, IPC_CREAT | 0660);
if (semid == -1) { perror("semget"); exit(1); }
print_sem_info(semid);
change_permissions(semid);
print_sem_info(semid);
semctl(semid, 0, IPC_RMID);
return 0;
}
These operate on a single semaphore identified by semnum within the set.
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Initialize one semaphore to a specific value */
int sem_setval(int semid, int semnum, int value)
{
union semun arg;
arg.val = value;
return semctl(semid, semnum, SETVAL, arg);
}
/* Read the current value of one semaphore */
int sem_getval(int semid, int semnum)
{
/* GETVAL returns the value directly as the function result */
/* No arg needed */
return semctl(semid, semnum, GETVAL);
}
int main(void)
{
int semid;
int val;
/* Create a set with 3 semaphores */
semid = semget(IPC_PRIVATE, 3, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(1); }
/* Initialize each semaphore */
if (sem_setval(semid, 0, 1) == -1) { perror("SETVAL 0"); exit(1); }
if (sem_setval(semid, 1, 3) == -1) { perror("SETVAL 1"); exit(1); }
if (sem_setval(semid, 2, 0) == -1) { perror("SETVAL 2"); exit(1); }
/* Read them back */
for (int i = 0; i < 3; i++) {
val = sem_getval(semid, i);
if (val == -1) { perror("GETVAL"); exit(1); }
printf("sem[%d] = %d\n", i, val);
}
/* Output:
* sem[0] = 1
* sem[1] = 3
* sem[2] = 0
*/
semctl(semid, 0, IPC_RMID);
return 0;
}
When you have a multi-semaphore set, SETALL and GETALL are more efficient than calling SETVAL/GETVAL in a loop. For SETALL, if any process is waiting and the change unblocks it, the kernel wakes it.
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Get number of semaphores in the set */
int sem_nsems(int semid)
{
union semun arg;
struct semid_ds ds;
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1)
return -1;
return (int)ds.sem_nsems;
}
/* Initialize all semaphores using SETALL */
int sem_setall(int semid, unsigned short *values)
{
union semun arg;
arg.array = values;
return semctl(semid, 0, SETALL, arg);
/* semnum (2nd arg) is ignored for SETALL */
}
/* Read all semaphore values using GETALL */
int sem_getall(int semid, unsigned short *values)
{
union semun arg;
arg.array = values;
return semctl(semid, 0, GETALL, arg);
}
int main(void)
{
int semid;
int nsems;
unsigned short *vals;
/* Create a set with 4 semaphores */
semid = semget(IPC_PRIVATE, 4, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(1); }
nsems = sem_nsems(semid);
printf("Set has %d semaphores\n", nsems);
/* Allocate array on the heap for portability */
vals = calloc(nsems, sizeof(unsigned short));
if (!vals) { perror("calloc"); exit(1); }
/* Set values: 1, 2, 3, 4 */
for (int i = 0; i < nsems; i++)
vals[i] = (unsigned short)(i + 1);
if (sem_setall(semid, vals) == -1) {
perror("SETALL"); exit(1);
}
printf("All semaphores initialized.\n");
/* Zero out array and read back */
for (int i = 0; i < nsems; i++)
vals[i] = 0;
if (sem_getall(semid, vals) == -1) {
perror("GETALL"); exit(1);
}
printf("Current values:\n");
for (int i = 0; i < nsems; i++)
printf(" sem[%d] = %u\n", i, vals[i]);
free(vals);
semctl(semid, 0, IPC_RMID);
return 0;
}
These three operations return diagnostic information about a single semaphore. They are read-only (require read permission only) and the arg is not needed.
| cmd | Returns | Kernel field |
| GETPID | PID of last process that called semop() on this semaphore. 0 if no semop() yet. | sempid |
| GETNCNT | Number of processes currently blocked waiting for this semaphore’s value to increase. | semncnt |
| GETZCNT | Number of processes currently waiting for this semaphore’s value to become 0. | semzcnt |
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Print diagnostic info for each semaphore in the set */
void print_sem_status(int semid)
{
union semun arg;
struct semid_ds ds;
int nsems, val, pid, ncnt, zcnt;
/* Get number of semaphores */
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1) {
perror("IPC_STAT"); return;
}
nsems = (int)ds.sem_nsems;
printf("%-6s %-8s %-8s %-8s %-8s\n",
"SEM#", "VALUE", "LASTPID", "NCNT", "ZCNT");
for (int i = 0; i < nsems; i++) {
val = semctl(semid, i, GETVAL);
pid = semctl(semid, i, GETPID);
ncnt = semctl(semid, i, GETNCNT);
zcnt = semctl(semid, i, GETZCNT);
if (val == -1 || ncnt == -1 || zcnt == -1) {
perror("semctl"); return;
}
printf("%-6d %-8d %-8d %-8d %-8d\n",
i, val, pid, ncnt, zcnt);
}
}
int main(void)
{
int semid = semget(IPC_PRIVATE, 3, IPC_CREAT | 0600);
if (semid == -1) { perror("semget"); exit(1); }
/* Initialize semaphores */
union semun arg;
unsigned short vals[3] = {1, 0, 5};
arg.array = vals;
semctl(semid, 0, SETALL, arg);
printf("=== Semaphore Status ===\n");
print_sem_status(semid);
/*
* Example output (no processes waiting):
* SEM# VALUE LASTPID NCNT ZCNT
* 0 1 0 0 0
* 1 0 0 0 1 (1 process might wait for value increase)
* 2 5 0 0 0
*/
semctl(semid, 0, IPC_RMID);
return 0;
}
| cmd | Scope | arg needed? | Permission | Return value |
| IPC_RMID | Set | No | Owner/root | 0 |
| IPC_STAT | Set | arg.buf | Read | 0 |
| IPC_SET | Set | arg.buf | Owner/root | 0 |
| GETVAL | Single | No | Read | semaphore value |
| SETVAL | Single | arg.val | Alter | 0 |
| GETALL | Set | arg.array | Read | 0 |
| SETALL | Set | arg.array | Alter | 0 |
| GETPID | Single | No | Read | PID (sempid) |
| GETNCNT | Single | No | Read | wait count (semncnt) |
| GETZCNT | Single | No | Read | zero-wait count (semzcnt) |
/* sem_helpers.h */
#ifndef SEM_HELPERS_H
#define SEM_HELPERS_H
#include <sys/types.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <stdio.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* Set one semaphore value */
static inline int semctl_setval(int semid, int semnum, int val)
{
union semun arg;
arg.val = val;
return semctl(semid, semnum, SETVAL, arg);
}
/* Get one semaphore value */
static inline int semctl_getval(int semid, int semnum)
{
return semctl(semid, semnum, GETVAL);
}
/* Remove the semaphore set */
static inline int semctl_rmid(int semid)
{
return semctl(semid, 0, IPC_RMID);
}
/* Get number of semaphores in set */
static inline int semctl_nsems(int semid)
{
union semun arg;
struct semid_ds ds;
arg.buf = &ds;
if (semctl(semid, 0, IPC_STAT, arg) == -1)
return -1;
return (int)ds.sem_nsems;
}
/* Initialize all semaphores using an array */
static inline int semctl_setall(int semid, unsigned short *values)
{
union semun arg;
arg.array = values;
return semctl(semid, 0, SETALL, arg);
}
/* Read all semaphore values into a caller-allocated array */
static inline int semctl_getall(int semid, unsigned short *values)
{
union semun arg;
arg.array = values;
return semctl(semid, 0, GETALL, arg);
}
#endif /* SEM_HELPERS_H */
Interview Questions — semctl() and semun
Next: semid_ds — The Associated Data Structure
Understand the kernel data structure behind every semaphore set and its fields.
