Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

Signale in C unter Linux: Interprozesskommunikation mit POSIX-Signalen verstehen (CPE2600 Lab 10)

Lerne in diesem Tutorial, wie POSIX-Signale in C funktionieren, wie man Signalhandler registriert, Signale sendet und mit sigaction detailreiche Informationen empfängt – mit praktischen Beispielen für CPE2600 Lab 10.

POSIX Signale C Signalhandler Linux CPE2600 Lab 10 Signals Interprozesskommunikation Signale kill Systemaufruf C sigaction Beispiel SIGINT SIGTERM SIGKILL Signale senden Linux Signal-Dispositionen SIGSEGV Handler SIGALRM alarm C Programmierung Signale Linux Signalbehandlung Tutorial Signalhandler registrieren siginfo_t Struktur asynchrone Benachrichtigung C

Einführung in POSIX-Signale unter Linux

Signale sind ein fundamentales Konzept der Interprozesskommunikation (IPC) in Unix-ähnlichen Betriebssystemen. Sie ermöglichen es, asynchrone Benachrichtigungen zwischen Prozessen oder vom Kernel an einen Prozess zu senden. Im Rahmen des CPE2600 Lab 10 – Signals lernst du, wie du Signalhandler registrierst, Signale sendest und auf empfangene Signale reagierst. Dieses Tutorial führt dich Schritt für Schritt durch die wichtigsten Konzepte und zeigt dir, wie du die Aufgaben des Labs selbstständig lösen kannst.

Stell dir vor, du spielst ein beliebtes Online-Spiel wie Fortnite oder League of Legends. Plötzlich erscheint ein Benachrichtigungssymbol – dein Team fordert dich zu einer Besprechung auf. Das ist wie ein Signal: Es unterbricht deine aktuelle Aktion, und du kannst entscheiden, wie du reagierst. In der Programmierung funktionieren Signale ähnlich: Sie unterbrechen den normalen Programmablauf und rufen eine speziell definierte Funktion auf – den Signalhandler.

Was ist ein Signal-Disposition?

Die Signal-Disposition (Signalverhalten) legt fest, was passiert, wenn ein Prozess ein Signal empfängt. Jedes Signal hat eine Standard-Disposition, die vom Betriebssystem vorgegeben wird. Es gibt fünf mögliche Standard-Dispositionen:

  • Terminate – Der Prozess wird beendet.
  • Ignore – Das Signal wird ignoriert.
  • Core – Der Prozess wird beendet und ein Core-Dump erstellt.
  • Stop – Der Prozess wird angehalten.
  • Continue – Ein angehaltener Prozess wird fortgesetzt.

Ein Signalhandler ist eine benutzerdefinierte Funktion, die aufgerufen wird, wenn ein bestimmtes Signal eintrifft. Damit kannst du das Standardverhalten überschreiben (außer bei SIGKILL und SIGSTOP).

Signale senden: kill-Systemaufruf und Tastenkombinationen

Um ein Signal programmatisch zu senden, verwendest du den kill()-Systemaufruf. Die Syntax ist:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

Beispiel: Sende SIGUSR1 an einen Prozess mit PID 1234:

kill(1234, SIGUSR1);

Von der Kommandozeile aus kannst du Signale mit dem kill-Befehl senden:

kill -SIGUSR1 1234

Viele Signale lassen sich auch über Tastenkombinationen auslösen, z. B. Strg+C für SIGINT oder Strg+Z für SIGTSTP.

Wichtige POSIX-Signale im Überblick

SIGINT (Signal 2)

Name: Interrupt vom Terminal. Standard-Disposition: Terminate. Kann überschrieben werden: Ja. Wird durch Drücken von Strg+C ausgelöst. In vielen Spielen wird SIGINT abgefangen, um eine „Bist du sicher, dass du beenden willst?“-Abfrage zu zeigen.

SIGTERM (Signal 15)

Name: Termination. Standard-Disposition: Terminate. Kann überschrieben werden: Ja. Dies ist der Standardbefehl zum Beenden eines Prozesses (kill ohne Signal sendet SIGTERM). Prozesse können auf SIGTERM reagieren, um sauber herunterzufahren, z. B. Daten zu speichern.

SIGUSR1 (Signal 10)

Name: Benutzerdefiniertes Signal 1. Standard-Disposition: Terminate. Kann überschrieben werden: Ja. Wird oft für anwendungsspezifische Zwecke verwendet, z. B. um einen Server anzuweisen, Logs zu rotieren.

SIGKILL (Signal 9)

Name: Kill (sofort beenden). Standard-Disposition: Terminate. Kann nicht überschrieben werden: Nein. Dieses Signal kann nicht abgefangen oder ignoriert werden – es beendet den Prozess sofort. Es ist die letzte Rettung, wenn ein Prozess nicht auf SIGTERM reagiert.

SIGSTOP (Signal 19)

Name: Stop (anhalten). Standard-Disposition: Stop. Kann nicht überschrieben werden: Nein. Ähnlich wie SIGKILL kann SIGSTOP nicht abgefangen werden; der Prozess wird sofort angehalten. Mit SIGCONT kann er fortgesetzt werden.

Teil 2: Arbeiten mit einem Signalhandler

Die bereitgestellte Datei signal_handler.c enthält ein Programm, das einen Handler für SIGINT registriert. Kompiliere und führe es aus. Um SIGINT zu senden, kannst du entweder Strg+C drücken oder den kill-Befehl verwenden:

kill -SIGINT <PID>

Beobachte, dass der Handler aufgerufen wird und das Programm danach beendet (weil im Handler exit() aufgerufen wird). Entferne nun den exit()-Aufruf aus dem Handler. Nach dem Empfang von SIGINT kehrt das Programm zur normalen Ausführung zurück – es läuft einfach weiter. Um das Programm dennoch zu beenden, kannst du SIGKILL senden:

kill -SIGKILL <PID>

Aktualisiere die Kommentare und committe deine Änderungen.

Teil 3: Signale vom Betriebssystem

SIGALRM und die alarm()-Funktion

Der alarm()-Systemaufruf plant die Zustellung von SIGALRM nach einer bestimmten Anzahl von Sekunden. Schreibe ein Programm signal_alarm.c, das einen Alarm nach 5 Sekunden auslöst und einen Handler verwendet, der eine Nachricht ausgibt:

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

void handler(int sig) {
    printf("Alarm empfangen!\n");
}

int main() {
    signal(SIGALRM, handler);
    alarm(5);
    printf("Warte auf Alarm...\n");
    pause(); // Warte auf Signal
    return 0;
}

SIGSEGV – Segmentation Fault

Ein Nullzeiger-Dereferenzierung löst SIGSEGV aus. Standardmäßig wird der Prozess beendet. Du kannst jedoch einen Handler installieren, der eine Nachricht ausgibt. Achtung: Wenn der Handler einfach zurückkehrt, wird die fehlerhafte Anweisung erneut ausgeführt – Endlosschleife! Beobachte dieses Verhalten in der modifizierten signal_segfault.c.

Teil 4: Detailinformationen mit sigaction

Der signal()-Systemaufruf liefert nur die Signalnummer. Mit sigaction() erhältst du eine siginfo_t-Struktur mit zusätzlichen Details wie der PID des Senders. Beispiel für signal_sigaction.c:

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

void handler(int sig, siginfo_t *info, void *context) {
    printf("Signal %d empfangen von PID %d\n", sig, info->si_pid);
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = handler;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGUSR1, &sa, NULL);
    printf("PID: %d\n", getpid());
    pause();
    return 0;
}

Dieses Programm gibt die PID des Senders aus, wenn es SIGUSR1 empfängt. Ein typischer Anwendungsfall ist ein Server, der von einem Client ein Signal erhält und die Sender-PID protokolliert.

Zusammenfassung und nächste Schritte

In diesem Tutorial hast du die Grundlagen von POSIX-Signalen in C kennengelernt: Signal-Dispositionen, das Senden von Signalen mit kill() und Tastenkombinationen, die wichtigsten Signale (SIGINT, SIGTERM, SIGUSR1, SIGKILL, SIGSTOP) und wie man Signalhandler mit signal() und sigaction() registriert. Diese Kenntnisse sind essenziell für das CPE2600 Lab 10 und helfen dir, asynchrone Ereignisse in Linux-Programmen zuverlässig zu behandeln.

Wenn du mehr über fortgeschrittene Themen wie signal-sichere Funktionen oder das Blockieren von Signalen erfahren möchtest, wirf einen Blick in die Manpages (man 7 signal) oder in die Quellen des Linux-Kernels. Viel Erfolg bei deinem Labor!