System V Semaphores ntroduction, Semaphore Sets & semget()

System V Semaphores — Part 1
Introduction, Semaphore Sets & semget() | TLPI Chapter 47
Topic
IPC Synchronization
API
semget()
Level
Intermediate
What Are Semaphores?

A semaphore is a kernel-maintained integer used to coordinate access to shared resources between processes or threads. Unlike mutexes which are purely binary (locked/unlocked), semaphores can hold non-negative integer values and support counting semantics.

The core idea: before accessing a shared resource, a process decrements (waits on) the semaphore. If the value would go negative, the process blocks. When done, the process increments (posts) the semaphore, potentially waking up a waiting process.

System V semaphores were introduced with UNIX System V and are part of the “System V IPC” trio — semaphores, message queues, and shared memory. They are identified by integer keys, not file descriptors.

How a Semaphore Controls Access
Process A
wants shared memory
Semaphore Value = 1
decrement → value = 0 → OK, enter
Shared Resource
e.g. shared memory
Process B
tries to enter — BLOCKS
Semaphore Value = 0
decrement → would be -1 → BLOCK
Currently used
by Process A
Key Terms for This Section

semaphore set semget() IPC key ftok() IPC_CREAT IPC_EXCL semid nsems semaphore identifier

Semaphore Sets — The Unique System V Design

System V semaphores are allocated in sets. A semaphore set contains one or more semaphores. Each semaphore within the set is an independent non-negative integer. The entire set is treated as a single object — it has one identifier, one set of permissions, and one creation/deletion lifecycle.

When you create a semaphore set, you specify how many semaphores it contains. You then operate on individual semaphores by their index (0-based) within the set.

semid (e.g. 5) Semaphore Set (nsems = 4)
sem[0]
val=1
sem[1]
val=0
sem[2]
val=3
sem[3]
val=0

Most applications only need a single semaphore, but the set structure is always present.

semget() — Create or Open a Semaphore Set

semget() is the first call you make. It either creates a new semaphore set or opens an existing one.

#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
/* Returns semaphore set identifier (semid) on success, -1 on error */

Parameters:

Parameter Description
key IPC key — identifies the semaphore set. Use ftok() to generate or IPC_PRIVATE for private sets.
nsems Number of semaphores in the set. Ignored when opening an existing set (pass 0).
semflg Flags + permission bits. Common: IPC_CREAT, IPC_EXCL, e.g. IPC_CREAT | 0660

Flag combinations and their meaning:

Flags Behaviour
0 (no flags) Open existing set; fail (ENOENT) if not found
IPC_CREAT Create if not exists; open if exists
IPC_CREAT | IPC_EXCL Create new; fail (EEXIST) if already exists

Code Example 1 — Creating a Semaphore Set
#include <stdio.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <errno.h>

int main(void)
{
    key_t key;
    int semid;

    /* Generate a key from a file path + project id */
    key = ftok("/tmp/mysem_file", 42);
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    /*
     * Create a semaphore set with 2 semaphores.
     * IPC_CREAT | 0600 : create if not exists, owner r/w only.
     */
    semid = semget(key, 2, IPC_CREAT | IPC_EXCL | 0600);
    if (semid == -1) {
        if (errno == EEXIST) {
            /* Already exists — open it instead */
            semid = semget(key, 0, 0);
            if (semid == -1) { perror("semget open"); exit(EXIT_FAILURE); }
            printf("Opened existing semaphore set, id=%d\n", semid);
        } else {
            perror("semget create");
            exit(EXIT_FAILURE);
        }
    } else {
        printf("Created new semaphore set, id=%d\n", semid);
    }

    return 0;
}

Code Example 2 — Private Semaphore (Parent-Child)

IPC_PRIVATE always creates a new semaphore set regardless of any existing sets. It is useful when a process wants a semaphore shared only with its children (the children inherit the semid across fork).

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/wait.h>

int main(void)
{
    int semid;
    pid_t pid;

    /* IPC_PRIVATE creates a fresh set; nsems=1, one semaphore */
    semid = semget(IPC_PRIVATE, 1, 0600);
    if (semid == -1) {
        perror("semget");
        exit(EXIT_FAILURE);
    }
    printf("Private semid = %d\n", semid);

    pid = fork();
    if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }

    if (pid == 0) {
        /* Child: same semid is valid here after fork */
        printf("Child: semid = %d (inherited)\n", semid);
        exit(0);
    }

    wait(NULL);

    /* Parent must clean up — kernel does NOT auto-delete */
    if (semctl(semid, 0, IPC_RMID) == -1)
        perror("semctl IPC_RMID");
    else
        printf("Semaphore set deleted\n");

    return 0;
}

Generating Keys with ftok()
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
/* Returns IPC key on success, -1 on error */

ftok() generates a key by combining the inode number and device of pathname with the low 8 bits of proj_id. The file must exist and be accessible. Multiple unrelated processes can arrive at the same key by agreeing on the same pathname and proj_id.

/* Both processes use the same key: */
key_t key = ftok("/usr/local/myapp/lockfile", 1);

/*
 * WARNING: if the file is deleted and recreated, its inode number
 * changes, so ftok() will return a different key.
 * Use a stable, long-lived file for the pathname.
 */

Semaphore Permissions

Like files, semaphore sets have ownership (UID/GID) and permission bits. However, there is no execute bit — only read and write/alter bits are meaningful.

Permission Octal Meaning
Owner read 0400 Can read semaphore values (semctl GETVAL etc.)
Owner write/alter 0200 Can perform semop() and semctl() modifications
Group read 0040 Group members can read
Group write 0020 Group members can alter
Other read 0004 Others can read
Other write 0002 Others can alter
/* Common permission patterns */
semget(key, 1, IPC_CREAT | 0600);  /* only owner can use */
semget(key, 1, IPC_CREAT | 0660);  /* owner + group */
semget(key, 1, IPC_CREAT | 0666);  /* everyone — use carefully */

Interview Questions — semget() & Semaphore Sets

Q1. What is the difference between a semaphore and a mutex?

A mutex is a binary lock owned by the thread that locked it — only that thread can unlock it. A semaphore is a kernel counter with no ownership. It can be incremented by any process/thread and supports values greater than 1 (counting semaphore). A binary semaphore (value 0 or 1) behaves similarly to a mutex but without ownership semantics.

Q2. Why does semget() operate on sets of semaphores rather than individual ones?

The System V design allows atomic operations on multiple semaphores simultaneously using a single semop() call. This prevents deadlocks in situations where a process must acquire multiple resources at once — if all increments/decrements happen atomically, there is no window for partial acquisition that leads to circular waiting.

Q3. What is IPC_PRIVATE and when should you use it?

IPC_PRIVATE is a special key value (0) that always creates a brand-new semaphore set. It is appropriate when the semaphore will be shared only between related processes (parent and children) because the semid is passed via inheritance through fork(), not through a shared key.

Q4. What happens to a System V semaphore set when all processes using it exit?

Nothing — it persists in the kernel until explicitly deleted with semctl(semid, 0, IPC_RMID) or the system reboots. This is a major difference from POSIX semaphores and file-based mechanisms, and is one of the well-known disadvantages of System V IPC.

Q5. What is the role of ftok() and what are its limitations?

ftok() derives an IPC key from a file’s inode number and a project ID byte. Its main limitation is that if the file is deleted and recreated, the inode changes and ftok() returns a different key — causing processes to fail to find each other’s semaphore sets. Also, two different path/project_id pairs can theoretically collide to produce the same key (very rare but possible).

Q6. What does the nsems parameter mean in semget(), and what value should you pass when opening an existing set?

nsems specifies how many semaphores to create in the new set. When opening an existing set (not creating), pass 0 — the value is ignored by the kernel in that case.

Next: semop() — Performing Semaphore Operations

Learn how to wait on, signal, and zero-test semaphores using the semop() system call.

Leave a Reply

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