System V semaphores are kernel resources. The kernel enforces hard limits on how many you can create, how large they can be, and how many operations can be done at once. Hitting these limits returns errors like ENOSPC (no space) or EINVAL (invalid argument). Knowing the limits helps you design robust applications and troubleshoot failures.
On Linux, these limits are tunable at runtime via /proc/sys/kernel/sem.
| Limit Name | Meaning | Typical Default (Linux) | Error when exceeded |
|---|---|---|---|
| SEMMNI | Maximum number of semaphore sets (identifiers) system-wide | 32000 | ENOSPC from semget() |
| SEMMSL | Maximum number of semaphores per set | 32000 | EINVAL from semget() |
| SEMMNS | Maximum total semaphores across all sets system-wide | 1024000000 (effectively SEMMNI×SEMMSL) | ENOSPC from semget() |
| SEMVMX | Maximum value a semaphore can hold | 32767 | ERANGE from semop() or semctl() |
| SEMOPM | Maximum number of operations per semop() call (nsops limit) | 500 | E2BIG from semop() |
| SEMAEM | Maximum semadj value (SEM_UNDO adjustment per process) | SEMVMX (32767) | ERANGE from semop() |
Linux exposes all four tunable limits in a single line in /proc/sys/kernel/sem:
# Read current limits
$ cat /proc/sys/kernel/sem
250 32000 32 128
# Format: SEMMSL SEMMNS SEMOPM SEMMNI
# 250 32000 32 128
# (on a typical system; modern kernels have much higher defaults)
# Meaning:
# SEMMSL = 250 max semaphores per set
# SEMMNS = 32000 max semaphores system-wide
# SEMOPM = 32 max ops per semop() call
# SEMMNI = 128 max number of semaphore sets
# Increase limits (as root)
$ echo "1000 1024000 500 32000" > /proc/sys/kernel/sem
# Make permanent across reboots (in /etc/sysctl.conf):
# kernel.sem = 1000 1024000 500 32000
# Apply without reboot:
$ sysctl -p
# View with sysctl
$ sysctl kernel.sem
kernel.sem = 250 32000 32 128
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
/* The seminfo structure (Linux-specific, returned by IPC_INFO) */
/* Available via sys/sem.h on Linux */
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf; /* for IPC_INFO */
};
void print_sem_limits(void) {
union semun arg;
struct seminfo info;
arg.__buf = &info;
/* semid = 0 and semnum = 0 are ignored for IPC_INFO */
if (semctl(0, 0, IPC_INFO, arg) == -1) {
perror("semctl IPC_INFO");
return;
}
printf("=== System V Semaphore Limits ===\n");
printf(" SEMMNI (max semaphore sets) : %d\n", info.semmni);
printf(" SEMMSL (max sems per set) : %d\n", info.semmsl);
printf(" SEMMNS (max sems system-wide) : %d\n", info.semmns);
printf(" SEMVMX (max semaphore value) : %d\n", info.semvmx);
printf(" SEMOPM (max ops per semop call) : %d\n", info.semopm);
printf(" SEMAEM (max SEM_UNDO adjustment) : %d\n", info.semaem);
}
/* Count currently used semaphore sets */
void print_sem_usage(void) {
union semun arg;
struct seminfo info;
arg.__buf = &info;
int max_id = semctl(0, 0, IPC_INFO, arg);
if (max_id == -1) { perror("IPC_INFO"); return; }
printf("\n=== Current Usage ===\n");
printf(" Maximum semid in use : %d\n", max_id);
/* Walk through semaphore sets to count active ones */
int count = 0;
for (int i = 0; i <= max_id; i++) {
struct semid_ds ds;
union semun u;
u.buf = &ds;
if (semctl(i, 0, IPC_STAT, u) == 0)
count++;
}
printf(" Active semaphore sets: %d\n", count);
}
int main(void) {
print_sem_limits();
print_sem_usage();
return 0;
}
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
/* Check if creating nsems semaphores in one set is feasible */
int check_limits(int nsems_requested) {
union semun arg;
struct seminfo info;
arg.__buf = &info;
if (semctl(0, 0, IPC_INFO, arg) == -1) {
perror("IPC_INFO");
return -1;
}
if (nsems_requested > info.semmsl) {
fprintf(stderr, "Error: requested %d sems, max per set is %d (SEMMSL)\n",
nsems_requested, info.semmsl);
return -1;
}
printf("Limits OK: requesting %d sems, SEMMSL=%d, SEMVMX=%d, SEMOPM=%d\n",
nsems_requested, info.semmsl, info.semvmx, info.semopm);
return 0;
}
/* Safe semop wrapper that checks nsops limit */
int safe_semop(int semid, struct sembuf *sops, size_t nsops) {
union semun arg;
struct seminfo info;
arg.__buf = &info;
if (semctl(0, 0, IPC_INFO, arg) != -1) {
if ((int)nsops > info.semopm) {
fprintf(stderr, "Error: %zu ops exceeds SEMOPM limit of %d\n",
nsops, info.semopm);
return -1;
}
}
return semop(semid, sops, nsops);
}
int main(void) {
/* Check before attempting to create */
if (check_limits(10) == 0) {
int semid = semget(IPC_PRIVATE, 10, 0600);
if (semid == -1) {
if (errno == ENOSPC)
fprintf(stderr, "System limit SEMMNI or SEMMNS reached!\n");
else
perror("semget");
} else {
printf("Created semid=%d with 10 semaphores\n", semid);
semctl(semid, 0, IPC_RMID);
}
}
return 0;
}
Cause: SEMMNI (max sets) or SEMMNS (max total sems) limit reached.
Fix: Run ipcs -s to find stale semaphore sets and remove them with ipcrm -s. Or increase SEMMNI via /proc/sys/kernel/sem.
Cause: nsems > SEMMSL, or nsems = 0 when creating, or nsems > existing set size.
Fix: Reduce the number of semaphores in the set, or increase SEMMSL.
Cause: Adding sem_op would push the semaphore value above SEMVMX (32767).
Fix: Logic error in your code — you are releasing a semaphore more times than you acquired it. Check acquire/release pairing.
Cause: nsops argument to semop() exceeds SEMOPM limit.
Fix: Split the operation into multiple semop() calls, or increase SEMOPM.
#!/bin/bash
# Shell: list and remove all semaphore sets owned by current user
echo "Current semaphore sets:"
ipcs -s
echo ""
echo "Removing all semaphore sets owned by $USER..."
for semid in $(ipcs -s | awk -v user="$USER" '$3 == user {print $2}'); do
echo " Removing semid=$semid"
ipcrm -s "$semid"
done
echo "Done. Remaining:"
ipcs -s
/* C program: remove all semaphore sets created by this process */
#include <sys/sem.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <unistd.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void cleanup_own_semaphores(void) {
union semun arg;
struct seminfo info;
struct semid_ds ds;
union semun su;
arg.__buf = &info;
int max_id = semctl(0, 0, IPC_INFO, arg);
if (max_id < 0) return;
su.buf = &ds;
for (int i = 0; i <= max_id; i++) {
if (semctl(i, 0, IPC_STAT, su) == 0) {
if (ds.sem_perm.uid == getuid()) {
printf("Removing semaphore set semid=%d\n", i);
semctl(i, 0, IPC_RMID);
}
}
}
}
SEMVMX is the maximum value a semaphore can hold (typically 32767). If a semop() would increase a semaphore above this value, the call fails with errno = ERANGE. This also applies to semctl(SETVAL) if the value exceeds SEMVMX.semctl(0, 0, IPC_INFO, arg) where arg.__buf points to a struct seminfo. This Linux-specific command fills in all limits including semmni, semmsl, semmns, semvmx, semopm, and semaem. The return value is the highest semaphore set ID currently in use.ipcs -s and look for abandoned semaphore sets from crashed processes.250 32000 32 128.