free linux system programming course System V Semaphores

 

System V Semaphores
Chapter 47 · The Linux Programming Interface · Part 1: Introduction
📘 Theory Deep Dive
💻 Code Examples
🎯 Interview Ready

Chapter 47 Series Navigation

What is a Semaphore?

A semaphore is a kernel-maintained integer whose value is always greater than or equal to 0. It is used to synchronize the actions of multiple processes, not to transfer data between them.

Unlike pipes or message queues that move data from one process to another, semaphores are purely about coordination — they let processes agree on when it is safe to access a shared resource.

The most common use case is controlling access to a block of shared memory: before any process reads or writes the shared memory, it first checks (and adjusts) the semaphore to make sure no other process is currently using it.

Operations You Can Perform on a Semaphore

The kernel lets you do four things with a semaphore value:

Operation Description Blocks?
Set absolute value Force the semaphore to a specific integer (e.g., set to 1) No
Add a number Increase the semaphore value (signal / release) No
Subtract a number Decrease the semaphore value (wait / acquire). Blocks if result would go below 0. Yes
Wait for zero Block until the semaphore value becomes 0 Yes

Key Rule: The kernel will never allow a semaphore to go below 0. If a subtraction would make it negative, the calling process is put to sleep until another process adds enough to make the subtraction possible.

How a Semaphore Synchronizes Two Processes

The diagram below shows the classic producer-consumer synchronization using a semaphore initialized to 0:

Process A (Producer)
1. Create semaphore set
2. Initialize semaphore = 0
… does some work …
3. Add 1 to semaphore → value becomes 1

semaphore
value

Process B (Consumer)
1. Try to subtract 1 → value = 0-1 = -1 ❌
2. BLOCKED — waiting for value ≥ 1
3. Wakes up when Process A adds 1 → subtracts 1 ✅

Timeline View
A: create+init(0)
B: subtract(1) → BLOCK
A: add(1)
B: UNBLOCKED subtract(1)
B: continues
→ time flows left to right

System V Semaphores Work in Sets

Unlike POSIX semaphores where you create one semaphore at a time, System V semaphores are always created as a group called a semaphore set. Each set can contain one or more individual semaphores, all sharing the same semaphore ID.

Semaphore Set (semid = 1234)
sem[0]
3
mutex sem
sem[1]
0
ready flag
sem[2]
5
resource count
Note: All three semaphores share one semid. You reference them by index: sem[0], sem[1], sem[2].

This design is unusually complex compared to most OSes because:

  • Creation (semget) and initialization (semctl) are two separate steps
  • This separation can cause race conditions if two processes try to initialize at the same time
  • The semop() call can atomically operate on multiple semaphores in one set at once

General Steps to Use a System V Semaphore
1
semget() — Create or open a semaphore set
Returns a semaphore ID (semid) for the set
2
semctl(SETVAL or SETALL) — Initialize semaphore values
Only ONE process should do this to avoid race conditions
3
semop() — Perform operations (acquire/release resources)
Add, subtract, or wait-for-zero on semaphore values
4
semctl(IPC_RMID) — Delete the semaphore set when done
Only ONE process should do this

Semaphore vs Mutex — Key Differences
Feature System V Semaphore Mutex (pthread)
Scope Between processes Between threads in one process
Value range 0 to SEMVMX (usually 32767) Locked (0) or Unlocked (1)
Ownership Any process can release Only the locker can unlock
Persistence Persists until explicitly deleted or reboot Destroyed when process exits
Works in sets Yes (semaphore sets) No
Initialization Separate step (race condition risk) At creation time

Complete Demo Program — svsem_demo.c

This program shows the basic usage of all three semaphore system calls. Run it in two modes:

  • One argument: create a new semaphore set and initialize it to the given value
  • Two arguments: perform an add/subtract operation on an existing semaphore
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

/* semun union - needed by semctl() */
union semun {
    int              val;    /* for SETVAL */
    struct semid_ds *buf;    /* for IPC_STAT and IPC_SET */
    unsigned short  *array;  /* for GETALL and SETALL */
};

/* Simple timestamp helper */
void print_time() {
    time_t t = time(NULL);
    struct tm *tm_info = localtime(&t);
    char buf[20];
    strftime(buf, sizeof(buf), "%H:%M:%S", tm_info);
    printf("%s", buf);
}

int main(int argc, char *argv[])
{
    int semid;

    if (argc < 2 || argc > 3) {
        fprintf(stderr, "Usage:\n");
        fprintf(stderr, "  %s <init-value>          # create semaphore\n", argv[0]);
        fprintf(stderr, "  %s <semid> <operation>   # operate on semaphore\n", argv[0]);
        return 1;
    }

    if (argc == 2) {
        /* ---- CREATE and INITIALIZE a new semaphore set ---- */
        union semun arg;

        /* semget: IPC_PRIVATE = unique key, 1 semaphore in set */
        semid = semget(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR);
        if (semid == -1) {
            perror("semget");
            exit(1);
        }

        /* semctl SETVAL: set semaphore 0 to the given initial value */
        arg.val = atoi(argv[1]);
        if (semctl(semid, 0, SETVAL, arg) == -1) {
            perror("semctl SETVAL");
            exit(1);
        }

        printf("Semaphore ID = %d\n", semid);

    } else {
        /* ---- OPERATE on an existing semaphore ---- */
        struct sembuf sop;

        semid         = atoi(argv[1]);  /* semaphore set ID */
        sop.sem_num   = 0;              /* which semaphore in the set */
        sop.sem_op    = atoi(argv[2]);  /* positive=add, negative=subtract, 0=wait-for-zero */
        sop.sem_flg   = 0;              /* no special flags */

        printf("%ld: about to semop at ", (long)getpid());
        print_time();
        printf("\n");

        /* This call may block if sem_op would make value go below 0 */
        if (semop(semid, &sop, 1) == -1) {
            perror("semop");
            exit(1);
        }

        printf("%ld: semop completed at ", (long)getpid());
        print_time();
        printf("\n");
    }

    return 0;
}

Shell session showing blocking behaviour:

# Step 1: Create semaphore initialized to 0
$ ./svsem_demo 0
Semaphore ID = 98307

# Step 2: Try to subtract 2 (will BLOCK because 0 - 2 would go below 0)
$ ./svsem_demo 98307 -2 &
23338: about to semop at 10:19:42
[1] 23338

# Step 3: Add 3 — this wakes up the background process
$ ./svsem_demo 98307 +3
23339: about to semop at 10:19:55
23339: semop completed at 10:19:55
23338: semop completed at 10:19:55     # background job unblocked!
[1]+ Done   ./svsem_demo 98307 -2

After adding 3, the semaphore value = 3. The background subtract-2 can now proceed (3 – 2 = 1 ≥ 0), so the kernel wakes it up.

Common Use Cases for Semaphores
🔒 Mutual Exclusion

Initialize to 1. Process does subtract-1 before entering critical section, add-1 when done. Only one process can be inside at a time.

📦 Resource Counting

Initialize to N (number of resources). Each consumer subtracts 1. When all N are used, new requests block. Producers add 1 when returning resources.

🔔 Signaling

Initialize to 0. Process B waits (subtract-1 blocks). Process A does its work, then adds 1, which wakes Process B. Replaces signals for parent-child sync after fork().

🧵 Shared Memory Guard

Pair with a shared memory segment. Semaphore protects the memory from simultaneous access by multiple processes.

Required Header Files
#include <sys/types.h>   /* Required for portability (older systems) */
#include <sys/ipc.h>     /* Defines IPC_PRIVATE, IPC_CREAT, IPC_EXCL, ftok() */
#include <sys/sem.h>     /* Declares semget(), semctl(), semop() */
                          /* Also defines: struct sembuf, struct semid_ds */
                          /* Also defines: SETVAL, GETVAL, IPC_RMID, etc. */

/* The semun union - must be declared by the programmer on Linux */
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-specific) */
};
Important: On Linux, the semun union is not defined in any system header. You must declare it yourself in your code. POSIX specifies its members, but Linux requires you to write it.

Interview Questions — System V Semaphores Introduction
Q1: What is a semaphore and what is its primary purpose in Linux IPC?
A semaphore is a kernel-maintained non-negative integer used for process synchronization. Its primary purpose is to coordinate access to shared resources — particularly shared memory — so that multiple processes do not access the resource at the same time. Unlike pipes or message queues, semaphores do not transfer data; they only signal or gate access.
Q2: What happens if a process tries to subtract a value that would make a semaphore go below zero?
The kernel blocks the calling process. It stays blocked until some other process increments the semaphore enough so that the requested subtraction can complete without the result going below zero. At that point, the kernel automatically wakes the blocked process.
Q3: What is a semaphore set? Why does System V use sets instead of individual semaphores?
A semaphore set is a group of one or more semaphores allocated together and identified by a single semaphore ID (semid). System V uses sets to allow atomic operations across multiple semaphores simultaneously — for example, acquiring two resources atomically. The semop() system call can operate on several semaphores in a set in a single atomic step, which avoids deadlocks that would occur if the operations were done individually.
Q4: What race condition exists in System V semaphore creation and how can it be avoided?
Creation (semget) and initialization (semctl SETVAL/SETALL) are two separate steps. If two processes execute both steps concurrently, one might start using a semaphore that the other is still initializing. The solution is to designate exactly one process as the initializer (usually using IPC_CREAT | IPC_EXCL in semget so only one process creates it), and the initializer must complete semctl before other processes begin operations.
Q5: How is a semaphore different from a mutex?
A mutex is a binary lock (locked/unlocked) that can only be unlocked by the thread that locked it — it enforces ownership. A semaphore has no ownership concept: any process can increment or decrement it. Semaphores can hold values greater than 1 (counting semaphores for resource pools). System V semaphores work across processes; pthread mutexes work across threads within one process. Semaphores persist in the kernel even after the creating process exits; mutexes are automatically cleaned up.
Q6: Why must the semun union be declared by the programmer on Linux?
POSIX specifies that the fourth argument to semctl() is a union semun that the caller must supply. However, the POSIX standard does not mandate that this union be defined in the system headers — it leaves that to the implementation. Linux system headers intentionally omit it (per the standard), so the programmer must declare it manually in their source file before using semctl().

Next: semget() — Creating and Opening Semaphore Sets

Part 2: semget() →

Leave a Reply

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