POSIX Semaphores โ€“ Complete Programs sem_open, sem_wait, sem_post, sem_close, sem_unlink

 

POSIX Semaphores โ€“ Complete Programs
Full working examples combining sem_open, sem_wait, sem_post, sem_close, sem_unlink
๐Ÿ“˜ Chapter 53 โ€“ TLPI
๐Ÿ”ง Named & Unnamed Semaphores
๐ŸŽฏ All Concepts Combined

Putting It All Together

This file brings together all POSIX semaphore concepts from Chapter 53 into complete, compilable programs. Each program demonstrates a real-world scenario and uses multiple semaphore functions together. This is the best way to understand how the API fits as a system.

Topics Covered:

Named vs Unnamed Semaphores Process Synchronisation Thread Synchronisation Producer-Consumer Reader-Writer Semaphore Compile: -lpthread

Program 1: Named Semaphore โ€“ Two Processes Synchronising

This demonstrates using a named POSIX semaphore to synchronise two separate processes. The “sender” creates the semaphore and posts to it; the “receiver” waits on it.

Named Semaphore: Inter-Process Sync
Process A (Sender)

sem_open(“/sync”, O_CREAT, 0600, 0)
โ†“
do work…
โ†“
sem_post() โ† signals B
โ†“
sem_close()
sem_unlink()

โŸท Process B (Receiver)

sem_open(“/sync”, 0)
โ†“
sem_wait() โ† blocks until A posts
โ†“
process A’s output
โ†“
sem_close()

Both processes share /dev/shm/sem.sync on Linux

sender.c

/* sender.c โ€” compile: gcc sender.c -o sender -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>

#define SEM_NAME "/posix_sync_demo"

int main(void)
{
    sem_t *sem;

    printf("[Sender] Creating semaphore '%s' (initial value=0)\n", SEM_NAME);

    /* O_EXCL ensures we fail if it already exists (cleanup needed) */
    sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0);
    if (sem == SEM_FAILED) {
        /* Try removing stale semaphore and retry */
        sem_unlink(SEM_NAME);
        sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, 0);
        if (sem == SEM_FAILED) {
            perror("sem_open");
            exit(EXIT_FAILURE);
        }
    }

    printf("[Sender] Doing some work (3 seconds)...\n");
    sleep(3);

    printf("[Sender] Work done. Posting semaphore to wake receiver.\n");
    if (sem_post(sem) == -1) {
        perror("sem_post");
        sem_close(sem);
        sem_unlink(SEM_NAME);
        exit(EXIT_FAILURE);
    }

    sem_close(sem);

    /* Wait a bit before unlinking so receiver can open it */
    sleep(1);
    sem_unlink(SEM_NAME);
    printf("[Sender] Semaphore unlinked. Done.\n");

    return EXIT_SUCCESS;
}

receiver.c

/* receiver.c โ€” compile: gcc receiver.c -o receiver -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <semaphore.h>
#include <fcntl.h>

#define SEM_NAME "/posix_sync_demo"

int main(void)
{
    sem_t *sem;
    int ret;

    /* Open the semaphore created by sender (may need to retry if
       sender hasn't created it yet) */
    printf("[Receiver] Opening semaphore '%s'...\n", SEM_NAME);
    sem = sem_open(SEM_NAME, 0);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        exit(EXIT_FAILURE);
    }

    printf("[Receiver] Waiting for sender to complete work...\n");

    /* Wait, handling signal interruption */
    do {
        ret = sem_wait(sem);
    } while (ret == -1 && errno == EINTR);

    if (ret == -1) {
        perror("sem_wait");
        sem_close(sem);
        exit(EXIT_FAILURE);
    }

    printf("[Receiver] Sender signalled us! Processing sender's output.\n");

    sem_close(sem);
    printf("[Receiver] Done.\n");

    return EXIT_SUCCESS;
}
Run in two terminals:
Terminal 1: ./receiver
Terminal 2: ./sender

Program 2: Unnamed Semaphore โ€“ Thread-Safe Bounded Queue

A bounded queue shared between producer and consumer threads. Three unnamed semaphores handle: mutual exclusion, available slots, and available items.

/* bounded_queue.c โ€” gcc bounded_queue.c -o bq -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

#define QUEUE_SIZE  4
#define NUM_ITEMS  12

typedef struct {
    int  data[QUEUE_SIZE];
    int  head;
    int  tail;
    sem_t mutex;      /* protects head/tail access */
    sem_t empty;      /* counts empty slots */
    sem_t full;       /* counts filled slots */
} BoundedQueue;

void bq_init(BoundedQueue *q)
{
    q->head = 0;
    q->tail = 0;
    sem_init(&q->mutex, 0, 1);           /* binary semaphore */
    sem_init(&q->empty, 0, QUEUE_SIZE);  /* all slots empty */
    sem_init(&q->full,  0, 0);           /* no items yet */
}

void bq_put(BoundedQueue *q, int item)
{
    sem_wait(&q->empty);    /* wait for empty slot */
    sem_wait(&q->mutex);    /* lock queue */

    q->data[q->tail] = item;
    q->tail = (q->tail + 1) % QUEUE_SIZE;

    sem_post(&q->mutex);    /* unlock queue */
    sem_post(&q->full);     /* signal: one more item available */
}

int bq_get(BoundedQueue *q)
{
    int item;
    sem_wait(&q->full);     /* wait for available item */
    sem_wait(&q->mutex);    /* lock queue */

    item = q->data[q->head];
    q->head = (q->head + 1) % QUEUE_SIZE;

    sem_post(&q->mutex);    /* unlock queue */
    sem_post(&q->empty);    /* signal: one more slot available */
    return item;
}

void bq_destroy(BoundedQueue *q)
{
    sem_destroy(&q->mutex);
    sem_destroy(&q->empty);
    sem_destroy(&q->full);
}

BoundedQueue queue;

void *producer(void *arg)
{
    int i;
    for (i = 1; i <= NUM_ITEMS; i++) {
        printf("[Producer] Putting item %d\n", i);
        bq_put(&queue, i);
        usleep(100000); /* 100ms */
    }
    return NULL;
}

void *consumer(void *arg)
{
    int i, item;
    for (i = 0; i < NUM_ITEMS; i++) {
        item = bq_get(&queue);
        printf("[Consumer] Got item %d\n", item);
        usleep(300000); /* 300ms โ€” slower than producer */
    }
    return NULL;
}

int main(void)
{
    pthread_t prod, cons;

    bq_init(&queue);

    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

    bq_destroy(&queue);
    printf("All done. Queue is empty.\n");
    return EXIT_SUCCESS;
}

Program 3: sem_timedwait() โ€“ Watchdog with Timeout

A watchdog thread waits on a semaphore for a “heartbeat” signal. If no heartbeat arrives within the timeout period, it logs a warning. This pattern is common in embedded systems and server applications.

/* watchdog.c โ€” gcc watchdog.c -o watchdog -lpthread -D_XOPEN_SOURCE=600 */
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <unistd.h>

#define HEARTBEAT_TIMEOUT_SEC  2
#define RUN_DURATION_SEC      10

sem_t heartbeat_sem;
volatile int running = 1;

/* Watchdog: waits for heartbeat, warns if none within timeout */
void *watchdog(void *arg)
{
    struct timespec deadline;
    int missed = 0;

    while (running) {
        clock_gettime(CLOCK_REALTIME, &deadline);
        deadline.tv_sec += HEARTBEAT_TIMEOUT_SEC;

        if (sem_timedwait(&heartbeat_sem, &deadline) == 0) {
            printf("[Watchdog] Heartbeat received. System OK.\n");
            missed = 0;
        } else if (errno == ETIMEDOUT) {
            missed++;
            printf("[Watchdog] WARNING: No heartbeat for %d second(s)! "
                   "Missed=%d\n", HEARTBEAT_TIMEOUT_SEC, missed);
            if (missed >= 3) {
                printf("[Watchdog] CRITICAL: 3 consecutive missed heartbeats!\n");
                missed = 0; /* reset and keep watching */
            }
        } else {
            perror("[Watchdog] sem_timedwait");
            break;
        }
    }
    printf("[Watchdog] Exiting.\n");
    return NULL;
}

/* Heartbeat sender: simulates sending heartbeats (with a gap) */
void *heartbeat_sender(void *arg)
{
    int i;
    for (i = 0; i < RUN_DURATION_SEC; i++) {
        sleep(1);

        /* Simulate a failure window: no heartbeat from seconds 4-7 */
        if (i >= 3 && i < 7) {
            printf("[Sender] Simulating failure โ€” no heartbeat (second %d)\n",
                   i + 1);
        } else {
            printf("[Sender] Sending heartbeat (second %d)\n", i + 1);
            sem_post(&heartbeat_sem);
        }
    }
    running = 0;
    sem_post(&heartbeat_sem); /* Wake watchdog to exit */
    return NULL;
}

int main(void)
{
    pthread_t wd, hs;

    sem_init(&heartbeat_sem, 0, 0);

    pthread_create(&wd, NULL, watchdog,         NULL);
    pthread_create(&hs, NULL, heartbeat_sender, NULL);
    pthread_join(hs, NULL);
    pthread_join(wd, NULL);

    sem_destroy(&heartbeat_sem);
    printf("Watchdog demo complete.\n");
    return EXIT_SUCCESS;
}

๐Ÿ“š Chapter 53 โ€“ Complete Tutorial Index

All files in this tutorial series, covering Chapter 53 of TLPI (POSIX Semaphores):

Part 1: Closing and Removing Named Semaphores
sem_close(), sem_unlink(), reference counting, auto-close on exit/exec
Part 2: Semaphore Operations Overview
POSIX vs System V, binary vs counting semaphore, the semaphore value
Part 3: Waiting on a Semaphore
sem_wait(), sem_trywait(), sem_timedwait(), EINTR/EAGAIN/ETIMEDOUT handling
Part 4: Posting a Semaphore
sem_post(), async-signal-safety, wakeup policy, signal handler use
Part 5 (this file): Complete Programs
Named IPC sync, bounded queue, watchdog with timedwait

โšก Quick Reference โ€“ All POSIX Semaphore Functions
Function Purpose Returns Key errno
sem_open() Create/open a named semaphore sem_t* or SEM_FAILED EEXIST, ENOENT
sem_close() Close process’s handle to named semaphore 0 or -1 EINVAL
sem_unlink() Remove named semaphore from filesystem 0 or -1 ENOENT
sem_init() Initialise unnamed semaphore 0 or -1 EINVAL, ENOSYS
sem_destroy() Destroy unnamed semaphore 0 or -1 EINVAL
sem_wait() Decrement; block if 0 0 or -1 EINTR
sem_trywait() Decrement; fail immediately if 0 0 or -1 EAGAIN
sem_timedwait() Decrement; block up to absolute deadline 0 or -1 ETIMEDOUT, EINTR
sem_post() Increment; wake one waiter if any 0 or -1 EOVERFLOW
sem_getvalue() Read current value of semaphore 0 or -1 EINVAL
Compile note: All programs using POSIX semaphores should be linked with -lpthread on most Linux systems:
gcc myprogram.c -o myprogram -lpthread

On older systems you may need -lrt as well. For sem_timedwait(), define _XOPEN_SOURCE 600 or _POSIX_C_SOURCE 200112L at the top of your source file.

๐ŸŽฏ Chapter-Level Interview Questions
Q1: What is the difference between a named and an unnamed POSIX semaphore?

A: A named semaphore has a filesystem name (like /my_sem), is created with sem_open(), persists beyond the process lifetime, and can be shared between unrelated processes that know the name. An unnamed semaphore is a sem_t variable initialised with sem_init(), lives in memory (stack, heap, or shared memory), and is typically used to synchronise threads within a process or related processes sharing memory.

Q2: How do you share an unnamed POSIX semaphore between two processes?

A: Place the sem_t variable in a shared memory region (created with shm_open() + mmap(), or shmget()) and call sem_init() with pshared=1. This allows processes that share the memory to use the same semaphore. If pshared=0, the semaphore can only be used between threads of the same process.

Q3: A process calls sem_post() N times before any other process calls sem_wait(). What happens?

A: The semaphore value is incremented N times. When another process later calls sem_wait(), it will succeed immediately without blocking (decrementing the accumulated count). POSIX semaphores “remember” posts โ€” they are not edge-triggered like condition variable signals. This makes them reliable for producer-ahead-of-consumer scenarios.

Q4: What is SEM_VALUE_MAX and why does it matter?

A: SEM_VALUE_MAX is the maximum value a semaphore can hold. On Linux it is typically 2,147,483,647 (INT_MAX). If sem_post() would cause the value to exceed this maximum, it fails with errno = EOVERFLOW. In practice this limit is almost never reached, but it is important to know about when building systems that loop calling sem_post() without matching sem_wait() calls.

Q5: Describe a scenario where sem_timedwait() is better than sem_wait().

A: In a server that processes client requests: if acquiring a resource semaphore takes too long (deadlock, slow holder), sem_wait() would block forever. Using sem_timedwait() with a reasonable deadline (e.g., 500ms) lets the server detect the problem, return an error to the client, log the issue, or take corrective action โ€” rather than hanging permanently.

Q6: Why must the EINTR error from sem_wait() be handled separately from other errors?

A: EINTR is not a real error โ€” it means a signal interrupted the blocking call. The semaphore was not decremented and no resource was acquired. The correct response is to simply retry sem_wait(). Other errors (like EINVAL for an invalid semaphore pointer) indicate real problems that cannot be retried. Conflating the two leads to either: infinite retry loops on real errors, or premature abort on signal interruptions.

Chapter 53 Complete!
You have covered all key topics from TLPI Chapter 53 โ€“ POSIX Semaphores

โ† Start from Part 1 โ† Back: sem_post()

Leave a Reply

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