Programming lesson
Mastering POSIX Signals in C: A Lab-Based Guide to Inter-Process Communication
Learn how to use POSIX signals for inter-process communication in C. This tutorial covers signal handlers, sending signals, sigaction, and handling SIGINT, SIGSEGV, SIGALRM with practical lab exercises.
Introduction to Signals in C
Signals are a form of inter-process communication (IPC) used in Unix-like operating systems. They notify a process that a particular event has occurred. Think of signals like notifications on your phone: you get a buzz, but the message content is separate. In programming, signals are lightweight—they carry only a signal number, not data. This tutorial aligns with the CPE2600 Lab 10 assignment on signals, helping you understand signal dispositions, handlers, and how to send and receive signals programmatically and from the command line.
What Is a Signal Disposition?
A signal disposition defines the action taken by a process when it receives a signal. There are five default dispositions:
- Terminate – The process is terminated.
- Ignore – The signal is discarded.
- Core dump – The process terminates and produces a core dump file.
- Stop – The process is stopped (suspended).
- Continue – The process resumes if stopped.
A signal handler is a user-defined function that overrides the default disposition. It is invoked when the signal is delivered, and after it returns, execution continues from where it was interrupted.
Sending Signals Programmatically and from the Command Line
To send a signal programmatically, use the kill() system call. For example, to send SIGUSR1 to a process with PID 1234:
kill(1234, SIGUSR1);From the command line, use the kill command:
kill -SIGUSR1 1234Some signals can be sent via key combinations, like Ctrl+C for SIGINT.
POSIX Signal Types and Their Defaults
Here are five important signals:
- SIGINT – Interrupt from keyboard (Ctrl+C). Default: Terminate. Disposition can be overridden.
- SIGTERM – Termination request. Default: Terminate. Overridable.
- SIGUSR1 – User-defined signal. Default: Terminate. Overridable.
- SIGKILL – Kill signal. Default: Terminate. Cannot be caught or ignored—ensures a process can always be killed.
- SIGSTOP – Stop process. Default: Stop. Cannot be caught or ignored—ensures a process can always be stopped.
The inability to override SIGKILL and SIGSTOP is a safety feature: it prevents a misbehaving process from becoming unkillable.
Working with a Signal Handler (Part 2 Example)
In the lab, you compile signal_handler.c which registers a handler for SIGINT. To send SIGINT, you can press Ctrl+C in the terminal where the process runs, or use kill -INT <PID>. The handler prints a message and then the program exits (if the handler calls exit()). If you modify the handler to not exit, the process continues. To terminate it, you must use SIGKILL (kill -KILL <PID>) because SIGKILL cannot be caught.
Signals from the Operating System: SIGALRM and SIGSEGV
SIGALRM
The alarm() system call schedules a SIGALRM signal after a specified number of seconds. Write a program that sets an alarm for 5 seconds and handles SIGALRM:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig) {
printf("Alarm received!\n");
}
int main() {
signal(SIGALRM, handler);
alarm(5);
pause(); // wait for signal
return 0;
}SIGSEGV
Dereferencing a NULL pointer causes a segmentation fault, which is actually SIGSEGV sent by the OS. If you install a handler for SIGSEGV, the handler runs, but after returning, execution retries the faulting instruction—causing an infinite loop. This demonstrates why you should not attempt to “handle” SIGSEGV and continue; instead, you should exit or use sigaction to get more info.
Getting Details with sigaction
The signal() system call is simple but limited. The sigaction() call provides richer information via a siginfo_t structure, including the sender's PID and the faulting address for SIGSEGV. Example:
#include <stdio.h>
#include <signal.h>
#include <string.h>
void handler(int sig, siginfo_t *info, void *context) {
printf("Signal %d from PID %d\n", sig, info->si_pid);
}
int main() {
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &act, NULL);
// ...
}This is especially useful for debugging or when you need to know which process sent the signal.
Real-World Trend: Signals in Modern Apps
Just as notifications in apps like TikTok or Instagram let you know about likes or follows, signals in C are lightweight notifications. In gaming, signals can be used to pause a game (SIGSTOP) or request graceful shutdown (SIGTERM). In AI applications, signals can interrupt long-running computations. Understanding signals is foundational for building robust, responsive systems.
Conclusion
Signals are a powerful IPC mechanism. You've learned about dispositions, handlers, sending signals, and using sigaction for detailed info. Practice by completing the lab exercises: modify the handler to not exit, observe infinite loops with SIGSEGV, and use sigaction to print sender PID. Commit your code to GitHub Classroom as required.