The raw System V semaphore API is complex. Semaphores are allocated in sets, can be decremented by arbitrary amounts, and require boilerplate for every operation. In practice, most applications only need a simple mutex โ an object that is either free or in use.
A binary semaphore protocol wraps System V semaphores behind a clean two-operation API: reserve (lock) and release (unlock). This file builds that API from scratch.
A binary semaphore has exactly two states and supports exactly two operations:
| Operation | semop call | Behaviour |
|---|---|---|
| reserve() | sem_op = -1 |
If semval = 1: decrease to 0 and return. If semval = 0: block until available. |
| release() | sem_op = +1 |
Increase semval from 0 to 1, waking any blocked caller. |
We define a clean header with types and function prototypes. The implementation hides all System V details from callers.
/* binary_sem.h โ Binary semaphore API using System V semaphores */
#ifndef BINARY_SEM_H
#define BINARY_SEM_H
#include <sys/sem.h>
/* Flags for bsCreate() */
#define BS_CREATE 0x01 /* create new semaphore */
#define BS_EXCL 0x02 /* fail if semaphore already exists */
/* Initial states */
#define BS_AVAILABLE 1 /* semaphore starts unlocked (available) */
#define BS_INUSE 0 /* semaphore starts locked (in-use) */
/*
* bsCreate - create or open a binary semaphore
* key: IPC key (use IPC_PRIVATE for private semaphores)
* initValue: BS_AVAILABLE (1) or BS_INUSE (0)
* flags: BS_CREATE, BS_EXCL
*
* Returns semid on success, -1 on error.
*/
int bsCreate(key_t key, int initValue, int flags);
/*
* bsReserve - acquire (lock) the binary semaphore
* Returns 0 on success, -1 on error.
* Blocks if semaphore is currently in-use.
*/
int bsReserve(int semid);
/*
* bsRelease - release (unlock) the binary semaphore
* Returns 0 on success, -1 on error.
*/
int bsRelease(int semid);
/*
* bsReserveNowait - non-blocking acquire attempt
* Returns 1 if acquired, 0 if busy, -1 on error.
*/
int bsReserveNowait(int semid);
/*
* bsDelete - remove the semaphore set
*/
int bsDelete(int semid);
#endif /* BINARY_SEM_H */
/* binary_sem.c โ Implementation of binary semaphore protocol */
#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
#include "binary_sem.h"
/*
* Required union for semctl() on some systems.
* (POSIX leaves this to the user to define.)
*/
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
/* -------------------------------------------------------
* bsCreate: create or obtain a binary semaphore
* ------------------------------------------------------- */
int bsCreate(key_t key, int initValue, int flags)
{
int semFlags = 0600; /* rw permissions for owner */
if (flags & BS_CREATE) semFlags |= IPC_CREAT;
if (flags & BS_EXCL) semFlags |= IPC_EXCL;
int semid = semget(key, 1, semFlags);
if (semid == -1)
return -1;
/* Only initialize if we just created it */
if (flags & BS_CREATE) {
union semun arg;
arg.val = initValue; /* BS_AVAILABLE=1 or BS_INUSE=0 */
if (semctl(semid, 0, SETVAL, arg) == -1)
return -1;
}
return semid;
}
/* -------------------------------------------------------
* bsReserve: acquire the semaphore (blocking)
* ------------------------------------------------------- */
int bsReserve(int semid)
{
struct sembuf sop;
sop.sem_num = 0;
sop.sem_op = -1; /* subtract 1 (available โ in-use) */
sop.sem_flg = SEM_UNDO; /* auto-release on process death */
return semop(semid, &sop, 1);
}
/* -------------------------------------------------------
* bsRelease: release the semaphore
* ------------------------------------------------------- */
int bsRelease(int semid)
{
struct sembuf sop;
sop.sem_num = 0;
sop.sem_op = +1; /* add 1 (in-use โ available) */
sop.sem_flg = SEM_UNDO;
return semop(semid, &sop, 1);
}
/* -------------------------------------------------------
* bsReserveNowait: non-blocking try-acquire
* Returns: 1 = acquired, 0 = busy, -1 = error
* ------------------------------------------------------- */
int bsReserveNowait(int semid)
{
struct sembuf sop;
sop.sem_num = 0;
sop.sem_op = -1;
sop.sem_flg = SEM_UNDO | IPC_NOWAIT;
if (semop(semid, &sop, 1) == -1) {
if (errno == EAGAIN)
return 0; /* semaphore is busy */
return -1; /* real error */
}
return 1; /* acquired */
}
/* -------------------------------------------------------
* bsDelete: remove the semaphore set
* ------------------------------------------------------- */
int bsDelete(int semid)
{
return semctl(semid, 0, IPC_RMID);
}
This demo uses the binary semaphore API to protect a shared counter. Multiple workers increment it concurrently. Without the lock, race conditions corrupt the counter.
/* demo_binary_sem.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include "binary_sem.h"
#define NUM_WORKERS 4
#define INCREMENTS 10000
int main(void)
{
/* Create shared memory for a counter */
int shmid = shmget(IPC_PRIVATE, sizeof(long), IPC_CREAT | 0600);
long *counter = (long *)shmat(shmid, NULL, 0);
*counter = 0;
/* Create binary semaphore, starts available (1) */
int semid = bsCreate(IPC_PRIVATE, BS_AVAILABLE, BS_CREATE);
if (semid == -1) { perror("bsCreate"); exit(EXIT_FAILURE); }
printf("Spawning %d workers, each doing %d increments\n",
NUM_WORKERS, INCREMENTS);
for (int i = 0; i < NUM_WORKERS; i++) {
pid_t pid = fork();
if (pid == 0) {
for (int j = 0; j < INCREMENTS; j++) {
/* Critical section: lock โ read-modify-write โ unlock */
bsReserve(semid);
(*counter)++; /* protected increment */
bsRelease(semid);
}
shmdt(counter);
exit(0);
}
}
for (int i = 0; i < NUM_WORKERS; i++)
wait(NULL);
printf("Expected counter: %d\n", NUM_WORKERS * INCREMENTS);
printf("Actual counter: %ld\n", *counter);
printf("Result: %s\n",
(*counter == NUM_WORKERS * INCREMENTS) ? "CORRECT" : "RACE CONDITION");
shmdt(counter);
shmctl(shmid, IPC_RMID, NULL);
bsDelete(semid);
return 0;
}
Expected output:
Spawning 4 workers, each doing 10000 increments
Expected counter: 40000
Actual counter: 40000
Result: CORRECT
If you remove the bsReserve/bsRelease calls, the counter will be less than 40000 due to race conditions (lost updates).
Sometimes you want to try to acquire a lock and do something else if it’s busy. Use bsReserveNowait() for this pattern.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "binary_sem.h"
int main(void)
{
int semid = bsCreate(IPC_PRIVATE, BS_AVAILABLE, BS_CREATE);
/* First try: lock is free */
printf("Attempt 1: ");
int result = bsReserveNowait(semid);
if (result == 1) {
printf("Lock acquired!\n");
} else if (result == 0) {
printf("Lock is busy, doing something else...\n");
} else {
perror("bsReserveNowait");
}
/* Now the lock is held (value = 0). Try again: */
printf("Attempt 2 (lock already held): ");
result = bsReserveNowait(semid);
if (result == 1) {
printf("Lock acquired again!\n"); /* unexpected */
} else if (result == 0) {
printf("Lock is busy -- EAGAIN (IPC_NOWAIT)\n");
}
/* Release the lock acquired in attempt 1 */
bsRelease(semid);
printf("Lock released.\n");
/* Third try: should succeed again */
printf("Attempt 3: ");
result = bsReserveNowait(semid);
if (result == 1)
printf("Lock acquired successfully!\n");
bsRelease(semid);
bsDelete(semid);
return 0;
}
Expected output:
Attempt 1: Lock acquired!
Attempt 2 (lock already held): Lock is busy -- EAGAIN (IPC_NOWAIT)
Lock released.
Attempt 3: Lock acquired successfully!
IPC_PRIVATE only works for related processes (parent/child). For two completely unrelated programs to share a semaphore, use ftok() to generate a common key.
/* server.c โ acquires named semaphore, holds it for 5 seconds, releases */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include "binary_sem.h"
#define SEM_KEY_PATH "/tmp" /* path for ftok() */
#define SEM_KEY_ID 42 /* project ID for ftok() */
int main(void)
{
key_t key = ftok(SEM_KEY_PATH, SEM_KEY_ID);
if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }
/* Create semaphore (will fail if already exists โ use IPC_EXCL) */
int semid = bsCreate(key, BS_AVAILABLE, BS_CREATE | BS_EXCL);
if (semid == -1) {
/* Already exists โ open it */
semid = bsCreate(key, BS_AVAILABLE, 0);
if (semid == -1) { perror("bsCreate"); exit(EXIT_FAILURE); }
}
printf("Server (PID=%d): trying to acquire semaphore...\n", getpid());
bsReserve(semid);
printf("Server: acquired! Holding for 5 seconds...\n");
sleep(5);
printf("Server: releasing semaphore\n");
bsRelease(semid);
return 0;
}
/* client.c โ tries to acquire the same named semaphore */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include "binary_sem.h"
#define SEM_KEY_PATH "/tmp"
#define SEM_KEY_ID 42
int main(void)
{
key_t key = ftok(SEM_KEY_PATH, SEM_KEY_ID);
if (key == -1) { perror("ftok"); exit(EXIT_FAILURE); }
/* Open existing semaphore (no BS_CREATE) */
int semid = bsCreate(key, 0, 0);
if (semid == -1) { perror("bsCreate"); exit(EXIT_FAILURE); }
printf("Client (PID=%d): waiting for semaphore...\n", getpid());
bsReserve(semid); /* blocks until server releases */
printf("Client: acquired semaphore!\n");
bsRelease(semid);
return 0;
}
| Feature | SysV Binary Sem | POSIX Mutex | POSIX Semaphore |
|---|---|---|---|
| Scope | Any processes | Threads in one process (or shared mem) | Any processes (named) or threads |
| Auto-unlock on death | Yes (SEM_UNDO) | No (robust mutex possible) | No |
| API complexity | High (wrapped here) | Low | Medium |
| FIFO wake order | Not guaranteed | Priority-based | Priority-based |
| Try-lock | Yes (IPC_NOWAIT) | pthread_mutex_trylock() | sem_trywait() |
| Persistence | Kernel persistent (survives process death) | Process-lifetime (unless in shm) | Named: persistent; unnamed: not |
A single-file program that implements and uses the binary semaphore protocol โ useful for quick testing or interview coding exercises.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/wait.h>
/* ===== Inline binary semaphore implementation ===== */
union semun { int val; struct semid_ds *buf; unsigned short *array; };
static int bs_create(int init_val) {
int semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (semid == -1) return -1;
union semun arg; arg.val = init_val;
if (semctl(semid, 0, SETVAL, arg) == -1) return -1;
return semid;
}
static int bs_lock(int semid) {
struct sembuf sop = {0, -1, SEM_UNDO};
return semop(semid, &sop, 1);
}
static int bs_unlock(int semid) {
struct sembuf sop = {0, +1, SEM_UNDO};
return semop(semid, &sop, 1);
}
static int bs_trylock(int semid) {
struct sembuf sop = {0, -1, SEM_UNDO | IPC_NOWAIT};
if (semop(semid, &sop, 1) == -1) {
return (errno == EAGAIN) ? 0 : -1;
}
return 1;
}
static void bs_destroy(int semid) {
semctl(semid, 0, IPC_RMID);
}
/* ===== Application code ===== */
#define NUM_PROCESSES 3
#define WORK_CYCLES 5
int main(void)
{
int semid = bs_create(1); /* 1 = available */
if (semid == -1) { perror("bs_create"); return 1; }
printf("Binary semaphore created (semid=%d)\n\n", semid);
for (int i = 0; i < NUM_PROCESSES; i++) {
pid_t pid = fork();
if (pid == 0) {
for (int j = 0; j < WORK_CYCLES; j++) {
/* Try non-blocking first */
int got = bs_trylock(semid);
if (got == 1) {
printf("Process %d (PID=%d): trylock succeeded, cycle %d\n",
i, getpid(), j);
usleep(100000); /* 100ms of "work" */
bs_unlock(semid);
} else if (got == 0) {
printf("Process %d (PID=%d): busy, blocking...\n", i, getpid());
bs_lock(semid); /* wait properly */
printf("Process %d (PID=%d): got lock after wait, cycle %d\n",
i, getpid(), j);
usleep(50000);
bs_unlock(semid);
}
}
exit(0);
}
}
for (int i = 0; i < NUM_PROCESSES; i++)
wait(NULL);
bs_destroy(semid);
printf("\nAll processes done.\n");
return 0;
}
Q1: What is a binary semaphore and how does it differ from a general semaphore?
A binary semaphore can only have the value 0 (in-use) or 1 (available). A general (counting) semaphore can have any non-negative integer value. Binary semaphores are used for mutual exclusion (mutex-like locking), while counting semaphores are used for resource counting or producer-consumer synchronization.
Q2: Why would you build a binary semaphore wrapper rather than using System V semaphores directly?
The raw API requires managing semaphore sets, using struct sembuf arrays, handling the semun union, and tracking index numbers. A wrapper hides all this complexity behind simple reserve()/release() calls, making code easier to read, maintain, and less error-prone.
Q3: Why is SEM_UNDO particularly important in a binary semaphore implementation?
If a process dies while holding the binary semaphore (value = 0), no other process can ever acquire it. SEM_UNDO ensures the kernel automatically adds back +1 when the process dies, restoring the semaphore to the available state and letting other processes proceed.
Q4: What is the System V semaphore value used for “available” and “in-use” states?
Available (free): semaphore value = 1.
In-use (locked): semaphore value = 0.
The reserve() operation subtracts 1 (1โ0, blocks if already 0). The release() operation adds 1 (0โ1, waking any waiting process).
Q5: How do you share a binary semaphore between completely unrelated processes?
Use ftok() to generate a common IPC key from a shared filesystem path and a project ID. Both processes call semget() with the same key. The first process creates the semaphore (with IPC_CREAT), and the second opens it by the same key without IPC_CREAT.
Q6: Why is a binary semaphore sometimes preferred over a POSIX mutex for inter-process locking?
System V binary semaphores: (1) survive process death (SEM_UNDO), (2) are kernel-persistent (survive even if no process is running), (3) work for any two unrelated processes without a shared memory segment. POSIX mutexes require shared memory for IPC and do not auto-unlock on non-robust variants when a process dies.
Q7: What happens if you call release() (unlock) on a binary semaphore that is already available (value = 1)?
The semaphore value becomes 2, which breaks the binary invariant. The next two callers of reserve() will both succeed (each getting value 1 and 0 respectively), allowing two processes into the critical section simultaneously โ defeating the purpose of mutual exclusion. Always pair each reserve with exactly one release.
You have covered all topics from TLPI Chapter 47 in this PDF.
Go back to the chapter index or review any part.
