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.

SignalNr.DefaultBedeutung
SIGHUP1TermHangup — Terminal geschlossen, häufig als Reload-Konvention für Dienste
SIGINT2TermInterrupt vom Terminal, Strg+C
SIGQUIT3CoreQuit vom Terminal, Strg+, erzeugt Coredump
SIGILL4CoreIllegal Instruction — CPU traf auf einen unbekannten Opcode
SIGABRT6CoreAbort — typischerweise von abort() oder assert()
SIGFPE8CoreFloating Point Exception, oft Division durch Null
SIGKILL9TermSofortiger Kill — kann NICHT abgefangen oder ignoriert werden
SIGUSR110TermFrei für die Anwendung, beliebig belegbar
SIGSEGV11CoreSegmentation Fault — ungültiger Speicherzugriff
SIGUSR212TermFrei für die Anwendung, beliebig belegbar
SIGPIPE13TermSchreiben in eine Pipe ohne Leser
SIGALRM14TermTimer-Ablauf nach alarm()
SIGTERM15TermDefault-Signal von kill — höfliche Aufforderung zum Beenden
SIGCHLD17IgnKindprozess hat sich beendet oder geändert
SIGCONT18ContFortsetzen eines gestoppten Prozesses
SIGSTOP19StopAnhalten — kann NICHT abgefangen oder ignoriert werden
SIGTSTP20StopStop vom Terminal, Strg+Z
SIGTTIN21StopHintergrundprozess versucht von Terminal zu lesen
SIGTTOU22StopHintergrundprozess versucht auf Terminal zu schreiben
SIGWINCH28IgnTerminal-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.

DefaultAktion
TermProzess wird sofort beendet, kein Coredump
CoreProzess wird beendet und ein Coredump geschrieben (sofern erlaubt)
IgnSignal wird ignoriert, der Prozess läuft weiter
StopProzess wird angehalten, bis er SIGCONT empfängt
ContEin 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.

Bash Alle Signale auflisten
kill -l
Output
 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) SIGRTMAX

kill -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.

Bash Eigener Handler für SIGINT und SIGTERM
#!/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; done

Das 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.

Bash Signale aus der Shell senden
kill -TERM 12345
kill -HUP $(pidof nginx)
kill -USR1 $$
kill -0 12345

kill -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

Bash nginx neu konfigurieren ohne Verbindungsabbruch
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

Bash Prozess vorruebergehend einfrieren
kill -STOP 12345
sleep 60
kill -CONT 12345

SIGSTOP 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

Bash Diagnose-Coredump eines laufenden Prozesses
kill -ABRT 12345

Bei 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

Bash Reload-Konvention selbst implementieren
#!/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
done

SIGUSR1 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

Bash Temp-Dir mit garantiertem Aufraeumen
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

Bash Auf Terminal-Groessenaenderung reagieren
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; done

SIGWINCH 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

/ Weiter

Zurück zu Prozesse & Jobs

Zur Übersicht