Why Semaphores Matter in Inter Process Communication
Without synchronization, process synchronization in C becomes a nightmare. Here’s a classic scenario most developers encounter:
- Process A reads a shared counter (value = 5)
- Process B also reads the same counter (value = 5)
- Both increment it independently
- Both write back 6, but the correct answer should be 7
This is a race condition, and it silently corrupts data. No crash, no error — just wrong output. Semaphores solve this by enforcing orderly access to critical sections.
A practical example most engineers relate to:
Imagine 3 users sending print jobs to a single printer simultaneously. Without locking, all three outputs overlap into garbage. With a semaphore, the printer is locked by one process at a time. The others wait, then take turns. Clean output every time.
Semaphores aren’t just for printers, though. They’re used across:
- Shared memory access between processes
- File descriptor management in servers
- Hardware peripheral access in embedded firmware
- Database write locks in multi-process applications

What Is a Semaphore in C?
A semaphore is an integer variable maintained by the OS kernel, used for signaling between processes. Two atomic operations – wait (P) and signal (V) – form the foundation of all semaphore logic:
- P operation (wait/down): Decrements the semaphore. If the value drops below zero, the process blocks.
- V operation (signal/up): Increments the semaphore. If processes are waiting, one is unblocked.
The key guarantee: these operations are atomic. The OS ensures no two processes can execute P or V on the same semaphore at the same instant – which is exactly what makes them reliable for synchronization.
Types of Semaphores
1. Binary Semaphore
- Values: 0 or 1 only (locked / unlocked)
- Functions identically to a mutex (mutual exclusion lock)
- Only one process is allowed in the critical section at any time
- Best for protecting a single shared resource
Use when: One database connection, one log file write operation, one hardware register.
2. Counting Semaphore
- Value can be any non-negative integer
- Represents the count of available identical resources
- Multiple processes can enter the critical section simultaneously, up to the semaphore’s value
Use when: Managing a pool of connections, thread pool slots, or multiple identical hardware peripherals.
Counting Semaphore – Step-by-Step Example:
Assume 5 printers are available (semaphore initialized to 5):
Event | Semaphore Value | Status |
Initial state | 5 | All printers free |
3 jobs arrive | 2 | 3 printers in use |
4 more jobs arrive | 0 | 2 assigned, 2 waiting |
1 printer freed | 0→1→0 | 1 waiting job proceeds |
2 printers freed | 0→2→0 | 2 remaining jobs proceed |
This table shows exactly how counting semaphores manage resource pools dynamically.
System V Semaphores in C: The Three Key System Calls
System V semaphores are the standard IPC semaphore implementation in UNIX/Linux. Three system calls power everything: semget(), semop(), and semctl().
semget() – Create or Access a Semaphore Set
c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
Arguments:
- key – Uniquely identifies the semaphore set. Generated via ftok() or set as an arbitrary value.
- nsems – Number of semaphores in the set. Use 1 for binary; set higher for counting semaphores.
- semflg – Flags that control creation and permissions:
- IPC_CREAT – Create if it doesn’t exist
- IPC_EXCL – Fail if it already exists (used with IPC_CREAT)
- Permission bits (e.g., 0666)
Returns: Valid semaphore ID on success, -1 on failure.
Common errors to watch for:
Error Code | Meaning |
EACCES | Permission denied |
EEXIST | Semaphore already exists |
ENOENT | Semaphore set not found |
ENOMEM | Insufficient memory |
ENOSPC | System semaphore limit reached |
semop() – Perform Wait (P) and Signal (V) Operations
c
int semop(int semid, struct sembuf *semops, size_t nsemops);
This is the heart of semaphore synchronization. The sembuf structure defines each operation:
c
struct sembuf {
unsigned short sem_num; /* Semaphore number in the set */
short sem_op; /* Operation value */
short sem_flg; /* IPC_NOWAIT, SEM_UNDO */
};
What sem_op controls:
- Negative value (e.g., -1) – Acquire resource (wait/P). Blocks if not available.
- Zero – Wait until semaphore value reaches 0.
- Positive value (e.g., +1) – Release resource (signal/V).
Practical lock/unlock pattern used in real code:
c
struct sembuf sem_lock = { 0, -1, SEM_UNDO }; /* Wait / acquire */
struct sembuf sem_unlock = { 0, 1, SEM_UNDO }; /* Signal / release */
The SEM_UNDO flag tells the kernel to automatically undo the semaphore operation if the process terminates unexpectedly — critical for preventing deadlocks in production systems.
semctl() – Control and Configure Semaphores
c
int semctl(int semid, int semnum, int cmd, ...);
Used for initialization, querying state, and cleanup.
Key cmd values:
Command | Purpose |
IPC_STAT | Read current semaphore state into semid_ds |
IPC_SET | Set owner, group, and permission fields |
IPC_RMID | Remove the semaphore set from the system |
IPC_INFO | Return system-wide semaphore limits |
SETVAL | Initialize a semaphore to a specific value |
GETVAL | Read the current value of a semaphore |
Always call semctl(semid, 0, IPC_RMID) when your program exits. Uncleaned semaphores persist in the kernel and consume system resources until reboot.

Step-by-Step: IPC Semaphore Workflow in C
Here’s the standard 3-step flow for using System V semaphores:
Step 1 – Create or connect to a semaphore set:
c
key_t key = ftok("progfile", 65);
int semid = semget(key, 1, IPC_CREAT | 0666);
Step 2 – Initialize the semaphore value:
c
semctl(semid, 0, SETVAL, 1); /* Binary semaphore, starts unlocked */
Step 3 – Lock before critical section, unlock after:
c
struct sembuf sb = {0, -1, SEM_UNDO};
semop(semid, &sb, 1); /* Lock: P operation */
/* --- Critical section --- */
counter++; /* Shared resource access */
sb.sem_op = 1;
semop(semid, &sb, 1); /* Unlock: V operation */
Step 4 – Cleanup:
c
semctl(semid, 0, IPC_RMID);
Practical Project: Parent-Child Counter with Semaphore Synchronization
The Problem:
Two processes – parent and child – both increment a shared memory counter in parallel. Without synchronization, the final counter value is wrong because both processes read-modify-write at the same time, overwriting each other’s updates.
The Setup:
- Shared memory stores the counter and read/write status flags
- Both parent and child increment the counter N times (passed via command-line or defaulted)
- Expected final value: 2 × N
- Without semaphore: Final value is unpredictably less than 2 × N
The Fix:
A single binary semaphore (semaphore value = 1) ensures that only one process enters the increment section at a time:
- Process acquires semaphore (P operation – value drops to 0)
- Reads counter from shared memory
- Increments it
- Writes back to shared memory
- Releases semaphore (V operation – value returns to 1)
- Other process now proceeds
Result: Final counter always equals exactly 2 × N, regardless of scheduling order.
This pattern — shared memory + semaphore — is one of the most common IPC combinations in real UNIX systems programming.
Common Mistakes Developers Make with Semaphores
Even experienced developers hit these pitfalls:
- Forgetting to initialize with semctl(SETVAL) – An uninitialized semaphore has an undefined value, causing random blocking or non-blocking behavior.
- Not cleaning up with IPC_RMID – Semaphore sets persist after process exit. Run ipcs -s on Linux to see lingering IPC resources.
- Skipping SEM_UNDO – If a process holding a semaphore crashes, no other process can acquire it. SEM_UNDO lets the kernel clean up automatically.
- Using the wrong nsems in semget() – Requesting fewer semaphores than your code uses causes runtime failures that are hard to trace.
- Deadlock from nested P operations – Never call P on the same semaphore twice from the same process without an intervening V.
Semaphores vs Mutexes vs Spinlocks – When to Use What
Mechanism | Scope | Best For | Overhead |
System V Semaphore | Inter-process | Shared resources across processes | Medium |
POSIX Semaphore | Thread or process | Portable, modern IPC | Low–Medium |
Mutex (pthread) | Intra-process (threads) | Thread synchronization | Low |
Spinlock | Intra-process | Ultra-short critical sections | Very Low |
For inter process communication in C (multiple separate processes), System V or POSIX semaphores are the right tools. For threads within the same process, pthread_mutex is usually simpler and more efficient.
Semaphores in Embedded Systems
In embedded systems, semaphores appear in a different form but serve the same purpose. RTOS platforms like FreeRTOS, Zephyr, and ThreadX all provide semaphore APIs for task synchronization. The concept is identical to System V semaphores – only the API differs.
Trends shaping semaphore use in 2026 and beyond:
- Multi-core microcontrollers (RP2040, STM32H7, i.MX RT) now require hardware semaphores for inter-core synchronization alongside software ones.
- POSIX-compliant RTOS environments are growing, making POSIX semaphores (sem_open, sem_wait, sem_post) increasingly portable across Linux and embedded targets.
- Security-focused IPC – Modern embedded OS designs treat semaphore access as a security boundary, not just a performance tool.
- Rust and memory-safe systems languages are entering the embedded IPC space, but C remains dominant in production firmware for cost and ecosystem reasons.
Understanding System V semaphores gives you the conceptual foundation for all of these platforms.
Learn Process Synchronization with Hands-On Training at IIES
If you want to go beyond theory and build real confidence with process synchronization in C, system calls, and embedded OS concepts, structured training makes a significant difference.
IIES — Indian Institute of Embedded Systems (Bangalore) offers industry-focused courses in:
- Embedded C and Systems Programming — covering IPC, semaphores, memory management, and RTOS internals
- Embedded AI — for developers building intelligent edge devices and AI-powered embedded systems
- IoT and RTOS Development — practical projects using FreeRTOS and Zephyr
IIES courses are designed for working engineers and fresh graduates alike, with live project exposure and placement support. If you’re targeting roles in embedded software, systems programming, or firmware development, IIES gives you the practical edge that textbooks can’t.

Conclusion
Understanding IPC using semaphore in C is essential for building reliable and efficient multitasking applications. Semaphores help prevent race conditions, coordinate access to shared resources, and ensure proper process synchronization.
Whether using a binary semaphore for mutual exclusion or a counting semaphore for managing multiple resources, mastering semaphore operations such as semget(), semop(), and semctl() is a fundamental skill for systems programmers and embedded developers.
By following best practices and using System V semaphores correctly, developers can create robust applications capable of handling concurrent processing safely and efficiently.