Signal Masks in Linux
Signal masks are one of the most powerful features of sigaction().
They allow developers to block specific signals while executing critical code sections.
This prevents:
- Re-entrancy problems
- Data corruption
- Inconsistent shared state

Blocking Signals Using sa_mask
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT);
This blocks SIGINT while the current handler is executing.
Using sigprocmask() in Linux
sigprocmask() provides explicit control over signal blocking.
sigprocmask() Example
sigset_t mask, oldmask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmask(SIG_BLOCK,
&mask,
&oldmask);
/* Critical section */
sigprocmask(SIG_SETMASK,
&oldmask,
NULL);
This is commonly used in:
What Are Async-Signal-Safe Functions?
Signal handlers can interrupt program execution at any moment.
If the interrupted code was already executing a non-reentrant function, calling the same function inside the signal handler may cause:
- Deadlocks
- Heap corruption
- Crashes
- Undefined behavior
This is why POSIX defines async-signal-safe functions.
Safe Functions Inside Signal Handlers
The following functions are considered async-signal-safe:
- write()
- read()
- close()
- kill()
- sigaction()
- sigprocmask()
- _exit()
- sem_post()
These functions are safe to call inside signal handlers.
Unsafe Functions Inside Signal Handlers
Never call these functions from a signal handler:
- printf()
- fprintf()
- sprintf()
- malloc()
- free()
- exit()
- STL containers
- Most C++ library functions
These functions may internally use locks or shared memory structures.
Why printf() Is Unsafe in Signal Handlers
Many beginners use:
printf("Signal received\n");
inside signal handlers.
This is dangerous.
If the signal interrupts another printf() call, the internal stdio state may become corrupted.
Always use:
write()
instead.
signal() vs sigaction() Comparison
| Feature | signal() | sigaction() |
|---|
| Reliability | Limited | High |
| Portability | Inconsistent | POSIX standard |
| Signal masking | No | Yes |
| Extra signal info | No | Yes |
| Syscall restart | Unreliable | Supported |
| Production suitability | Poor | Excellent |
| Multi-thread support | Weak | Better |
| Advanced control | Minimal | Extensive |
Real-World Uses of Linux Signals
Linux signals are heavily used in production systems.
Examples include:
1. Graceful Shutdown
Servers use SIGTERM for clean shutdown procedures.
2. Reloading Configuration Files
Daemons often use SIGHUP to reload configuration files without restarting.
3. Child Process Monitoring
SIGCHLD notifies the parent when child processes terminate.
4. Crash Detection
SIGSEGV is used by debuggers and crash handlers.
5. Timers and Scheduling
SIGALRM supports timer-based applications.
6. Inter-Process Communication
Processes can exchange custom notifications using:
Signals in Embedded Linux Systems
Signals are also important in embedded Linux development.
Embedded Linux applications use signals for:
- Process supervision
- Hardware event notification
- Watchdog systems
- Real-time control
- Child process management
Understanding signals is essential for embedded systems engineers working with Linux.
Common Mistakes in Linux Signal Handling
Many beginners make critical mistakes while handling signals.
Using printf() Inside Handlers
Unsafe and may deadlock.
Forgetting SA_RESTART
Causes interrupted system calls.
Ignoring EINTR Errors
Can break system-level applications.
Performing Heavy Logic Inside Handlers
Signal handlers should remain minimal.
Not Blocking Signals in Critical Sections
Can lead to race conditions.
Best Practices for Linux Signal Handling
Always Prefer sigaction()
Avoid using signal() in production code.
Keep Handlers Small
Signal handlers should execute quickly.
Use Async-Signal-Safe Functions Only
Prefer write() over printf().
Use Signal Masks Carefully
Protect shared resources using sa_mask and sigprocmask().
Handle EINTR Properly
Always consider interrupted system calls.
Test Signals Thoroughly
Signal-related bugs are often difficult to debug.
Advanced Linux Signal Handling Concepts
To truly understand Linux signal handling, developers must also understand how signals interact with processes, threads, schedulers, system calls, and the Linux kernel.
This section covers deeper Linux internals concepts that are commonly asked in interviews and heavily used in production Linux systems.
Signal Lifecycle in Linux Kernel
The lifecycle of a Linux signal involves several internal kernel stages.
1. Signal Generation
A signal may be generated by:
- The Linux kernel
- Hardware exceptions
- Another process
- Terminal drivers
- Timers
- System calls
Examples:
- Pressing Ctrl + C generates SIGINT
- Invalid memory access generates SIGSEGV
- Division by zero generates SIGFPE
- Calling kill() generates user-defined signals
2. Signal Pending State
Once generated, the signal becomes pending for the target process.
Linux maintains pending signal sets inside the process descriptor.
If the signal is blocked using a signal mask, Linux keeps the signal pending until it becomes unblocked.
3. Signal Delivery
The kernel checks:
- Whether the signal is blocked
- Whether the signal is ignored
- Whether a handler exists
If allowed, the signal is delivered.
4. Signal Handler Execution
The process execution pauses temporarily.
Linux saves:
- CPU registers
- Program counter
- Stack state
- Processor context
The signal handler executes, and after completion, execution resumes.
Standard Signals vs Real-Time Signals in Linux
Linux supports two categories of signals.
Standard Signals
Traditional UNIX signals include:
- SIGINT
- SIGTERM
- SIGKILL
- SIGSEGV
- SIGALRM
Characteristics:
- Limited queueing
- Multiple identical signals may merge into one
- Lower priority
Real-Time Signals in Linux
Linux also supports POSIX real-time signals.
These signals range from:
SIGRTMIN to SIGRTMAX
Features of real-time signals:
- Queued individually
- Delivered in order
- Carry additional data
- Higher priority than standard signals
Real-time signals are commonly used in:
- Embedded Linux
- Real-time systems
- Industrial control
- High-performance IPC
Example of Real-Time Signal Handling
#include
#include
#include
void handler(int sig)
{
write(STDOUT_FILENO,
"Real-time signal received
",
27);
}
int main()
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGRTMIN,
&sa,
NULL);
while (1)
{
pause();
}
}
Sending Signals in Linux
Signals can be sent using multiple mechanisms.
Using kill() System Call
kill(pid, SIGTERM);
This sends a signal to another process.
Using raise()
raise(SIGINT);
This sends a signal to the current process.
Using alarm()
alarm(5);
Generates SIGALRM after 5 seconds.
Using sigqueue()
sigqueue() allows sending additional data with signals.
Example:
union sigval value;
value.sival_int = 100;
sigqueue(pid,
SIGUSR1,
value);
This is useful in advanced IPC applications.
Signal Handling in Multi-Threaded Applications
Signals behave differently in multi-threaded Linux applications.
Understanding this behavior is extremely important for backend servers and concurrent Linux software.
Process-Wide vs Thread-Specific Signals
Some signals target:
- Entire processes
- Specific threads
Linux chooses a thread that is not blocking the signal.
pthread_sigmask()
Multi-threaded programs should use:
pthread_sigmask()
instead of:
sigprocmask()
for thread-specific signal masking.
Dedicated Signal Handling Threads
Large applications often create a dedicated signal-handling thread.
Benefits:
- Cleaner architecture
- Easier debugging
- Better synchronization
- Improved reliability
sigwait() in Linux
sigwait() allows synchronous signal handling.
Instead of asynchronous handlers, threads explicitly wait for signals.
Example:
sigset_t set;
int sig;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigwait(&set, &sig);
This approach is common in enterprise Linux applications.
Handling SIGCHLD in Linux
SIGCHLD is generated when a child process:
Parent processes must handle this correctly to avoid zombie processes.
Zombie Processes in Linux
A zombie process occurs when:
- A child exits
- Parent does not read exit status
Zombie processes waste process table entries.
Preventing Zombie Processes
Example:
void handler(int sig)
{
while (waitpid(-1,
NULL,
WNOHANG) > 0)
{
}
}
This reaps terminated child processes.
Signals and Daemon Processes
Linux daemons rely heavily on signals.
Common examples:
| Signal | Usage |
|---|
| SIGTERM | Graceful shutdown |
| SIGHUP | Reload configuration |
| SIGUSR1 | Custom operations |
| SIGCHLD | Child monitoring |
Examples include:
- Nginx
- Apache
- Systemd services
- Database servers
Linux Signals and Debugging
Signals are critical for debugging Linux applications.
Debuggers like GDB use signals extensively.
Examples:
- SIGSEGV for segmentation faults
- SIGTRAP for breakpoints
- SIGABRT for abnormal termination
Debugging Segmentation Faults
Segmentation faults are among the most common Linux runtime errors.
Causes include:
- Null pointer dereference
- Invalid memory access
- Stack corruption
- Buffer overflow
Tools used:
- gdb
- valgrind
- address sanitizer
Core Dumps in Linux
Certain signals generate core dump files.
Examples:
Core dumps help developers analyze crashes.
Enable core dumps:
ulimit -c unlimited

Linux Signal Handling Best Architecture
Production Linux systems usually follow these principles:
- Minimal signal handlers
- Event-driven design
- Dedicated signal threads
- Safe synchronization
- Async-signal-safe operations only
Many applications use handlers only to set flags.
Example:
volatile sig_atomic_t stop = 0;
void handler(int sig)
{
stop = 1;
}
Main loop:
while (!stop)
{
/* Application logic */
}
This is one of the safest signal handling approaches.
Performance Impact of Signals in Linux
Signals are lightweight compared to many IPC mechanisms.
However, excessive signal usage may impact performance because:
- Context switching occurs
- Kernel intervention is required
- CPU cache may be disturbed
High-frequency communication should usually use:
- Shared memory
- Eventfd
- Pipes
- Message queues
instead of signals.
Security Considerations of Linux Signals
Signals also have security implications.
Improper signal handling may lead to:
- Race conditions
- Denial of service
- Privilege issues
- Process manipulation
Linux restricts signal sending using permissions.
A process can only send signals if:
- Same user owns both processes
- Sender has sufficient privileges
Common Production Issues in Linux Signal Handling
In real-world Linux applications, improper signal handling can introduce difficult and unpredictable bugs.
Unlike normal function calls, signals are asynchronous. They can interrupt the process at almost any point during execution.
Because of this behavior, developers must design signal handling carefully.
Race Conditions Caused by Signals
One of the most common problems in Linux signal handling is race conditions.
A race condition occurs when:
- The main program modifies shared data
- A signal interrupts execution
- The signal handler accesses the same data simultaneously
This may leave the application in an inconsistent state.
Example:
int counter = 0;
void handler(int sig)
{
counter++;
}
If the signal interrupts another update operation on counter, the value may become corrupted.
This problem becomes even more dangerous in:
Preventing Race Conditions in Signal Handlers
Linux developers usually prevent race conditions using:
- Signal masking
- Atomic variables
- Minimal handlers
- Deferred processing
A common and safe architecture is:
- Signal handler sets a flag
- Main loop checks the flag
- Actual processing occurs outside the handler
Example:
volatile sig_atomic_t shutdown_requested = 0;
void handler(int sig)
{
shutdown_requested = 1;
}
Main loop:
while (!shutdown_requested)
{
/* Main application logic */
}
This approach reduces complexity and improves application stability.
Why Signal Handlers Should Be Minimal
Signal handlers should execute as quickly as possible.
Heavy operations inside handlers can create serious problems such as:
- Deadlocks
- Memory corruption
- Re-entrancy bugs
- Application freezes
- Unexpected crashes
Good signal handlers usually perform only small operations such as:
- Setting flags
- Writing to pipes
- Posting semaphores
- Logging minimal messages using write()
Complex operations should always be moved outside the signal handler.
Signal Handling and System Calls
Linux signals directly affect blocking system calls.
For example:
read(socket_fd, buffer, size);
If a signal interrupts this call, Linux may return:
-1
with:
errno = EINTR
Applications must handle this carefully.
This is one reason why SA_RESTART is commonly used.
Without proper handling:
- Network servers may disconnect clients
- Embedded applications may stop reading sensors
- IPC systems may fail unexpectedly
Signals and Linux Process States
Signals can also change Linux process states.
| Signal | Result |
|---|
| SIGSTOP | Stops process execution |
| SIGCONT | Resumes stopped process |
| SIGTERM | Requests termination |
| SIGKILL | Immediately terminates process |
This behavior is heavily used by:
- Linux shells
- Terminal job control
- Process managers
- Debugging tools
Signals in Linux Daemons and Services
Linux background services rely heavily on signals.
For example:
- SIGTERM is used for graceful shutdown
- SIGHUP reloads configuration files
- SIGUSR1 performs custom operations
- SIGCHLD monitors worker processes
Popular Linux services such as:
- Nginx
- Apache
- MySQL
- PostgreSQL
- Redis
all depend heavily on robust signal handling.
Signal Handling in Embedded Linux Systems
Embedded Linux systems often use signals for lightweight event notification.
Common embedded Linux uses include:
- Watchdog systems
- Sensor event notifications
- Child process supervision
- Real-time alerts
- Fault handling
However, embedded systems developers must be careful because improper signal handling may reduce system reliability.
Many embedded Linux systems therefore combine:
- Signals
- Event loops
- Threads
- Message queues
- Shared memory
for more stable architectures.
Linux Signals and Performance
Signals are lightweight compared to many IPC mechanisms.
However, excessive signal usage can still impact performance.
Each signal delivery requires:
- Kernel intervention
- Context saving
- Context restoration
- Scheduler interaction
Frequent signal generation may therefore increase CPU overhead.
For high-frequency communication, Linux developers usually prefer:
- Shared memory
- eventfd
- Pipes
- Socket pairs
- Message queues
Signals are best suited for notifications rather than bulk communication.
Linux Signals and Security
Linux signals also play a role in operating system security.
A process cannot arbitrarily send signals to every other process.
Linux permission rules ensure that:
- The sending process must own the target process
- Or possess elevated privileges
Improper signal handling may introduce:
- Denial-of-service vulnerabilities
- Race conditions
- Unexpected privilege behavior
- Service crashes
Production Linux applications should validate all signal-related logic carefully.
Final Thoughts on Linux Signal Handling
Signals remain one of the most important mechanisms in Linux internals.
Although many modern IPC methods exist, signals are still heavily used because they are:
- Fast
- Lightweight
- Kernel-supported
- Efficient for notifications
However, signal handling must be implemented carefully.
Poor signal handling can introduce:
- Random crashes
- Deadlocks
- Race conditions
- Data corruption
- Difficult debugging problems
Developers should always prefer:
- sigaction()
- Async-signal-safe operations
- Proper signal masking
- Minimal handlers
- Structured architecture
Mastering Linux signal handling greatly improves your understanding of Linux internals, operating systems, process management, and system-level software development.
Conclusion
Linux signals are one of the core building blocks of Linux internals and system programming.
Although signal() is useful for learning basic concepts, modern Linux applications should use sigaction() because it provides:
- Better reliability
- Advanced signal handling
- Signal masking
- Rich signal context
- Production-grade robustness
- Safer execution
Understanding Linux signal handling is essential for:
- Linux developers
- Embedded systems engineers
- Kernel programmers
- Backend developers
- System software engineers
Mastering sigaction(), signal masks, and async-signal-safe programming will help you build stable, reliable, and professional Linux applications.
