SrcForge

Lesson 42

IPC and Signals

IPC (Inter-Process Communication) is a collection of mechanisms that allow two or more processes to exchange data and synchronize their actions. Common IPC mechanisms include pipes, FIFOs, message queues, shared memory, semaphores, and sockets. Signals are software interrupts that notify a process about asynchronous events. Together, IPC and signals are fundamental to operating systems, daemons, web servers, and any multi-process application in C.

C Track

Save progress as you learn.

44 lessons

Lesson content

IPC and Signals in C Programming: A Complete Beginner to Advanced Guide

What are IPC and Signals in C Programming?

Modern applications rarely run as a single process. Web servers, operating systems, databases, and many desktop applications use multiple processes that need to communicate and coordinate with each other. This communication is known as Inter-Process Communication (IPC).

IPC (Inter-Process Communication) is a collection of mechanisms that allow two or more processes to exchange data and synchronize their actions. Since each process has its own memory space, special techniques are required for communication.

A signal is a software interrupt sent to a process to notify it that an event has occurred, such as pressing Ctrl+C, a timer expiring, or another process requesting termination. Think of IPC as a telephone conversation between people where information is exchanged, while signals are like a doorbell notifying someone that immediate attention is needed.

Why Do IPC and Signals Exist?

Without IPC, processes cannot share information, applications become isolated, and resource sharing becomes difficult. Without signals, processes cannot respond to asynchronous events, graceful program termination becomes harder, and process control becomes limited.

Real-World Use Cases

IPC and Signals are widely used in:

  • Operating systems
  • Linux daemons
  • Client-server applications
  • Database management systems
  • Web servers
  • Embedded systems
  • Shells and terminal applications
  • Multi-process applications

Prerequisites

Before learning IPC and Signals in C programming, you should understand:

  • Basic C programming
  • Functions
  • Pointers
  • Structures
  • File handling
  • Processes (fork())
  • Basic Linux commands
  • GCC compilation

Core Concepts

What is IPC?

Inter-Process Communication (IPC) allows multiple processes to communicate and synchronize their work. Common IPC mechanisms include pipes, named pipes (FIFOs), message queues, shared memory, semaphores, and sockets.

Types of IPC

IPC MechanismCommunication TypeSpeedTypical Use Case
PipeParent ↔ ChildFastSimple communication
Named Pipe (FIFO)Unrelated processesFastLocal communication
Message QueueMessage-basedModerateStructured messages
Shared MemoryShared dataVery FastLarge data exchange
SemaphoreSynchronizationVery FastResource protection
SocketLocal or NetworkModerateClient-server applications

Pipes

A pipe creates a unidirectional communication channel between related processes, used mainly between parent and child processes. Data is transferred as a byte stream.

pipe()
read()
write()
close()

Named Pipes (FIFOs)

Unlike ordinary pipes, FIFOs allow unrelated processes to communicate.

mkfifo()
open()
read()
write()
close()

Message Queues

A message queue allows processes to exchange structured messages with priorities and without shared memory.

msgget()
msgsnd()
msgrcv()
msgctl()

Shared Memory

Shared memory is one of the fastest IPC mechanisms because multiple processes access the same memory region directly. It is ideal for large datasets but requires synchronization, usually with semaphores.

shmget()
shmat()
shmdt()
shmctl()

Semaphores

A semaphore controls access to shared resources and prevents race conditions using Wait (P) and Signal (V) operations.

semget()
semop()
semctl()

Signals

A signal is a software interrupt delivered to a process. Common signals include:

SignalDescription
`SIGINT`Interrupt (Ctrl+C)
`SIGTERM`Termination request
`SIGKILL`Forcefully terminate process
`SIGSEGV`Segmentation fault
`SIGALRM`Alarm timer expired
`SIGCHLD`Child process terminated
`SIGUSR1`User-defined signal 1
`SIGUSR2`User-defined signal 2

Signal Handling

Signals can be handled using signal() or preferably sigaction(). sigaction() is the modern and more reliable API because it provides greater control over signal handling.

signal()
sigaction()

Code Examples

Example 1: Beginner – Handling SIGINT

#include <stdio.h>                 // Standard input/output library
#include <signal.h>                // Signal handling library

void handler(int sig)              // Signal handler function
{
    printf("SIGINT received!\n");  // Display message
}

int main()                         // Program entry point
{
    signal(SIGINT, handler);       // Register SIGINT handler

    while (1)                      // Infinite loop
    {
    }                              // Wait for Ctrl+C

    return 0;                      // Exit program
}

Example 2: Intermediate – Parent-Child Communication Using Pipes

#include <stdio.h>                 // Standard I/O library
#include <unistd.h>                // POSIX functions
#include <string.h>                // String functions

int main()                         // Program entry point
{
    int fd[2];                     // Pipe file descriptors

    pipe(fd);                      // Create pipe

    if (fork() == 0)               // Child process
    {
        close(fd[1]);              // Close write end

        char buffer[100];          // Buffer for data

        read(fd[0], buffer, sizeof(buffer)); // Read from pipe

        printf("%s\n", buffer);    // Print message
    }
    else                           // Parent process
    {
        close(fd[0]);              // Close read end

        write(fd[1], "Hello Child", 11); // Send message
    }

    return 0;                      // Exit program
}

Example 3: Intermediate – Sending a Signal

#include <stdio.h>                 // Standard I/O library
#include <signal.h>                // Signal library
#include <unistd.h>                // POSIX functions

int main()                         // Program entry point
{
    printf("%d\n", getpid());      // Print process ID

    pause();                       // Wait for a signal

    return 0;                      // Exit program
}
kill -SIGTERM <PID>

Example 4: Advanced – Shared Memory

#include <stdio.h>                 // Standard I/O library
#include <sys/shm.h>               // Shared memory

int main()                         // Program entry point
{
    int id = shmget(1234, 1024, IPC_CREAT | 0666); // Create shared memory

    char *memory = (char *)shmat(id, NULL, 0);     // Attach memory

    sprintf(memory, "Shared Data");                // Write data

    printf("%s\n", memory);                        // Display data

    shmdt(memory);                                 // Detach memory

    return 0;                                      // Exit program
}

Example 5: Advanced – Using sigaction()

#include <stdio.h>                 // Standard I/O library
#include <signal.h>                // Signal library

void handler(int sig)              // Signal handler
{
    printf("Signal received\n");   // Display message
}

int main()                         // Program entry point
{
    struct sigaction sa;           // Signal action structure

    sa.sa_handler = handler;       // Register handler

    sigemptyset(&sa.sa_mask);      // Clear signal mask

    sa.sa_flags = 0;               // No special flags

    sigaction(SIGINT, &sa, NULL);  // Install handler

    while (1)                      // Infinite loop
    {
    }

    return 0;                      // Exit program
}

Common Mistakes and Pitfalls

WrongCorrect
Ignoring return values of IPC functionsAlways check for errors
Using signal() for complex applicationsPrefer sigaction()
Forgetting to detach shared memoryCall shmdt()
Forgetting to remove IPC resourcesUse shmctl(), msgctl(), or semctl() when appropriate
Performing non-safe operations inside signal handlersOnly call async-signal-safe functions or set a flag

Wrong:

signal(SIGINT, handler);

Correct:

struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);

Best Practices

  • Prefer sigaction() over signal().
  • Check every IPC system call for errors.
  • Release IPC resources after use.
  • Use shared memory for high-speed communication.
  • Protect shared memory with semaphores.
  • Keep signal handlers short and simple.
  • Avoid dynamic memory allocation inside signal handlers.
  • Handle interrupted system calls correctly.
  • Document the communication protocol between processes.
  • Use unique IPC keys and appropriate permissions.

When NOT to Use This

IPC and Signals may not be the best choice when:

  • Communication occurs within a single process; threads may be more appropriate.
  • Processes run on different machines; network sockets or higher-level communication frameworks are required.
  • Complex distributed communication is needed; technologies like gRPC or message brokers provide better scalability.
  • Fine-grained synchronization between threads is required; thread synchronization primitives are usually more suitable.

Summary / Key Takeaways

  • IPC enables communication between separate processes.
  • Common IPC mechanisms include pipes, FIFOs, message queues, shared memory, semaphores, and sockets.
  • Shared memory is typically the fastest IPC method but requires synchronization.
  • Signals notify processes about asynchronous events.
  • sigaction() is preferred over signal() for robust signal handling.
  • Always check system call return values and clean up IPC resources.
  • Keep signal handlers minimal and avoid unsafe library functions within them.

FAQ About IPC and Signals in C

1. What is IPC in C programming?

IPC (Inter-Process Communication) is a set of mechanisms that enables different processes to exchange data and synchronize their activities.

2. What is the difference between a pipe and shared memory?

A pipe transfers data as a byte stream between processes, while shared memory allows multiple processes to access the same memory region directly, making it much faster for large amounts of data.

3. What is a signal in C?

A signal is a software interrupt that notifies a process that an event has occurred, such as a termination request or a keyboard interrupt.

4. Why is sigaction() preferred over signal()?

sigaction() provides more reliable and portable signal handling, supports additional configuration options, and avoids several historical limitations of signal().

5. Which IPC mechanism is the fastest?

Shared memory is generally the fastest IPC mechanism because processes communicate through a common memory region instead of copying data through the kernel. However, it must be combined with synchronization mechanisms such as semaphores to prevent race conditions.

Prev: Inline AssemblyBack to hub