Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 7 Dämonen, Zombies und Prozesse
  gp 7.1 Was ist ein Prozess?
  gp 7.2 Prozesskomponente
    gp 7.2.1 Prozessnummer (PID)
    gp 7.2.2 Prozessnummer des Vaterprozesses (PPID)
    gp 7.2.3 Benutzer- und Gruppennummer eines Prozesses (UID, EUID, GID, EGID)
    gp 7.2.4 Prozessstatus
    gp 7.2.5 Prozesspriorität
    gp 7.2.6 Timesharing-Prozesse
    gp 7.2.7 Prozessauslagerung
    gp 7.2.8 Steuerterminal
  gp 7.3 Prozesse überwachen – ps, top, kpm
  gp 7.4 Lebenszyklus eines Prozesses
  gp 7.5 Umgebungsvariablen eines Prozesses
    gp 7.5.1 Einzelne Umgebungsvariablen abfragen
    gp 7.5.2 Umgebungsvariable verändern oder hinzufügen – putenv() und setenv()
    gp 7.5.3 Löschen von Umgebungsvariablen – unsetenv() und clearenv()
  gp 7.6 Ressourcenlimits eines Prozesses
    gp 7.6.1 Mehr Sicherheit mit Ressourcenlimits
  gp 7.7 Prozesserkennung
  gp 7.8 Erzeugung von Prozessen – fork()
    gp 7.8.1 Pufferung
    gp 7.8.2 Was wird vererbt und was nicht?
    gp 7.8.3 Einen Prozess mit veränderter Priorität erzeugen
  gp 7.9 Warten auf einen Prozess
  gp 7.10 Die exec-Familie
    gp 7.10.1 execl()
    gp 7.10.2 execve()
    gp 7.10.3 execv()
    gp 7.10.4 execle()
    gp 7.10.5 execlp()
    gp 7.10.6 execvp()
    gp 7.10.7 Kindprozesse mit exec-Aufruf überlagern
  gp 7.11 Kommandoaufrufe aus dem Programm – system()
  gp 7.12 Dämonprozesse
    gp 7.12.1 Wie ein Prozess zum Dämon wird ...
    gp 7.12.2 Dämon, sprich mit uns ...
    gp 7.12.3 Protokollieren von Dämonen – syslog()
    gp 7.12.4 syslog() in der Praxis
    gp 7.12.5 Den Dämon, den ich rief ...
  gp 7.13 Rund um die Ausführung von Prozessen
    gp 7.13.1 Einen Dämon beim Booten mit einem init-Skript starten
    gp 7.13.2 Hintergrundprozesse und Jobkontrolle
    gp 7.13.3 Prozesse zeitgesteuert ausführen (cron-Jobs)
  gp 7.14 Zusammenfassung und Ausblick


Rheinwerk Computing

7.9 Warten auf einen Prozess  toptop

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.


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 7.1    Elternprozess beendet sich vor dem Kindprozess.


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 ---
 << zurück
  
  Zum Katalog
Zum Katalog: Linux-UNIX-Programmierung
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Linux-Server






 Linux-Server


Zum Katalog: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Katalog: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Katalog: Shell-Programmierung






 Shell-
 Programmierung


Zum Katalog: Linux Handbuch






 Linux Handbuch


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de