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 Mechanism | Communication Type | Speed | Typical Use Case |
|---|---|---|---|
| Pipe | Parent ↔ Child | Fast | Simple communication |
| Named Pipe (FIFO) | Unrelated processes | Fast | Local communication |
| Message Queue | Message-based | Moderate | Structured messages |
| Shared Memory | Shared data | Very Fast | Large data exchange |
| Semaphore | Synchronization | Very Fast | Resource protection |
| Socket | Local or Network | Moderate | Client-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:
| Signal | Description |
|---|---|
| `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
| Wrong | Correct |
|---|---|
| Ignoring return values of IPC functions | Always check for errors |
| Using signal() for complex applications | Prefer sigaction() |
| Forgetting to detach shared memory | Call shmdt() |
| Forgetting to remove IPC resources | Use shmctl(), msgctl(), or semctl() when appropriate |
| Performing non-safe operations inside signal handlers | Only 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.