Signale sind die einfachste Form von Inter-Prozess-Kommunikation, die Unix kennt: ein einzelnes ganzzahliges Token, das der Kernel oder ein anderer Prozess einem Ziel-Prozess asynchron zustellt. Jedes Signal hat einen Default-Handler — Beenden, Coredump, Pausieren oder Ignorieren — und für die meisten lässt sich dieser Handler durch eigenen Code ersetzen. Wer die Mechanik versteht, kann Skripte sauber abräumen, Dienste ohne Restart neu konfigurieren und Hänger gezielt entwirren. Zwei Signale fallen aus dem Rahmen: SIGKILL und SIGSTOP lassen sich nie abfangen — eine bewusste Garantie des Kernels.
Was Signale sind
Ein Signal ist eine asynchrone Benachrichtigung, die an einen Prozess zugestellt wird. Asynchron heißt: Sie kommt unabhängig vom aktuellen Programmfluss an — irgendwann zwischen zwei Maschinenbefehlen unterbricht der Kernel den Prozess, ruft den passenden Handler auf und kehrt anschließend an die ursprüngliche Stelle zurück. Aus Sicht des Programms wirkt das wie ein Software-Interrupt.
Signale kommen aus zwei Quellen. Der Kernel sendet sie, wenn er etwas Außergewöhnliches feststellt — Speicherzugriffsfehler, illegaler Befehl, Division durch Null, abgelaufener Timer, geschlossene Pipe. Andere Prozesse dürfen Signale per kill()-Systemaufruf an Ziele senden, sofern sie die nötigen Rechte haben (gleicher User oder root).
Der Standard ist POSIX: Eine fest definierte Menge von Signalen mit Namen wie SIGINT oder SIGTERM und zugehörigen Nummern. Linux ergänzt POSIX um die Realtime-Signale SIGRTMIN bis SIGRTMAX für Anwendungen, die Queueing brauchen. Jeder Prozess kann für die meisten Signale entweder den Default-Handler verwenden, einen eigenen Handler installieren oder das Signal blockieren und ignorieren.
Wichtigste Signale
Die folgende Tabelle zeigt die in der Praxis relevanten Signale. Die Nummern gelten für Linux auf x86 und ARM — auf anderen Plattformen können einzelne Werte abweichen, weshalb man im Zweifel immer den Namen und nicht die Nummer benutzt.
| Signal | Nr. | Default | Bedeutung |
|---|---|---|---|
| SIGHUP | 1 | Term | Hangup — Terminal geschlossen, häufig als Reload-Konvention für Dienste |
| SIGINT | 2 | Term | Interrupt vom Terminal, Strg+C |
| SIGQUIT | 3 | Core | Quit vom Terminal, Strg+, erzeugt Coredump |
| SIGILL | 4 | Core | Illegal Instruction — CPU traf auf einen unbekannten Opcode |
| SIGABRT | 6 | Core | Abort — typischerweise von abort() oder assert() |
| SIGFPE | 8 | Core | Floating Point Exception, oft Division durch Null |
| SIGKILL | 9 | Term | Sofortiger Kill — kann NICHT abgefangen oder ignoriert werden |
| SIGUSR1 | 10 | Term | Frei für die Anwendung, beliebig belegbar |
| SIGSEGV | 11 | Core | Segmentation Fault — ungültiger Speicherzugriff |
| SIGUSR2 | 12 | Term | Frei für die Anwendung, beliebig belegbar |
| SIGPIPE | 13 | Term | Schreiben in eine Pipe ohne Leser |
| SIGALRM | 14 | Term | Timer-Ablauf nach alarm() |
| SIGTERM | 15 | Term | Default-Signal von kill — höfliche Aufforderung zum Beenden |
| SIGCHLD | 17 | Ign | Kindprozess hat sich beendet oder geändert |
| SIGCONT | 18 | Cont | Fortsetzen eines gestoppten Prozesses |
| SIGSTOP | 19 | Stop | Anhalten — kann NICHT abgefangen oder ignoriert werden |
| SIGTSTP | 20 | Stop | Stop vom Terminal, Strg+Z |
| SIGTTIN | 21 | Stop | Hintergrundprozess versucht von Terminal zu lesen |
| SIGTTOU | 22 | Stop | Hintergrundprozess versucht auf Terminal zu schreiben |
| SIGWINCH | 28 | Ign | Terminal-Größe hat sich geändert |
Die für den Alltag wichtigsten sind SIGINT, SIGTERM, SIGKILL, SIGHUP, SIGSTOP und SIGCONT. Alles, was mit Coredumps zusammenhängt (SIGSEGV, SIGABRT, SIGFPE, SIGILL), kommt fast nur bei abstürzenden Programmen vor — als Anwender sieht man sie typischerweise nur in Crash-Logs.
Default-Handler
Jedes Signal hat eine im Kernel hinterlegte Default-Aktion, falls der Prozess keinen eigenen Handler installiert hat. Diese Aktion bestimmt, was passiert, wenn das Signal nicht abgefangen wird.
| Default | Aktion |
|---|---|
| Term | Prozess wird sofort beendet, kein Coredump |
| Core | Prozess wird beendet und ein Coredump geschrieben (sofern erlaubt) |
| Ign | Signal wird ignoriert, der Prozess läuft weiter |
| Stop | Prozess wird angehalten, bis er SIGCONT empfängt |
| Cont | Ein gestoppter Prozess wird fortgesetzt |
SIGTERM und SIGINT haben Default Term — der Prozess endet ohne Aufräum-Möglichkeit, sofern er keinen Handler installiert. Genau deshalb installieren saubere Programme einen Handler, der noch Datenbank-Verbindungen schließt, Buffer flusht und temporäre Dateien löscht, bevor das Programm endet. SIGCHLD hat Default Ign — der Eltern-Prozess wird durch das Ende eines Kindes nicht automatisch unterbrochen, kann aber per Handler oder wait() reagieren. Ob ein Coredump bei einem Core-Signal tatsächlich entsteht, hängt von ulimit -c und /proc/sys/kernel/core_pattern ab.
Signale auflisten
Bash und coreutils liefern eine eingebaute Übersicht aller Signale mit ihren Nummern. Das ist die schnellste Möglichkeit, im Terminal nachzuschlagen, welche Nummer zu welchem Namen gehört.
kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM
27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS
34) SIGRTMIN ... 64) SIGRTMAXkill -l SIGTERM liefert die Nummer zu einem Namen, kill -l 9 umgekehrt den Namen zu einer Nummer. Für die ausführliche Beschreibung jedes Signals — inklusive Default-Aktion, Standardisierung und Architektur-Abhängigkeiten — ist die Manpage man 7 signal die Referenz.
Signale abfangen mit trap
In Bash registrierst du eigene Handler mit dem Builtin trap. Die Syntax: trap 'BEFEHL' SIGNAL [SIGNAL ...]. Der Befehl wird in einfache Anführungszeichen gesetzt, damit Variablen erst zur Trigger-Zeit expandiert werden, nicht schon beim Setzen des Traps.
#!/usr/bin/env bash
cleanup() {
echo "Raeume auf..." >&2
rm -f /tmp/mein-skript.lock
exit 130
}
trap cleanup SIGINT SIGTERM
echo "Laufe — beende mit Strg+C"
while true; do sleep 1; doneDas Skript schreibt eine Lockdatei und installiert einen Handler, der bei SIGINT (Strg+C) oder SIGTERM (kill PID) die Lockdatei wegräumt und mit Exit-Code 130 endet. Für vollständige Cleanup-Patterns — EXIT- und ERR-Trap, Stack-Trace, mehrere Aufräum-Aktionen in einer Funktion — ist der Artikel zur Fehlerbehandlung die ausführliche Referenz. trap -p listet alle aktuell registrierten Handler, trap - SIGNAL (Bindestrich) entfernt einen Handler und stellt den Default wieder her.
Signale aus Programmen senden
Auf der Shell ist kill -SIGNAL PID der Standard-Weg, ein Signal zu senden — trotz seines Namens beendet kill keinen Prozess, sondern stellt nur das angegebene Signal zu. Ohne Signal-Argument sendet kill das Default-Signal SIGTERM. Details und Varianten (pkill, killall) finden sich im Artikel zu kill.
kill -TERM 12345
kill -HUP $(pidof nginx)
kill -USR1 $$
kill -0 12345kill -TERM ist die höfliche Variante, kill -HUP löst den klassischen Reload bei Diensten aus, kill -USR1 $$ schickt ein User-Signal an die eigene Shell ($$ ist die eigene PID), und kill -0 sendet kein Signal — der Aufruf prüft nur, ob die PID existiert und du zustellen dürftest. Programme nutzen je nach Sprache eigene APIs: in C die Header <signal.h> mit signal() oder sigaction(), in Python import signal, in Go os/signal, in Node.js process.on('SIGTERM', ...).
Praxis-Patterns
Die folgenden Bausteine decken die häufigsten Aufgaben rund um Signale ab — vom Reload eines laufenden Dienstes bis zum Cleanup-Trap im eigenen Skript.
Reload ohne Restart
kill -HUP $(pidof nginx)Viele Dienste — nginx, sshd, rsyslogd, haproxy — interpretieren SIGHUP als „Konfiguration neu laden”. Der Master-Prozess liest seine Konfigurationsdatei neu ein, startet die Worker neu und bedient bestehende Verbindungen sauber zu Ende. Ein klassisches systemctl restart würde dagegen den Dienst kurz komplett anhalten — kill -HUP ist die unterbrechungsfreie Variante.
Pause und Fortsetzen
kill -STOP 12345
sleep 60
kill -CONT 12345SIGSTOP friert den Prozess komplett ein — er verbraucht keine CPU mehr, behält aber Speicher und offene Dateien. SIGCONT setzt ihn fort, als wäre nichts gewesen. Praktisch für temporäre Lastreduktion oder zum Debuggen: einen laufenden Prozess anhalten, mit gdb oder strace inspizieren, dann fortsetzen. Da SIGSTOP nicht abgefangen werden kann, ist die Pause garantiert wirksam.
Coredump erzwingen
kill -ABRT 12345Bei einem hängenden Prozess kann ein Coredump die einzige Möglichkeit sein, den Zustand für eine spätere Analyse mit gdb festzuhalten. SIGABRT beendet den Prozess und erzeugt — sofern ulimit -c und core_pattern es zulassen — einen Coredump. Alternativ liefert gcore PID einen Coredump, ohne den Prozess zu beenden.
User-Signal an eigenes Skript
#!/usr/bin/env bash
reload_config() {
echo "Konfiguration neu laden..." >&2
source /etc/mein-skript.conf
}
trap reload_config USR1
echo "PID $$ — sende USR1 fuer Reload"
while true; do
sleep 30
do_work
doneSIGUSR1 und SIGUSR2 sind explizit für eigene Zwecke vorgesehen. Ein lang laufendes Skript kann mit einem trap ... USR1 einen Reload-Mechanismus implementieren: kill -USR1 PID von außen löst dann das Neu-Laden der Konfiguration aus, ohne den Worker zu unterbrechen. Wichtig dabei: Bash führt den Trap erst nach dem aktuell laufenden Befehl aus — bei sleep 30 kann sich die Reaktion also um bis zu 30 Sekunden verzögern.
Cleanup-Trap
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
echo "Arbeite in $tmp"
cp datei.bin "$tmp/work.bin"EXIT ist kein echtes Signal, sondern ein Bash-Pseudo-Trap, der bei jedem Skript-Ende läuft — egal ob durch normales Ende, exit N, set -e-Abbruch oder SIGINT. Das macht ihn zum idealen Ort für Aufräumarbeiten. Die Variable tmp wird in einfachen Anführungszeichen erst zur Trigger-Zeit expandiert, sodass der zum Cleanup-Zeitpunkt aktuelle Wert verwendet wird.
Window-Resize bemerken
redraw() {
local cols=$(tput cols)
local rows=$(tput lines)
echo "Neue Groesse: ${cols}x${rows}" >&2
}
trap redraw WINCH
echo "Aendere die Terminal-Groesse..."
while true; do sleep 1; doneSIGWINCH wird vom Terminal-Emulator gesendet, sobald sich die Fenstergröße ändert. TUI-Programme wie htop, vim oder tmux nutzen das, um ihre Layouts neu zu zeichnen. In Skripten ist es nützlich, wenn man eine Statuszeile oder Fortschrittsanzeige aktuell halten will, ohne ständig zu pollen.
Besonderheiten
SIGKILL und SIGSTOP sind unfangbar
Der Kernel garantiert, dass sich SIGKILL (9) und SIGSTOP (19) weder abfangen noch ignorieren noch blockieren lassen. Ein Prozess kann sich also nie gegen einen sauberen Kill oder ein Einfrieren wehren — das ist die letzte Verteidigungslinie des Betriebssystems gegen Amok-Programme. Praktische Konsequenz: Nutze kill -9 nur, wenn kill -TERM nicht greift, weil ein KILL keine Aufraeumarbeit mehr erlaubt. Datenbank-Server, die per SIGKILL beendet werden, können kaputte Indizes hinterlassen.
Signal-Nummern sind plattformabhaengig
Auf Linux/x86 ist SIGUSR1 die Nummer 10, auf Linux/MIPS aber 16 — die Namen sind POSIX-standardisiert, die Nummern nicht. Skripte und Programme sollten daher immer den Namen verwenden (kill -TERM, nicht kill -15), sonst funktionieren sie auf anderen Architekturen nicht zuverlaessig. Linux 1-31 sind die klassischen Standard-Signale, 34-64 sind die Realtime-Signale.
Realtime-Signale unterstuetzen Queueing
Die normalen Signale SIGUSR1 und SIGUSR2 werden nicht aufgestaut: Treffen mehrere Signale ein, bevor der Handler läuft, werden sie zu einem zusammengelegt. Die Realtime-Signale SIGRTMIN bis SIGRTMAX sind anders — der Kernel queued sie, sodass jedes einzelne Signal mit eigenem Handler-Aufruf zugestellt wird. Anwendungen, die Signale als zaehlbare Events brauchen (z. B. für Echtzeit-IPC), verwenden deshalb Realtime-Signale.
SIGPIPE killt standardmäßig
Schreibt ein Prozess in eine Pipe, deren Leser bereits geschlossen ist, bekommt er SIGPIPE mit Default Term — der Prozess wird ohne weiteres beendet. Genau das passiert in Pipelines wie cat riesige.log | head -1: Sobald head nach der ersten Zeile schliesst, bekommt cat SIGPIPE und endet. Das ist gewollt und harmlos. Programme, die mit Pipes arbeiten, ignorieren SIGPIPE oft explizit (signal(SIGPIPE, SIG_IGN)) und behandeln den Fehler stattdessen am write()-Returncode.
SIGCHLD ist die Basis von wait()
Wenn ein Kindprozess endet, sendet der Kernel SIGCHLD an den Eltern-Prozess. Default ist Ign — das Signal wird also ignoriert, der Eltern-Prozess merkt nichts, und wenn er nie wait() ruft, bleibt das Kind als Zombie liegen. Saubere Server installieren einen SIGCHLD-Handler, der waitpid() mit WNOHANG aufruft, um alle beendeten Kinder einzusammeln. Mehr zum Thema im Artikel zum Prozess-Modell.
Signal-safe ist eine kurze Liste
Aus einem Signal-Handler dürfen nur async-signal-safe Funktionen aufgerufen werden — die Liste in man 7 signal-safety umfasst etwa write(), _exit() und signal(), aber nicht printf, malloc, fprintf oder pthread_mutex_lock. Ein printf im Handler kann den Heap zerstören, wenn das Signal genau in einem malloc zugestellt wird. In Bash ist das Problem entschärft, weil Bash den Trap erst nach dem aktuellen Befehl ausführt — in C-Code hingegen ist es eine der häufigsten Quellen für Heisenbugs.
Bash-trap erbt nicht in Subshells
Ein in der Eltern-Shell gesetzter Trap wirkt nicht automatisch in (...)-Subshells, $(...)-Command-Substitutions oder mit & gestarteten Hintergrundprozessen. Eine Subshell startet mit Default-Handlern für alle Signale; auch ein trap '' SIGINT aus dem Eltern-Skript schuetzt die Subshell nicht. Wer das will, muss den Trap explizit in der Subshell erneut setzen oder set -E (errtrace) zumindest für ERR-Traps verwenden.
Trap-Handler überschreibt $? — wenn man nicht aufpasst
Innerhalb eines Trap-Handlers ist $? zunaechst der Exit-Code des Befehls, der den Trap ausgeloest hat. Sobald aber im Handler ein Befehl läuft, wird $? überschrieben. Wer den urspruenglichen Code später braucht, sichert ihn als allererste Aktion: local rc=$? direkt am Funktionsanfang. Erst danach folgen rm, kill, Logging — und am Ende exit "$rc", damit der Aufrufer den korrekten Status sieht.
Weiterfuehrende Ressourcen
Externe Quellen
- man 7 signal (man7.org) — Vollstaendige Referenz zu allen Signalen, Default-Aktionen und Architektur-Abhaengigkeiten
- man 7 signal-safety (man7.org) — Liste der async-signal-safe Funktionen, Pflicht-Lektuere für Handler in C
- man 1 kill (man7.org) — Manpage zum
kill-Befehl mit allen Optionen - GNU C Library: Signal Handling — Signal-API der glibc mit
signal()undsigaction() - POSIX: Signal Concepts — POSIX-Definition der Signale, Standardisierungs-Grundlage
Verwandte Artikel
- kill und Prozesse beenden —
kill,pkill,killallund die Eskalationsstrategie von TERM zu KILL - Prozess-Modell — PID, fork/exec und SIGCHLD als Grundlage der Prozess-Verwaltung
- Jobs, fg und bg — Wie SIGTSTP, SIGCONT und SIGHUP die Job-Steuerung der Shell antreiben
- Fehlerbehandlung in Bash —
trapim Detail mit EXIT, ERR und Cleanup-Patterns - nohup und disown — Prozesse vor SIGHUP schuetzen, wenn das Terminal schliesst