Eine Sekunde warten ohne Race-Condition
Disclaimer: Dieser Thread wurde aus dem alten Forum importiert. Daher werden eventuell nicht alle Formatierungen richtig angezeigt. Der ursprüngliche Thread beginnt im zweiten Post dieses Threads.
Disclaimer: Dieser Thread wurde aus dem alten Forum importiert. Daher werden eventuell nicht alle Formatierungen richtig angezeigt. Der ursprüngliche Thread beginnt im zweiten Post dieses Threads.
Rush, Teilaufgabe nuke
Bei der Teilaufgabe zu nuke soll ja nach dem kill() höchstens eine Sekunde lang auf ein eintreffendes SIGCHLD gewartet werden. In der Aufgabe wird dafür sleep() empfohlen. Das Warten mit sleep() ist aber meiner Meinung nach immer mit einer Race-Condition verbunden: Grundsätzlich kann das SIGCHLD VOR dem Aufruf von sleep() eintreffen. Dann wartet sleep() unbedingt eine Sekunde, obwohl der Kindprozess längst beendet ist.
Schöner wäre es, wenn wir – wie beim Warten auf Vordergrundprozesse – die Funktionalität von sigsuspend() verwenden könnten: Wir packen kill() und sigsuspend() in einen kritischen Abschnitt und sorgen so dafür, dass kein SIGCHLD verloren gehen kann. Das erste SIGCHLD, das nach dem Aufruf von kill() entsteht, wird garantiert von sigsuspend() abgearbeitet, egal an welcher Stelle im Code wir gerade sind, wenn das Signal eintrifft. Damit haben wir die oben beschriebene Race-Condition effektiv vermieden.
Leider unterstützt sigsuspend() keinen Timeout-Parameter, mit dem wir sagen könnten, dass höchstens eine gewisse Zeit auf ein Signal gewartet werden soll. Stattdessen können wir aber auf ein einfaches Hilfsmittel zurückgreifen: Alarme. Ein Alarm ist eine kurze Notiz an das OS, dass es uns nach einer bestimmten Zeit ein Signal vom Typ SIGALRM zustellen soll.
Die Idee ist nun, nach kill() einen 1s-Alarm zu setzen und dann mit sigsuspend() auf SIGCHLD und SIGALRM zu warten, je nach dem welches der beiden Signale früher eintritt.
Wenn wir alles richtig gemacht haben, dann MUSS innerhalb einer Sekunde mindestens eines der beiden Signale eintreffen. Und weil sich sigsuspend() ja beim ersten eingetroffenen Signal selbst beendet, haben wir gemäß der Aufgabe “maximal eine Sekunde” gewartet.
Damit sigsuspend() richtig funktioniert, müssen alle Aufrufe (kill(), alarm(), sigsuspend()) innerhalb eines kritischen Abschnitts stehen (mit SIGCHLD und SIGALRM als Maske). Nur so können wir sicher stellen, dass keines der beiden Signale verloren geht:
sigsuspend() beendet den kritischen Abschnitt, wartet auf eines der beiden Signale, und betritt – sobald ein Signal eingetroffen ist – sofort wieder den kritischen Abschnitt. Das alles passiert garantiert atomar, sodass es zu keiner Race-Condition vor dem Aufruf von sigsuspend() kommen kann, weil sämtliche Signale, die vor sigsuspend() entstehen, bis zum Aufruf von sigsuspend() “aufgehoben” werden.
(Das ist übrigens analog zu wait() in Java. PFP lässt grüßen :p)
Damit lässt sich die Idee wie folgt umsetzen:
In Pseudo-Code:
[li]kritischen Abschnitt betreten: SIGCHLD und SIGALRM blockieren[/li]
[li]SIGTERM senden (eigentliche Aufgabe von nuke)[/li]
[li]Alarm in einer Sekunde anfordern[/li]
[li]sigsuspend() mit SIGCHLD und SIGALRM aufrufen[/li]
[li]kritischen Abschnitt verlassen[/li]
Und in C:
[code=c]///// Am Anfang des Programms:
// Wir koennen weder den Default-Handler von SIGALRM gebrauchen (der bricht das
// Programm naemlich ab), noch duerfen wir es ignorieren, weil sonst
// sigsuspend() nicht mehr funktioniert.
// Deswegen leeren Handler einrichten:
void emptyhandler(int s) {}
struct sigaction act;
act.sa_handler = emptyhandler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART;
sigaction(SIGALRM, &act, NULL);
///// nuke:
// Kritischen Abschnitt betreten:
sigset_t old_mask;
sigset_t new_mask;
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGCHLD);
sigaddset(&new_mask, SIGALRM);
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);
// SIGTERM senden:
kill(pid, SIGTERM);
// Alarm anfordern:
alarm(1);
// Warten:
sigset_t wait_mask;
sigfillset(&wait_mask);
sigdelset(&wait_mask, SIGCHLD);
sigdelset(&wait_mask, SIGALRM);
sigsuspend(&wait_mask);
// Kritischen Abschnitt beenden:
sigprocmask(SIG_SETMASK, &old_mask, NULL);[/code]
Wollt ich nur kurz hier loswerden, weil wir das in unserer Abgabe nicht richtig gebacken bekommen haben. Und es ist auch nicht wesentlich komplizierter oder länger als die Variante mit sleep().
Das ist leider in der Tat ein Bug in der Aufgabenstellung.
Für die SP1+2-Leute, die die [m]rush[/m] seit dieser Woche bearbeiten, haben wir die Aufgabenstellung bereits angepasst. Für die SP2-Leute tun wir bei der Korrektur einfach so, als gebe es dieses Nebenläufigkeitsproblem nicht. Es ist glücklicherweise auch kein schlimmes Problem: Im Extremfall warten wir eben eine Sekunde zu lang, aber es kann nichts kaputtgehen.
Dein Lösungsvorschlag ist durchaus elegant.
Eine minimale Anmerkung hätte ich allerdings: Du solltest beim Einrichten der Signalbehandlung das Flag [m]SA_RESTART[/m] angeben. Sonst schmieren dir blockierende Systemaufrufe wie [m]waitpid()[/m] (oder auch Funktionen wie [m]fgets()[/m], was intern ein [m]read()[/m] macht) ab, wenn unerwartet ein [m]SIGALRM[/m] kommt.
Stimmt. An der Stelle war ich mir nicht ganz sicher. Hatte immerhin ne 50:50-Chance, das Flag richtig zu setzen ;).
Hab’s im Code entsprechend geändert.