Fundamentals 19 min read

Understanding Unix Signals: From Hardware Interrupts to Custom Handlers

This article explains what Unix signals are, categorizes reliable and unreliable signals, describes how they are generated by hardware and software, and details registration, blocking, pending states, handling functions, and common usage examples with C code.

Open Source Linux
Open Source Linux
Open Source Linux
Understanding Unix Signals: From Hardware Interrupts to Custom Handlers

1. What is a signal?

A signal is essentially a software interrupt.

Example:

Enter a command to start a foreground process in the shell.

The user presses Ctrl‑C, generating a hardware interrupt.

If the CPU is executing that process, the user‑space code pauses, the CPU switches to kernel mode to handle the interrupt.

The terminal driver translates Ctrl‑C into a SIGINT signal and records it in the process's PCB.

Before returning to user space, the kernel sees the pending SIGINT, whose default action is to terminate the process, so the process ends.

In this example, the hardware interrupt caused by Ctrl‑C is a signal. Only the foreground process receives such a signal; appending '&' runs a process in the background. The shell can run one foreground and many background processes, and only the foreground process can receive control‑key signals like Ctrl‑C.

2. Types of signals

Use the command to view: kill -l Unreliable signals: numbers 1‑31 may be lost. Reliable signals: numbers 34‑64 cannot be lost.

Signal table
Signal table

SIGHUP (1): Hangup detected on controlling terminal or death of controlling process.

SIGINT (2): Interrupt from keyboard (Ctrl‑C), action: terminate.

SIGQUIT (3): Quit from keyboard (Ctrl‑\), action: core dump.

SIGABRT (6): Abort signal from abort(3) (e.g., double free), action: core dump.

SIGKILL (9): Kill signal, action: terminate; cannot be blocked, ignored, or handled.

SIGSEGV (11): Invalid memory reference (null‑pointer dereference, out‑of‑bounds access), action: core dump.

SIGPIPE (13): Broken pipe, action: terminate.

SIGCHLD (17): Child stopped or terminated (ignored by default).

SIGSTOP (19): Stop process, action: stop.

SIGTSTP (20): Stop typed at terminal (Ctrl‑Z), action: stop.

For detailed actions and information, see man 7 signal .

3. Signal generation

3.1 Hardware generation

Hardware generation occurs via terminal key presses:

Ctrl‑C: SIGINT (2) sent to the foreground process; appending '&' runs it in the background, fg brings it back to the foreground.

Ctrl‑Z: SIGTSTP (20), rarely used unless a specific scenario requires it.

Ctrl‑\\: SIGQUIT (3), generates a core dump file.

Conditions for generating a core dump:

ulimit -a   # ensure core dump size is not limited
# Core dump can be produced by:
# 1. Dereferencing a null pointer (SIGSEGV)
# 2. Out‑of‑bounds memory access (SIGSEGV)
# 3. Double free (SIGABRT)
# 4. free(NULL) does not crash

3.2 Software generation

Software generation uses system calls to send signals to processes.

kill function

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);   // pid: process ID, sig: signal number
// Returns 0 on success, -1 on failure

kill command: kill -[signal] pid abort: void abort(void); – raises signal 6 (SIGABRT).

alarm: unsigned int alarm(unsigned int seconds); – raises SIGALRM after the specified seconds.

4. Signal registration

Signal registration is divided into reliable and unreliable registration and is implemented with a bitmap and a sigqueue queue.

Signal registration diagram
Signal registration diagram

4.1 Unreliable signal registration

When a process receives an unreliable signal:

Set the corresponding bit in the bitmap to 1.

Add a sigqueue node to the queue; if a node for that signal already exists, do not add another.

4.2 Reliable signal registration

When a process receives a reliable signal:

Set the corresponding bit in the bitmap to 1 and always add a sigqueue node, regardless of existing nodes.

5. Signal deregistration

5.1 Unreliable signal deregistration

Clear the corresponding bit to 0 and dequeue the sigqueue node.

5.2 Reliable signal deregistration

Dequeue the sigqueue node; if no other node for that signal remains, clear the bitmap bit, otherwise keep the bit set.

6. Signal blocking

6.1 How signals are blocked

Signal blocking diagram
Signal blocking diagram

Blocking a signal does not prevent its registration; the signal is registered but not processed until it is unblocked. The kernel sets the corresponding bit in the block bitmap to 1. When the process returns from kernel to user space, do_signal checks the block bitmap and skips processing blocked signals.

6.2 sigprocmask

Function prototype:

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

Parameters:

how: operation type
SIG_BLOCK   – set bits to block
SIG_UNBLOCK – clear bits to unblock
SIG_SETMASK – replace the block bitmap
set: pointer to bitmap to modify
oldset: receives previous bitmap

Example that blocks all signals and then loops:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signcallback(int signumber) {
    printf("change the signal %d
", signumber);
}

int main() {
    sigset_t set, oldset;
    sigfillset(&set); // set all bits to 1 (block all signals)
    sigprocmask(SIG_BLOCK, &set, &oldset);
    while (1) {
        sleep(1);
    }
    return 0;
}
Result of blocking example
Result of blocking example

7. Pending signals

7.1 Concept of pending

Delivery is the actual execution of a signal’s action. The period between generation and delivery is the pending state. A blocked signal remains pending until unblocked. Blocking differs from ignoring: a blocked signal is never delivered, while an ignored signal is delivered and then discarded.

7.2 sigpending

Prototype:

int sigpending(sigset_t *set);

It retrieves the set of pending signals for the calling process.

Example:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void signalcallback(int signumber) {
    printf("change signumber %d
", signumber);
}

void printsigset(sigset_t *set) {
    for (int i = 0; i < 32; i++) {
        if (sigismember(set, i)) putchar('1');
        else putchar('0');
    }
    putchar('
');
}

int main() {
    signal(2, signalcallback);
    signal(10, signalcallback);
    sigset_t set, oldset, pending;
    sigfillset(&set); // block all signals
    sigprocmask(SIG_BLOCK, &set, &oldset);
    while (1) {
        sigpending(&pending);
        printsigset(&pending);
        sleep(1);
    }
    return 0;
}
Pending signals output
Pending signals output

8. Signal handling

Signal handling overview
Signal handling overview
Each signal has two flag bits (blocked and pending) and a function pointer indicating its action.

Examples of signal states:

SIGHUP is not blocked and has never occurred; its default action will be taken upon delivery.

SIGINT has occurred but is currently blocked, so it cannot be delivered yet; its default action is termination, but it cannot be ignored while blocked.

SIGQUIT has not occurred; if it occurs while blocked, a user‑defined handler will be invoked after unblocking.

8.1 signal function

Changes the handling action of a signal.

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

8.2 sigaction function

Reads or modifies the action associated with a specific signal.

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

Structure struct sigaction fields:

void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;   // signals blocked during handler execution
int sa_flags;      // e.g., SA_SIGINFO
void (*sa_restorer)(void);

Example using sigaction:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void signcallback(int signumber) {
    printf("change signumber %d
", signumber);
}

int main() {
    struct sigaction act, oldact;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = signcallback;
    sigaction(3, &act, &oldact); // set handler for SIGQUIT (3)
    while (1) {
        sleep(1);
    }
    return 0;
}
sigaction example result
sigaction example result

8.3 Custom signal handling flow

Custom handling flow
Custom handling flow

The task_struct contains a struct sighand_struct. struct sighand_struct holds an array struct k_sigaction action[_NSIG].

Each array entry’s _sighandler_t sa_handler stores the handling method; changing it alters how the signal is processed.

9. Signal catching

9.1 Conditions for catching

If the handling action is a user‑defined function, the signal is caught and the function is invoked upon delivery.

9.2 Catching flow

Signal catching flow
Signal catching flow

When returning from kernel to user mode, the kernel calls do_signal:

If no pending signal, sys_return resumes the original user‑space context.

If a pending signal exists, the kernel invokes the registered handler (e.g., sighandler) instead of the original context, using a separate stack.

After the handler returns, the kernel automatically performs a sigreturn system call to resume normal execution.

10. Common signal set operations

int sigemptyset(sigset_t *set);   // clear all bits
int sigfillset(sigset_t *set);    // set all bits
int sigaddset(sigset_t *set, int signum); // set specific bit
int sigdelset(sigset_t *set, int signum); // clear specific bit
int sigismember(const sigset_t *set, int signum); // test bit

11. SIGCHLD signal

SIGCHLD is sent to a parent when a child terminates. By default it is ignored; if ignored, terminated children become zombie processes.

Custom handling example:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>

void signcallback(int signumber) {
    printf("change signal %d
", signumber);
    wait(NULL);
}

int main() {
    signal(17, signcallback); // SIGCHLD
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork");
        return -1;
    } else if (pid == 0) {
        printf("I am child
");
        sleep(1);
        exit(12);
    } else {
        while (1) {
            sleep(1);
        }
    }
    return 0;
}

Command to view background processes: ps aux | grep ./fork

Background process view
Background process view
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

process managementClinuxUnixsignals
Open Source Linux
Written by

Open Source Linux

Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.