System V Semaphore Limits

 

System V Semaphore Limits
Chapter 47 · The Linux Programming Interface · Part 5
📐 Kernel Parameters
🔧 Tuning
🖥️ /proc Interface

Why Semaphore Limits Matter

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.

All System V Semaphore Limits
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()

Reading and Changing Limits via /proc

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

Querying Limits from C Using semctl(IPC_INFO)
#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;
}

Visual: How the Limits Relate to Each Other

Entire System (bounded by SEMMNI and SEMMNS)

Semaphore Set 0 (semid=100)
sem[0]
1
sem[1]
0
sem[2]
5
3 sems ≤ SEMMSL
values ≤ SEMVMX (32767)

Semaphore Set 1 (semid=101)
sem[0]
3
sem[1]
0
2 sems ≤ SEMMSL

… up to SEMMNI sets …

Total semaphores across all sets = 3 + 2 + … ≤ SEMMNS
Each semop() call may include up to SEMOPM operations per call

Checking Limits Before Creating Semaphore Sets
#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;
}

Common Limit-Related Errors and Solutions
semget() returns ENOSPC

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.

semget() returns EINVAL

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.

semop() returns ERANGE

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.

semop() returns E2BIG

Cause: nsops argument to semop() exceeds SEMOPM limit.

Fix: Split the operation into multiple semop() calls, or increase SEMOPM.

Cleaning Up Stale Semaphores — Shell and C
#!/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);
            }
        }
    }
}

Interview Questions — Semaphore Limits
Q1: What does SEMVMX control and what error occurs when it is exceeded?
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.
Q2: How do you read System V semaphore limits on Linux without reading /proc manually?
By calling 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.
Q3: A semget() call fails with ENOSPC even though you only have a few semaphore sets. What are two possible causes?
1. The SEMMNI limit (maximum number of sets system-wide) is too low for the current workload — other processes may have created many sets. 2. The SEMMNS limit (total semaphores system-wide) is exhausted — even if the number of sets is low, the total semaphore count across all sets may have reached its maximum. Check with ipcs -s and look for abandoned semaphore sets from crashed processes.
Q4: What format does /proc/sys/kernel/sem use, and what are the four values?
It contains a single line with four space-separated integers in order: SEMMSL (max semaphores per set), SEMMNS (max total semaphores system-wide), SEMOPM (max operations per semop() call), and SEMMNI (max number of semaphore sets). For example: 250 32000 32 128.

Leave a Reply

Your email address will not be published. Required fields are marked *