7.9 Warten auf einen Prozess
Wenn es nötig ist, auf die Beendigung eines Prozesses zu warten, z. B. einer Server-Client-Anwendung, dann stehen zu diesem Zweck die beiden Funktionen wait() und waitpid() zur Verfügung. Beendet sich der Elternprozess vor dem Kindprozess, entsteht ein »verwaister« Kindprozess. Dessen nimmt sich der init-Prozess an, der (Urgroß-)Vater aller Prozesse. Hier ein Beispiel, worauf ich hinauswill:
/* waise.c */
#include <unistd. h.>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main (void) {
pid_t pid;
switch (pid = fork ()) {
case -1:
printf ("Fehler bei fork()\n");
break;
case 0:
printf ("--- Im Kindprozess (%d) ---\n",getpid());
printf("Elternprozess : %d\n", getppid());
sleep(2);
printf ("--- Im Kindprozess (%d) ---\n",getpid());
printf("Elternprozess : %d\n", getppid());
break;
default:
printf ("--- Im Elternprozess (%d) ---\n",getpid());
sleep(1);
printf(" --- Elternprozess endet vorzeitig ---\n");
exit(0); /* Sofort beenden */
}
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o waise waise.c
$ ./waise
--- Im Kindprozess (3356) ---
Elternprozess : 3355
--- Im Elternprozess (3355) ---
--- Elternprozess endet vorzeitig ---
--- Im Kindprozess (3356) ---
Elternprozess : 1
Die Ausgabe zeigt, wie nach vorzeitiger Beendigung des Elternprozesses init (PPID = 1) der neue Elternprozess geworden ist.
Anders hingegen sieht die Sache schon aus, wenn sich der Kindprozess verabschiedet, ohne dass der Elternprozess davon in Kenntnis gesetzt wurde. Ein Beispiel zu diesem Fall:
/* zombie.c */
#include <unistd. h.>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main (void) {
pid_t pid;
switch (pid = fork ()) {
case -1:
printf ("Fehler bei fork()\n");
break;
case 0:
printf("--- Im Kindprozess (%d) ---\n",getpid());
printf("Elternprozess : %d\n", getppid());
printf("--- Kindprozess beendet sich ---\n");
exit(0);
default:
printf("--- Im Elternprozess (%d) ---\n",getpid());
printf("--- Beenden mit <STRG>+<C> ---\n");
while(1); /* Endlosschleife */
}
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o zombie zombie.c
$ ./zombie
--- Im Kindprozess (3457) ---
Elternprozess : 3456
--- Kindprozess beendet sich ---
--- Im Elternprozess (3456) ---
--- Beenden mit <STRG>+<C> ---
Öffnen Sie nun eine weitere Konsole, und lassen Sie sich die laufenden Prozesse mit ps bzw. in einer X-Konsole mit ps x anzeigen:
$ ps f
...
PID TTY STAT COMMAND
3456 pts/2 R 0:04 ./zombie
3457 pts/2 Z 0:00 \_ [zombie] <defunct>
3458 pts/1 R 0:00 ps
...
Der Kindprozess, der sich beendet hat, ohne dass der Elternprozess sich bereits darum gekümmert hat, wird als Zombie bezeichnet. Der Zustand wird hier bei ps mit Z angezeigt. Bei den verwaisten Prozessen, die zuvor besprochen wurden, übernahm init die Aufgabe, die Sie jetzt in solch einem Fall selbst übernehmen müssen. Wenn Sie schon Kinder auf die Welt bringen, müssen Sie auch dafür geradestehen. Init macht nämlich nichts anderes mit dem Waisenprozess, als eine wait()-Funktion aufzurufen, um auf den Beendigungsstatus zu warten.
Hierzu die Syntax der beiden Funktionen, womit Sie auf die Beendigung des Kindprozesses warten können:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait( int *status );
pid_t waitpid( pid_t pid, int *status, int optionen );
Die Funktion wait() wartet, bis sich ein Kindprozess beendet, und gibt dessen PID als Rückgabewert zurück. Bei Fehler wird -1 zurückgegeben. Die Funktion waitpid() hingegen wartet auf einen bestimmten Kindprozess mit der in pid als erstes Argument angegebenen Prozess-ID. Sofern beim Argument status nicht der NULL-Zeiger angegeben wurde, wird hier die Statusinformation zu dem beendeten Prozess gespeichert. Um diesen Status auszuwerten, sind in der Headerdatei <sys/wait.h> folgende Makros definiert:
Tabelle 7.6
Makros in <sys/wait.h>
Makro
|
Bedeutung
|
WIFEXITED( status )
|
Ist TRUE, wenn sich ein Kindprozess normal beendet hat. Wollen Sie dabei den genauen Rückgabewert vom Kindprozess erfragen, dann können Sie dies mit dem Makro WEXITSTATUS(status) abfragen.
|
WIFSIGNALED( status )
|
Ist TRUE, wenn der Kindprozess durch ein Signal terminiert wurde, das dieser nicht abgefangen hat.
|
WTERMSIG( status )
|
Damit können Sie die Nummer des Signals erfahren, die den Prozessabbruch bewirkt hat.
|
WCOREDUMP( status )
|
In BSD und SVR4 können Sie damit erfahren, ob das Signal, das den Kindprozess abgebrochen hat, eine Core-Datei angelegt hat.
|
WIFSTOPPED( status )
|
Ist TRUE, wenn ein Kindprozess angehalten wurde.
|
WSTOPSIG( status )
|
Die Nummer des Signals, die den Kindprozess gestoppt hat.
|
Um den Status auszuwerten, schreibt man am besten extra eine Funktion dazu. Hierzu ein Beispiel mit der Funktion wait(). Es wird ein Kindprozess erzeugt und abgefragt, wie dieser beendet werden soll (normal oder anormal). Der Elternprozess wartet in der Zwischenzeit auf den Kindprozess mit wait(). Sobald sich der Kindprozess beendet, fährt auch der Elternprozess mit der Auswertung des Status über die Beendigung des Kindprozesses mit der selbst geschriebenen Funktion child_status() fort. Auf den Status WCOREDUMP() wurde aus Portabilitätsgründen verzichtet. Sofern auf Ihrem System vorhanden, können Sie dies gerne selbst nachholen. Hier das Beispiel dazu:
/* wait.c */
#include <unistd. h.>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
static void child_status (int status) {
if (WIFEXITED (status)) {
printf ("Kind normal beendet mit Rückgabewert %d\n",
WEXITSTATUS(status));
}
else if (WIFSIGNALED (status)) {
printf ("Kind mit Signalnummer %d beendet\n",
WTERMSIG(status));
/* WCOREDUMP könnte hier stehen */
}
else if (WIFSTOPPED (status)) {
printf ("Kind wurde angehalten mit Signalnummer %d\n",
WSTOPSIG(status));
/* I.d.R. wird dies SIGSTOP sein, aber es gibt */
/* ja auch noch das ptrace()-Interface. */
}
}
int main (void) {
pid_t pid;
int status;
int end;
switch (pid = fork ()) {
case -1:
perror("fork()");
return EXIT_FAILURE;
case 0:
printf (" --- Im Kindprozess ---\n");
printf ("Wie wollen Sie den Prozess beenden?\n\n");
printf ("- 1 - WIFEXITED (normal)\n");
printf ("- 2 - WIFSIGNALED (anormal)\n");
printf ("Ihre Auswahl : ");
scanf ("%d", &end);
if (end == 1)
exit (66);
else if (end == 2)
abort (); /* Entspricht kill(0, SIGABRT); */
else
printf ("Was den nun? ->%d<- ???\n", end);
break; /* Normale Beendigung mit 0 */
default: /* Elternprozess wartet auf Kind mit pid */
if (wait (&status) != pid) {
perror("wait()");
return EXIT_FAILURE;
}
/* Status vom Kind auswerten */
child_status (status);
}
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o wait wait.c
$ ./wait
--- Im Kindprozess ---
Wie wollen Sie den Prozess beenden?
- 1 - WIFEXITED (normal)
- 2 - WIFSIGNALED (abnormal)
Ihre Auswahl : 1
Kindprozess normal beendet mit Rückgabewert 66
$ ./wait
--- Im Kindprozess ---
Wie wollen Sie den Prozess beenden?
- 1 - WIFEXITED (normal)
- 2 - WIFSIGNALED (abnormal)
Ihre Auswahl : 2
Kindprozess mit Signalnummer 6 beendet
Zu den Signalen erfahren Sie in Kapitel 6 noch mehr. Also erspare ich mir hierzu erst einmal eine Erläuterung.
Mit wait() können Sie allerdings nur auf die Beendigung des nächsten Prozesses warten. Wollen Sie hingegen auf die Beendigung eines bestimmten Prozesses warten, dann ist waitpid() Ihr Fall. Mit dem ersten Argument von waitpid() legen Sie fest, worauf Sie warten wollen. Dazu haben Sie vier Möglichkeiten:
Tabelle 7.7
Mögliche Angaben für das erste Argument von waitpid()
pid
|
Bedeutung
|
–1
|
Auf beliebigen Prozess warten – äquivalent zu wait().
|
pid
|
Auf die Beendigung von pid warten.
|
0
|
Auf Beendigung warten, deren Prozessgruppen-ID gleich der Prozessgruppen-ID des aufrufenden Prozesses ist.
|
< – 1
|
Auf Beendigung warten, deren Prozessgruppen-ID dem absoluten Wert von pid entspricht.
|
Des Weiteren können Sie über das dritte Argument, optionen, das Verhalten von waitpid() beeinflussen. Folgende Konstanten können dabei verwendet werden:
Tabelle 7.8
Zusätzliche Optionen für das dritte Argument von waitpid()
Konstante
|
Beschreibung
|
WNOHANG
|
Der aufrufende Prozess wird nicht blockiert, wenn der Prozess »pid« noch nicht beendet wurde bzw. noch nicht im Zombie-Status ist. waitpid() liefert in diesem Fall 0 zurück.
|
WUNTRACED
|
Status des angehaltenen Kindprozesses, der mit pid spezifiziert wurde. Das Makro WIFSTOPPED liefert 1 zurück, wenn es sich beim Rückgabewert um die PID des angehaltenen Kindprozesses handelt.
|
Unter SVR4 gibt es außerdem noch die Optionen WNOWAIT und WCONTIUED, die hier nicht besprochen werden. Geben Sie hingegen 0 an, wird keine Option gesetzt.
Das folgende Listing zu waitpid() zeigt die Funktion mit der Option WNOHANG im Einsatz. Diese Option sorgt dafür, dass der Elternprozess nicht, wie z. B. mit wait(), zwingend auf die Beendigung des Kindprozesses warten muss. Somit können Sie im Elternprozess ebenfalls weitere Anweisungen ausführen.
/* waitpid.c */
#include <unistd. h.>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main (void) {
pid_t pid;
switch (pid = fork ()) {
case -1:
perror("fork()");
return EXIT_FAILURE;
case 0:
printf (" --- Im Kindprozess ---\n");
sleep (5);
printf(" --- Kindprozess ist fertig ---\n");
break;
default:
if (waitpid (pid, NULL, WNOHANG) != 0) {
perror("waitpid()");
return EXIT_FAILURE;
}
// Hier könnte weiterer Code ausgeführt werden
printf (" --- Elternprozess blockiert nicht ---\n");
}
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o waitpid waitpid.c
$ ./waitpid
--- Im Kindprozess ---
--- Elternprozess blockiert nicht ---
--- Kindprozess ist fertig ---
|