IPC Using Semaphores in C: Types, System Calls - Complete Guide

IPC Using Semaphores in C Types, System Calls - Complete Guide

Modern operating systems run dozens of processes in parallel – and that’s exactly where the trouble begins. When two or more processes reach for the same shared resource at the same time, things break in ways that are notoriously hard to debug. This is why IPC using semaphores in C remains one of the most important topics in systems programming, embedded development, and OS internals.

Whether you’re building a multi-process Linux application, working with RTOS-based embedded systems, or preparing for a systems programming interview, understanding semaphores in C gives you a precise, low-level tool for controlling access, preventing race conditions, and building reliable software. This guide covers everything, from what a semaphore actually is, to System V system calls, practical C code, and best practices you can apply today.

IPC using semaphores in C is a synchronization technique that enables multiple processes to safely share resources without causing race conditions or data corruption. System V semaphores use the semget(), semop(), and semctl() system calls to create, manage, and control process synchronization. Understanding binary and counting semaphores is essential for building reliable Linux, UNIX, embedded, and multitasking applications. This guide explains semaphore concepts, types, system calls, and practical examples for effective inter-process communication.

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

registor_now_P

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.

 

Explore Courses - Learn More

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:

  1. Process acquires semaphore (P operation – value drops to 0)
  2. Reads counter from shared memory
  3. Increments it
  4. Writes back to shared memory
  5. Releases semaphore (V operation – value returns to 1)
  6. 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.

Talk to Academic Advisor

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.

FAQs

A semaphore is a synchronization mechanism used to control access to shared resources among multiple processes or threads.

The two main types are:

  • Binary semaphore
  • Counting semaphore

It is a process synchronization technique that uses semaphores to coordinate access to shared resources between processes.

System V semaphores are kernel-managed semaphore sets used in UNIX and Linux for inter-process synchronization.

The primary system calls are:

  • semget()
  • semop()
  • semctl()

Author

Embedded Systems trainer – IIES

Updated On: 09-06-26


10+ years of hands-on experience delivering practical training in Embedded Systems and it's design