Signals in Linux Internals: Complete Guide to signal() vs sigaction() in Linux System Programming

Signals in Linux Internals signal() vs sigaction() Explained with Examples

Linux signal handling is one of the most fundamental concepts in Linux internals and system programming. Whether you are developing embedded Linux applications, writing system-level software, building servers, creating daemons, or working with device drivers, understanding Linux signals is essential. Signals provide a lightweight and asynchronous mechanism for inter-process communication (IPC) between the Linux kernel and user-space applications. They allow the operating system to notify a process when important events occur, such as keyboard interrupts, illegal memory access, timer expiration, child process termination, and software exceptions. Many beginners learn signal handling using the traditional signal() API, but professional Linux applications use sigaction() because it provides safer, more reliable, and production-ready signal handling.

In this complete guide, you will learn:

  • What Linux signals are
  • How signal handling works internally
  • Difference between signal() and sigaction()
  • Why sigaction() is preferred in Linux system programming
  • Signal masks and signal blocking
  • Async-signal-safe programming
  • Common Linux signals and their purposes
  • Real-world Linux signal handling examples
  • Best practices for production Linux applications
  • Common mistakes developers make while handling signals

This article is beginner-friendly while also covering advanced Linux internals concepts useful for embedded systems engineers and Linux developers.

Linux signals are asynchronous notifications used for inter-process communication and event handling in Linux system programming.
This guide explains how signal() and sigaction() work, their differences, signal masks, SA_RESTART, SA_SIGINFO, and async-signal-safe programming practices. Learn production-level Linux signal handling techniques with practical C examples, Linux internals concepts, and best practices for reliable system software.

Table of Contents

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

 

 

registor_now_P

 

 

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

Featuresignal()sigaction()
ReliabilityLimitedHigh
PortabilityInconsistentPOSIX standard
Signal maskingNoYes
Extra signal infoNoYes
Syscall restartUnreliableSupported
Production suitabilityPoorExcellent
Multi-thread supportWeakBetter
Advanced controlMinimalExtensive

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:

  • SIGUSR1
  • SIGUSR2

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:

  • Exits
  • Stops
  • Continues

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:

SignalUsage
SIGTERMGraceful shutdown
SIGHUPReload configuration
SIGUSR1Custom operations
SIGCHLDChild 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:

  • SIGSEGV
  • SIGABRT
  • SIGFPE

Core dumps help developers analyze crashes.

Enable core dumps:

ulimit -c unlimited

 

Explore Courses - Learn More

 

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.

SignalResult
SIGSTOPStops process execution
SIGCONTResumes stopped process
SIGTERMRequests termination
SIGKILLImmediately 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.

 

 

Talk to Academic Advisor

FAQs

signal() is an older and less reliable API for handling signals, while sigaction() is the modern POSIX-standard interface that provides advanced control, signal masking, and better portability for Linux system programming.

sigaction() offers safer and more predictable behavior, supports SA_RESTART, signal masks, and SA_SIGINFO, making it suitable for production Linux applications and multi-threaded programs.

printf() is not async-signal-safe and may cause deadlocks or memory corruption if interrupted by another signal. Functions like write() are recommended inside Linux signal handlers.

Author

Embedded Systems trainer – IIES

Updated On: 23-05-26


10+ years of hands-on experience delivering practical training in Embedded Systems and it's design