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.
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) |
โท | Process B (Receiver)
sem_open(“/sync”, 0) |
| 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;
}
Terminal 1:
./receiverTerminal 2:
./senderA 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;
}
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;
}
All files in this tutorial series, covering Chapter 53 of TLPI (POSIX Semaphores):
sem_close(), sem_unlink(), reference counting, auto-close on exit/exec
POSIX vs System V, binary vs counting semaphore, the semaphore value
sem_wait(), sem_trywait(), sem_timedwait(), EINTR/EAGAIN/ETIMEDOUT handling
| 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 |
-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.
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.
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.
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.
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.
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.
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.
