🚀 Introduction
Every Linux process is constantly surrounded by unseen events — keyboard interrupts, segmentation faults, timers, or child process exits.
But how does a process know when one of these events occurs?
That’s where Linux Signals come in.
Signals act like software interrupts — a lightweight communication channel between the operating system and running processes, allowing asynchronous event handling.
This article is the first in our Linux Signals Deep Dive Series, where we’ll start from user space and, in upcoming posts, travel into the kernel to understand how signals are delivered internally.
⚙️ What Are Signals in Linux?
A signal is an asynchronous notification sent to a process (or thread) to inform it of an event.
It could be generated by the kernel, another process, or even by the process itself using system calls like kill() or raise().
For example:
- Pressing Ctrl+C sends a
SIGINT. - Trying to access invalid memory triggers a
SIGSEGV. - A timer expiry results in a
SIGALRM.
🧩 Types of Signals
Linux defines two major signal categories:
🔹 Standard (Conventional) Signals
These are the original UNIX signals (1–31) defined in <signal.h>.
| Signal | Description | Default Action |
|---|---|---|
SIGINT | Sent when pressing Ctrl+C | Terminate process |
SIGTERM | Generic termination request | Terminate process |
SIGSEGV | Invalid memory access | Core dump |
SIGSTOP | Pause execution | Stop process |
SIGCONT | Resume a stopped process | Continue |
SIGHUP | Terminal hangup | Terminate process |
🔹 Real-Time Signals
Modern Linux systems extend the signal range from SIGRTMIN to SIGRTMAX.
They provide:
✅ Queued delivery (no loss of simultaneous signals)
✅ Priorities (higher signal numbers = higher priority)
✅ The ability to attach custom data using sigqueue()
Real-time signals are commonly used in multi-threaded or real-time embedded applications.
🧠 How User Space Handles Signals
When a signal is sent, the kernel marks the process as pending for that signal.
Upon scheduling, the process’s control flow is altered temporarily to invoke the corresponding signal handler.
You can define how your program reacts to signals by:
- Using the default action
- Ignoring the signal
- Defining a custom signal handler
Let’s use the modern, robust, and POSIX-compliant sigaction() API to demonstrate this.
🧪 Hands-On: Signal Handling Using sigaction()
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
void sigint_handler(int sig) {
printf("\nCaught signal %d (%s). Cleaning up safely...\n", sig, strsignal(sig));
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
return 1;
}
printf("Process running with PID %d. Press Ctrl+C to trigger SIGINT.\n", getpid());
while (1) {
pause(); // wait for signals
}
return 0;
}
🔍 Explanation
sigaction()replaces the oldersignal()API — it’s portable, reliable, and avoids race conditions.sa_maskdefines which signals to block while the handler runs.pause()suspends execution until a signal is received.- When you press
Ctrl+C,SIGINTis caught and handled gracefully.
🧰 Signal Masking: Blocking and Unblocking
There are cases where you don’t want a signal to interrupt critical code — for instance, during a file write or shared-memory update.
This is where signal masking helps.
A process can temporarily block signals using sigprocmask() until it’s ready to handle them.
Example: Temporarily Blocking SIGINT
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
printf("Blocking SIGINT for 5 seconds...\n");
sigprocmask(SIG_BLOCK, &mask, NULL);
sleep(5);
printf("Unblocking SIGINT now. Press Ctrl+C again!\n");
sigprocmask(SIG_UNBLOCK, &mask, NULL);
while (1)
pause();
}
💡 Note:
While blocked, the signal remains pending — and will be delivered once unblocked.
🧩 The sa_mask Field in sigaction()
sigaction() provides fine-grained control with the sa_mask field.
It specifies signals to be automatically blocked while a particular handler executes — preventing nested signal interruptions.
For instance, if SIGUSR1 triggers your handler, you can use sa_mask to block SIGUSR2 until the first handler completes.
⚡ Common Use Cases
✅ Graceful shutdowns: Handling SIGTERM to close files and release resources
✅ Timers: Using SIGALRM for periodic operations
✅ Child process management: Handling SIGCHLD after fork()
✅ Debugging: Reacting to SIGTRAP or SIGSTOP
✅ Real-time messaging: Using queued real-time signals for inter-thread communication
🧭 Recap
| Concept | Description |
|---|---|
| Signal | Notification sent to a process for an event |
| Handler | Function executed when a signal occurs |
| Mask | Blocks signals temporarily |
| sigaction() | POSIX-compliant signal handling API |
| Real-Time Signals | Queued and prioritized signals |
Signals enable asynchronous control — letting processes react instantly to external events without constant polling.
🌐 Coming Up Next
In the next part of this series, we’ll venture beneath user space — exploring how the Linux kernel actually delivers signals to processes.
We’ll decode internal functions like send_sig_info(), do_send_sig_info(), and get_signal() to reveal what really happens during signal delivery.
Stay tuned for Part 2: “Journey of a Signal — Inside the Linux Kernel” ⚙️
🚀 Want to Fast-Track Your Linux Learning Journey?
If you’re eager to build a solid foundation before diving into Linux Driver Development, start with my flagship program:
👉 Linux Rapid Mastery
🧩 Course Highlights
💡 4 Core Modules
- Linux Fundamentals
- Linux Application Development
- Linux Driver Fundamentals
- Linux Kernel Internals
🎯 Who It’s For: Beginners aiming to become Linux Driver Developers.
💬 Extras: Weekly live meetups, assignment reviews, and career guidance to help you move fast and stay confident.
Join thousands of engineers accelerating their Linux journey:
🌐 Enroll in Linux Rapid Mastery
Glad I observed this on google .
Great to Know this.