POSIX Semaphores Overview

 

POSIX Semaphores Overview
Chapter 53 – Part 1 of 6 | The Linux Programming Interface

What is a Semaphore?

A semaphore is a kernel-maintained integer variable used for synchronization. Its value is always non-negative (zero or greater). Two fundamental operations are performed on it:

  • Wait (P / decrement): If the semaphore value is greater than zero, decrement it and continue. If it is zero, block (sleep) until it becomes greater than zero.
  • Post (V / increment): Increment the semaphore value. If any process or thread is blocked waiting on this semaphore, wake one of them up.

The original concepts come from Dutch computer scientist Edsger Dijkstra. He called the operations P (proberen = to test) and V (verhogen = to increment). In POSIX, these are sem_wait() and sem_post().

Two Types of POSIX Semaphores

POSIX provides two kinds of semaphores. They use the same operations (sem_wait, sem_post, etc.) but differ in how they are created and where they live.

Property Named Semaphore Unnamed Semaphore
Identity Has a filesystem name like /mysem No name, lives in memory (a variable)
Create / Open sem_open() sem_init()
Destroy sem_close() + sem_unlink() sem_destroy()
Sharing Any unrelated process by name Must be in shared memory for processes; global variable for threads
Persistence Persists until explicitly unlinked Lives only as long as the memory it is in

Where Named Semaphores Live

On Linux, named semaphores are implemented as small files inside a special in-memory filesystem. When you call sem_open("/mysem", ...), Linux creates a file at:

/dev/shm/sem.mysem

You can see this with ls /dev/shm/. The /dev/shm mount point is a tmpfs filesystem — it lives entirely in RAM and is cleared on reboot. The semaphore name must start with a forward slash and contain no other slashes. Portable programs use exactly one slash at the start.

Named Semaphore: Kernel Storage Model
Process A
sem_open("/s")
Kernel
/dev/shm/sem.s
value = 1
Process B
sem_open("/s")
Both processes open the same semaphore by name. The kernel maintains one shared value.

The sem_t Data Type

The type sem_t is defined in <semaphore.h>. It is an opaque type — you should never try to read or write its internals directly. Always use the POSIX API functions.

  • For named semaphores, sem_open() returns a pointer to sem_t.
  • For unnamed semaphores, you declare a sem_t variable and pass its address to sem_init().
#include <semaphore.h>

/* Named semaphore – pointer returned by sem_open */
sem_t *named_sem;

/* Unnamed semaphore – you own the variable */
sem_t unnamed_sem;

Required Header and Linking

Every program using POSIX semaphores must:

  1. Include #include <semaphore.h>
  2. Link against the POSIX real-time library: compile with -lpthread (on Linux, semaphore functions are in the pthreads library)
gcc myprog.c -o myprog -lpthread

On some older Linux systems you may also need -lrt. On modern Linux with glibc, -lpthread is sufficient for all POSIX semaphore functions.

Binary Semaphore vs Counting Semaphore

POSIX semaphores support two usage patterns:

Binary Semaphore

Value is only 0 or 1. Acts like a mutex. One resource to protect, one holder at a time.

sem_init(&s, 0, 1);  /* initial = 1 */
Counting Semaphore

Value can be any non-negative integer. Controls access to a pool of N resources (e.g., 5 database connections).

sem_init(&s, 0, 5);  /* pool of 5 */

Complete Minimal Example

Here is the simplest possible POSIX semaphore program — two threads, one semaphore, used as a signal from thread 1 to thread 2.

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

sem_t sem;    /* unnamed semaphore, shared between threads */

void *thread_wait(void *arg)
{
    printf("Thread: waiting for signal...\n");
    sem_wait(&sem);          /* block until value > 0 */
    printf("Thread: got signal, continuing.\n");
    return NULL;
}

int main(void)
{
    pthread_t tid;

    sem_init(&sem, 0, 0);   /* pshared=0 (threads), initial value=0 */

    pthread_create(&tid, NULL, thread_wait, NULL);

    sleep(2);                /* simulate some work */
    printf("Main: posting semaphore.\n");
    sem_post(&sem);          /* increment, unblock the waiting thread */

    pthread_join(tid, NULL);
    sem_destroy(&sem);
    return 0;
}

Compile and run:

gcc minimal_sem.c -o minimal_sem -lpthread
./minimal_sem
# Output:
# Thread: waiting for signal...
# Main: posting semaphore.
# Thread: got signal, continuing.

Interview Questions – POSIX Semaphore Overview

Q1. What is the difference between a semaphore and a mutex?
A mutex is a locking mechanism where only the thread that locked it can unlock it (ownership model). A semaphore has no ownership — any thread or process can call sem_post to increment it. Semaphores are better suited for signaling between threads, while mutexes are better for protecting critical sections.
Q2. What are the two types of POSIX semaphores?
Named semaphores (identified by a filesystem path like /mysem, opened with sem_open) and unnamed semaphores (a sem_t variable initialized with sem_init, placed in shared memory or a global for sharing).
Q3. Where do named semaphores reside on Linux?
Linux stores named semaphores as files in /dev/shm/ with the prefix “sem.” — so sem_open(“/foo”, …) creates /dev/shm/sem.foo. This is a tmpfs filesystem in RAM.
Q4. Can a semaphore value go negative?
In the POSIX standard, no. The semaphore value is always non-negative. When it is zero and sem_wait is called, the caller blocks rather than decrementing to a negative number. Some older BSD implementations allowed negative values to track the number of waiters, but Linux and SUSv3 do not.
Q5. What header file and linker flag are required for POSIX semaphores?
Include <semaphore.h> and link with -lpthread (gcc prog.c -lpthread). On older systems, -lrt may also be needed.
Q6. What is a counting semaphore? Give a real use case.
A counting semaphore has an initial value N and represents N available resources. A classic use case is a thread pool with N workers — initialize the semaphore to N, and each worker does sem_wait before starting a task and sem_post when done, limiting concurrency to N tasks at a time.
Q7. Why is sem_t treated as an opaque type?
The internal structure of sem_t is implementation-defined and may change between OS versions. Accessing its members directly would make code non-portable and fragile. Always use the API functions.

Leave a Reply

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