POSIX Semaphores: Overview & Concepts

 

POSIX Semaphores: Overview & Concepts
Chapter 53, File 1 of 5 โ€” The Linux Programming Interface

What is a Semaphore?

A semaphore is a non-negative integer variable that is used to control access to a shared resource. The key idea is simple: before using a shared resource, a process/thread decrements (waits on) the semaphore. When done, it increments (posts to) the semaphore. If the semaphore value is zero, any process trying to decrement it will block until another process increments it.

The name “semaphore” comes from the railway signaling system where a flag (semaphore) controls whether a train can enter a section of track. Only one train is allowed in a section at a time โ€” just like only one process should access a critical section at a time.

๐Ÿ’ก Real-World Analogy: Parking Lot

Think of a parking lot with 3 spaces. The semaphore value = 3 (number of available spaces). When a car enters, the count goes down (sem_wait). When a car leaves, count goes up (sem_post). If count = 0 (lot full), the next car waits at the gate until someone leaves. A binary semaphore (value 0 or 1) works like a single toilet with a lock.

POSIX Semaphores vs System V Semaphores

Aspect POSIX Semaphores System V Semaphores
API complexity Simple, clean Complex, many flags
Header <semaphore.h> <sys/sem.h>
Identifier Name string or memory address Integer key (IPC key)
Create & Init Atomic (one call) Two separate steps (race condition possible)
Semaphore sets One semaphore per object Can group multiple semaphores
Thread-safe Yes (designed for threads too) Process-oriented
Linux implementation futex(2) system call semop(2) system call
Persistence Named: kernel persistence; Unnamed: memory lifetime Kernel persistence (survives reboot only when re-created)
Preferred for New code, threads, simple IPC Legacy systems, complex semaphore sets

Two Types of POSIX Semaphores

๐Ÿ”— Named Semaphore
  • Has a filesystem name like /mysem
  • Created/opened with sem_open()
  • Unrelated processes can share it
  • Lives in /dev/shm/sem.mysem
  • Kernel persistence (survives process death)
  • Must call sem_unlink() to delete
๐Ÿ“Œ Unnamed Semaphore
  • No name โ€” lives in a memory location
  • Created with sem_init()
  • Shared between threads or related processes
  • For processes: must be in shared memory
  • Lifetime = lifetime of the memory region
  • Must call sem_destroy() to clean up

The sem_t Data Type

sem_t is an opaque data type defined in <semaphore.h>. You should never directly access or copy its fields. Always use the API functions to manipulate it.

โš  Important Rule: Never copy a sem_t variable and operate on the copy. The result is undefined behavior. Always use the pointer returned by sem_open() or the address of the sem_t variable passed to sem_init().
/* WRONG - do not do this */
sem_t *sp = sem_open("/mysem", O_CREAT, 0660, 1);
sem_t copy = *sp;       /* WRONG: copying sem_t */
sem_wait(&copy);        /* UNDEFINED BEHAVIOR */

/* CORRECT */
sem_t *sp = sem_open("/mysem", O_CREAT, 0660, 1);
sem_wait(sp);           /* correct: use the pointer directly */

How POSIX Semaphores Work Internally on Linux

On Linux 2.6+ with NPTL (Native POSIX Thread Library), semaphore increment and decrement operations are implemented using the futex(2) system call (Fast Userspace muTEX).

sem_wait() called โ†’ Check semaphore value in userspace (atomic) โ†’ Value > 0?
Decrement & return immediately (no syscall)
Value = 0: call futex() to block in kernel โ† Value = 0: cannot decrement

The key insight: when there is no contention (semaphore value > 0), the entire sem_wait() operation happens in userspace without entering the kernel. This makes it very fast. The kernel (via futex) is only involved when blocking is actually needed.

Minimal POSIX Semaphore Program

Here is the most basic structure of a program using a POSIX named semaphore:

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>      /* O_CREAT, O_EXCL */
#include <sys/stat.h>   /* mode constants */

int main(void)
{
    sem_t *sem;

    /* Open (or create) a named semaphore with initial value 1 */
    sem = sem_open("/my_semaphore", O_CREAT, 0660, 1);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        exit(EXIT_FAILURE);
    }

    /* --- Decrement (lock): enter critical section --- */
    if (sem_wait(sem) == -1) {
        perror("sem_wait");
        exit(EXIT_FAILURE);
    }

    /* --- Critical section: access shared resource here --- */
    printf("Inside critical section\n");

    /* --- Increment (unlock): leave critical section --- */
    if (sem_post(sem) == -1) {
        perror("sem_post");
        exit(EXIT_FAILURE);
    }

    /* Close our reference to the semaphore */
    if (sem_close(sem) == -1) {
        perror("sem_close");
        exit(EXIT_FAILURE);
    }

    /* Remove the semaphore from the system (only once, by one process) */
    if (sem_unlink("/my_semaphore") == -1) {
        perror("sem_unlink");
        exit(EXIT_FAILURE);
    }

    return 0;
}
Compile:

gcc program.c -o program -pthread
# or
gcc program.c -o program -lrt

Linux Support Notes

Linux Version Support Level
Linux 2.4 Partial: only unnamed thread-shared semaphores
Linux 2.6 + NPTL glibc โœ… Full POSIX semaphore implementation (named + unnamed)
Linux 2.6+ (named) โœ… Named semaphores supported since kernel 2.6

๐Ÿ…พ Interview Questions & Answers

Q1. What is the difference between a binary semaphore and a counting semaphore?

A binary semaphore can only take values 0 or 1, behaving like a mutex lock. A counting semaphore can take any non-negative integer value, representing the count of available resources. POSIX semaphores support both: initialize with value 1 for binary behavior, or N for counting N resources.

Q2. What are the two types of POSIX semaphores?

Named semaphores: identified by a string name (e.g., /mysem), can be shared between unrelated processes, created with sem_open(). Unnamed semaphores: live in a memory region, shared between threads or related processes, created with sem_init().

Q3. Can a POSIX semaphore value go below zero?

No. The semaphore value is never permitted to go below 0. If a process tries to decrement a semaphore whose value is already 0, it either blocks (sem_wait) or returns an error (sem_trywait with EAGAIN).

Q4. Why are POSIX semaphores preferred over System V semaphores for new code?

POSIX semaphores have a simpler API, atomic creation + initialization (no race during init), work naturally with threads, and use the faster futex mechanism on Linux. System V semaphores require multiple calls to create and initialize, have complex semaphore sets, and are more suited to legacy code.

Q5. What Linux kernel feature is used to implement POSIX semaphores?

futex(2) โ€” Fast Userspace muTEX. It allows semaphore operations to complete entirely in userspace when there is no contention (semaphore value > 0). Only when blocking is needed does it make a system call into the kernel.

Q6. What happens if you copy a sem_t variable and use the copy?

The behavior is undefined. sem_t is an opaque type whose internals may include pointers or kernel-side references. Copying it with = does a shallow copy that is meaningless. You must always use the original pointer (for named semaphores) or the original variable’s address (for unnamed semaphores).

Q7. What is SEM_FAILED and when is it returned?

SEM_FAILED is the error return value of sem_open(). It is typically defined as ((sem_t *) 0) on Linux. It is returned when sem_open() fails, for example due to invalid name format, insufficient permissions, or when O_EXCL is specified and the semaphore already exists.

Leave a Reply

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