Linux - signals

What signals are, how they work in Linux, common signal types, trap usage and practical examples.

topic: Linux

release date: 2026-03-12

introduction

A signal is a small, asynchronous notification delivered to a process by the kernel, by another process, or by a user. Signals are used to indicate events such as interrupts from the terminal, timer expirations, child process status changes, or critical faults like segmentation violations. They are a primitive form of interprocess communication and are meant to be lightweight and quick.

Conceptually signals are not function calls. When a signal arrives the kernel marks it for the target process and that process, when scheduled, notices the pending signal. If a handler is installed the handler runs at an interrupt point. If not, the signal's default action applies. Some signals are uncatchable or unblockable and always perform their default action.

Signals are asynchronous. That means they can arrive at almost any time and can interrupt running code. Keep handlers minimal and safe.

types of signals

Below is a comprehensive table with signal numbers, names and short descriptions. Real-time signal numeric values can differ between kernels; prefer using names in scripts and programs. After the table there is a more detailed discussion of the most commonly used signals.

NUMBER NAME DESCRIPTION
1SIGHUPHangup - controlling terminal closed; often used to tell daemons to re-read config
2SIGINTInteractive interrupt (Ctrl+C)
3SIGQUITQuit - produce core dump (Ctrl+\)
4SIGILLIllegal instruction - invalid CPU instruction executed
5SIGTRAPTrace trap - used by debuggers
6SIGABRTAbort from abort(3) - program requested abnormal termination
7SIGBUSBus error - bad memory access alignment or hardware fault
8SIGFPEFloating point exception - division by zero, overflow, etc.
9SIGKILLImmediate uncatchable kill - terminate process without cleanup
10SIGUSR1User-defined signal 1 - for application-defined purposes
11SIGSEGVSegmentation fault - invalid memory access; usually core dumps
12SIGUSR2User-defined signal 2 - for application-defined purposes
13SIGPIPEBroken pipe - writing to a pipe with no reader
14SIGALRMAlarm clock - timer expiration from alarm()
15SIGTERMTermination request - polite shutdown signal
16SIGSTKFLTStack fault on coprocessor (architecture dependent)
17SIGCHLDChild status changed - parent should reap children
18SIGCONTContinue - resume a stopped process
19SIGSTOPStop - stop (pause) process; uncatchable
20SIGTSTPTerminal stop - interactive stop (Ctrl+Z)
21SIGTTINBackground read from TTY - stop
22SIGTTOUBackground write to TTY - stop
23SIGURGUrgent condition on socket
24SIGXCPUCPU time limit exceeded
25SIGXFSZFile size limit exceeded
26SIGVTALRMVirtual timer expired
27SIGPROFProfiling timer expired
28SIGWINCHWindow size change - terminal resized
29SIGIOI/O now possible - async I/O notification
30SIGPWRPower failure (system) - platform dependent
31SIGSYSBad system call
32SIGRTMINReal-time signal - start of realtime range
33SIGRTMIN+1Real-time signal
34SIGRTMIN+2Real-time signal
35SIGRTMIN+3Real-time signal
36SIGRTMIN+4Real-time signal
37SIGRTMAX-4Real-time signal
38SIGRTMAX-3Real-time signal
39SIGRTMAX-2Real-time signal
40SIGRTMAX-1Real-time signal
41SIGRTMAXReal-time signal - end of realtime range
42SIGRTMIN+5Real-time signal
43SIGRTMIN+6Real-time signal
44SIGRTMIN+7Real-time signal
45SIGRTMIN+8Real-time signal
46SIGRTMIN+9Real-time signal
47SIGRTMIN+10Real-time signal
48SIGRTMIN+11Real-time signal
49SIGRTMIN+12Real-time signal
50SIGRTMIN+13Real-time signal
51SIGRTMIN+14Real-time signal
52SIGRTMAX-5Real-time signal
53SIGRTMAX-6Real-time signal
54SIGRTMAX-7Real-time signal
55SIGRTMAX-8Real-time signal
56SIGRTMAX-9Real-time signal
57SIGRTMAX-10Real-time signal
58SIGRTMAX-11Real-time signal
59SIGRTMAX-12Real-time signal
60SIGRTMAX-13Real-time signal
61SIGRTMAX-14Real-time signal
62SIGRTMAX-15Real-time signal
63SIGRTMAX-16Real-time signal
64SIGRTMAX-17Real-time signal
Real-time signal numeric layout depends on kernel. Use kill -l to inspect values on your system.

detailed discussion - common signals

Below are more complete verbal descriptions and common use cases for the signals you will meet most often. Read these paragraphs before trying examples. Understanding intent matters more than memorizing numbers.

SIGHUP

Originally SIGHUP indicated that a user's modem line hung up. Today it commonly signals to daemons that the controlling terminal went away or that they should reinitialize. Many servers use SIGHUP to reload configuration without stopping the service. The handler for SIGHUP typically triggers a config re-read and a graceful restart of worker processes.

SIGINT

SIGINT is emitted by the terminal driver when the user presses Ctrl+C. It is a polite interactive interrupt. Programs frequently catch SIGINT to stop cleanly - for example, by closing files, flushing buffers, or saving progress - rather than terminating immediately.

SIGTERM

SIGTERM is the conventional signal for asking a process to terminate. It gives the process a chance to tidy up. Services, supervisors and shutdown scripts use SIGTERM so programs can shut down gracefully. If the process ignores SIGTERM or does not terminate in reasonable time, SIGKILL is used next.

SIGKILL

SIGKILL cannot be caught, blocked or ignored. The kernel kills the process immediately and reclaims resources. Because it prevents cleanup, prefer SIGTERM first and only use SIGKILL if the process is stuck.

SIGSTOP / SIGCONT

SIGSTOP pauses a process and is not catchable. SIGCONT resumes it. These signals are used by job control, debuggers and wrappers that want to suspend work temporarily.

SIGCHLD

When a child process stops or exits, SIGCHLD is sent to the parent. The parent should call wait or waitpid to reap the child and avoid zombie processes. Many server frameworks rely on SIGCHLD handlers to monitor children.

SIGALRM, SIGVTALRM, SIGPROF

These signals come from timers: SIGALRM from alarm(), SIGVTALRM from virtual CPU timers, SIGPROF from profiling timers. They are useful for implementing timeouts or sampling profilers.

SIGPIPE

Senders writing to a pipe whose reader closed receive SIGPIPE. Default behavior is termination. Many networked programs prefer to ignore SIGPIPE and handle EPIPE errors explicitly to implement retries or logging.

SIGSEGV, SIGILL, SIGFPE

These are serious faults: segmentation violation, illegal instruction, floating point exception. They usually indicate programming bugs or corrupted memory and often produce a core dump for debugging.

SIGUSR1 / SIGUSR2

User signals are free for application-specific meanings. They are small, simple control channels for toggling modes, requesting status dumps, rotating logs or reloading lightweight parts of state. They are very handy because they do not require sockets or complex IPC.

SIGWINCH

This signal indicates that the terminal window size changed. Terminal applications re-query the terminal size and redraw UI when they receive SIGWINCH.

kill command

The kill command sends signals to processes. Despite its name, it can be used to send any signal, not only termination signals. The most portable and readable way to use it is by signal name.

how it works

When you run kill -SIGNAL PID, the kernel arranges for the named signal to be delivered to the process with the given PID. If you do not have permission to signal the process, the kernel returns an error. Root can signal any process.

common syntaxes

  • kill -SIGTERM <pid> - signal by name
  • kill -15 <pid> - numeric signal
  • kill <pid> - default is SIGTERM on many systems
  • kill -l - list signals and numbers
  • pkill <pattern> - signal by process name or pattern
  • kill -TERM -<pgid> - send to process group (note leading minus)
  • kill -9 <pid> - force kill (SIGKILL)

examples

# polite shutdown
kill -SIGTERM 1234

# same using numeric form
kill -15 1234

# force kill if polite attempt fails
kill -9 1234

# send SIGUSR1 to process 4321
kill -USR1 4321

# send SIGTERM to all processes in process group 1000
kill -TERM -1000

# list signals on this system
kill -l

When scripting prefer names for readability and portability. Use negative PIDs to target process groups. pkill and pgrep work well when you need name-based matching, but be careful with ambiguous names.

Some administrative tools and service managers will re-spawn or react to kill signals. For example sending SIGTERM to a supervised process may cause the supervisor to restart it.

trap command

trap sets shell handlers for signals. When the shell receives a trapped signal it evaluates the trap code. Traps are the shell equivalent of signal handlers in C and are essential for cleanup and graceful exits.

basic forms

  • trap 'handler' SIGINT - run handler on SIGINT
  • trap '' SIGPIPE - ignore SIGPIPE
  • trap - SIGINT - restore default action for SIGINT
  • trap 'cmds' 0 - run cmds on shell exit (EXIT)

Trap handlers run in the context of the shell. Keep them safe and avoid complex non-reentrant operations that may fail if they interrupt other operations.

examples

# simple trap on Ctrl+C
trap 'echo "interrupted"; exit 130' SIGINT

# ignore SIGPIPE
trap '' SIGPIPE

# restore default for SIGINT
trap - SIGINT
Using exit inside scripts sourced by interactive shells closes the shell. Use return in that situation.

trap EXIT

The trap for signal number 0 or name EXIT runs when the shell exits for any reason - normal exit, unhandled error or after an explicit exit. It is ideal for final cleanup: removing temporary files, restoring terminal state, killing background jobs, or restoring cursor visibility.

# cleanup on any exit
tmpfile="/tmp/my.$$"
touch "$tmpfile"
trap 'rm -f "$tmpfile"; echo "cleaned";' EXIT

# rest of script...
sleep 2
exit 0

trap in scripts

In real scripts traps are used to ensure system state is consistent when the script ends or is interrupted. Below are practical, slightly more elaborate cases showing typical uses.

restore cursor visibility on exit

If a script hides the terminal cursor for a nicer UI, make sure to restore it even if the user presses Ctrl+C. Use a trap EXIT for that.

#!/usr/bin/env bash
# hide cursor and restore on exit
hide_cursor() {
	printf '\033[?25l'
}
show_cursor() {
	printf '\033[?25h'
}
hide_cursor
trap 'show_cursor; echo "cursor restored";' EXIT

# simulate work
for i in {1..10}; do
	printf '.'
	sleep 1
done

Alternative using tput if available: tput civis to hide and tput cnorm to show.

trap SIGUSR1 / SIGUSR2 for control

Use SIGUSR signals to add runtime control without sockets. Below a worker toggles verbose logging on SIGUSR1 and prints status on SIGUSR2.

#!/usr/bin/env bash
VERBOSE=0
status() {
	echo "pid $$ - verbose $VERBOSE"
}
toggle_verbose() {
	VERBOSE=$((1 - VERBOSE))
	echo "verbose -> $VERBOSE"
}
trap 'toggle_verbose' SIGUSR1
trap 'status' SIGUSR2
trap 'echo "exiting"; exit' SIGINT SIGTERM

while true; do
	[ "$VERBOSE" -eq 1 ] && echo "working..."
	sleep 2
done

reaping children and avoiding zombies

When a script spawns background jobs it should reap them. A SIGCHLD trap that calls wait -n or a loop with wait prevents accumulating zombie processes.

# example: reap children
trap 'while wait -n 2>/dev/null; do :; done' SIGCHLD

longtask() {
	sleep 5
	echo "done $$"
}
longtask &  # background
longtask &

# wait for all children
wait
Traps run in the main shell context. Avoid heavy computation in handlers; instead set flags and handle them in the main loop.

outro

Signals are compact and efficient for notifying processes about events. The right approach is to prefer graceful handling - use SIGTERM and traps to clean up, use SIGUSR1/SIGUSR2 for simple runtime control, and reserve SIGKILL only for stuck processes. For robust programs use sigaction() on the C side and keep shell traps careful and minimal.

If this article helps, consider a cup of coffee via >link<.