SUCHE MIT Google
Web virtualuniversity.ch
HOME DIDAKTIK ECDL ELEKTRONIK GUIDES HR MANAGEMENT MATHEMATIK SOFTWARE TELEKOM
DIENSTE
Anmeldung
Newsletter abonnieren
Sag's einem Freund!
VirtualUniversity als Startseite
Zu den Favoriten hinzufügen
Feedback Formular
e-Learning für Lehrer
Spenden
Autoren login
KURSE SUCHEN
Kurse veröffentlichen

Suche nach Datum:

Suche mit Schlüsselwort:

Suche nach Land:

Suche nach Kategorie:
PARTNER
ausbildung24.ch - Ausbildungsportal, Seminare, Kursen... 

 
HTMLopen.de - Alles was ein Webmaster braucht

 
PCopen.de - PC LAN Netze und Netzwerke - alles was ein IT Profi und Systemtechnicker braucht

TELEKOM

Parallelität und Signale

Parallelität

Ein Server wird im allgemeinen in einer Multitasking-Umgebung gestartet werden. Er soll schließlich mehrere Anfragen parallel abarbeiten können(concurrent server). Unter UNIX gibt es dazu den fork-Mechanismus.
    New processes are created by other processes, just like new humans. New humans are created by other humans, of course, not by processes. (Unix System Administration Handbook)

Die Systemaufrufe fork(), exec() und wait() haben mit der Generierung von Kindprozessen zu tun und erlauben die Synchronisation zwischen Eltern- und Kindprozessen. An dieser Stelle wird nur soweit darauf eingegangen, wie es zum Verständnis der folgenden Abschnitte nötig ist.

  • fork() erzeugt einen Kindprozeß,der ein vollständiges Abbild des Elternprozesses ist und der beimgleichen Stand des Befehlszählers fortgesetzt wird. Eltern- und Kindprozeßwird jedoch die Möglichkeit geboten, festzustellen, ob es sich umEltern- oder Kindprozeß handelt: Der Kindprozeß bekommt alsRückgabewert 0, der Elternprozeß die PID (Prozeß-ID) des Kindprozesses.
    • Etliche Systemprozesse schließen stdin, stdout und stderr, tretenalso in den Hintergrund. Solche Prozesse nennt man Daemonprozesse.
    • Terminiert der Elternprozeß vor dem Kindprozeß, wird dieserzum 'Waisenkind'. Normalerweise wird er dann vom Init-Prozeß 'adoptiert'.
    • Hat der Kindprozeß dann auch noch den Kontakt zum Terminal (Standardausgabe und -eingabe) verloren wird er zum 'Zombie'.
  • wait() ermöglicht dem Elternprozeß das Warten auf die Beendigung des/der Kindprozess(e). Der Elternprozeß wird verdrängt und erst durch das Ende eines Kindprozesses wieder "aufgeweckt". Zur Unterscheidung mehrerer Kindprozesse liefert die Funktion wait() die PID des "gestorbenen" Kindprozesses zurück. Gibt es keinen Kindprozeß, ist das Ergebnis -1.
  • Bei exec() wird der ursprüngliche Prozeß durch einen neuen Prozeß ersetzt (eine Rückkehr zum aufrufenden Prozeß ist daher logischerweise nicht möglich). exec() ist der komplizierteste Aufruf, da der komplette Prozeßadreßraum ersetzt werden muß.
Dazu ein erstes Beispiel in Perl:
                                       #!/usr/bin/perl -w
                                       
                                       # Kindprozess starten
                                       $chld_pid=fork();
                                       
                                       if ($chld_pid < 0)
                                         {
                                         die "Fork fehlgeschlagen: $!\n";
                                         }
                                       
                                       if($chld_pid == 0)          # I am the child.
                                         {
                                         print "CHILD: Here I am.\n";
                                         sleep 1;
                                         print "CHILD: terminating.\n";
                                         exit(1); # Exit-Status 1
                                         }
                                       
                                       else                       # I am the parent
                                         {
                                         print "PARENT: Kind wurde erzeugt. Warte...\n";
                                         waitpid($chld_pid,0);
                                         print "PARENT: Kind terminiert. Exit status: $?\n";
                                         }
                                       
waitpid wartet darauf, daß der Kind-Prozeß mit der angegebenen Prozeß-ID terminiert und nimmt den Rückgabewert entgegen (in Perl in der Variablen $?). Die Null ist ein Flag-Byte; hier kann man angeben, ob waitpid auch für gestoppte Kindprozesse zurückkehren soll, oder ob waitpid einfach nur nachsehen soll, ob der Kindprozeß mit der angegebenen PID terminiert hat, ohne zu warten, falls das nicht der Fall war. Daneben gibt's noch die Funktion wait, die wartet, bis irgend ein Kindprozeß terminiert, und dann dessen PID zurückgibt.

In welchem Teil sich das Programm befinden, kann wir anhand des Rückgabewerts von fork() festgestellt werden. Beim Kindprozeß ist dieser Null, beim Elternprozeß die PID des Kind-Prozesses. Beide Prozesse haben zunächst denselben Eingabe- und Ausgabekanal, und teilen sich auch alle anderen Filedeskriptoren. Wenn sie nun beide wahllos auf den Ausgabekanal schreiben, werden die beiden Ausgaben einfach durcheinandergemischt. Wenn sie beide von der Eingabe lesen, gewinnt der Schnellere, wenn eine neue Eingabe ansteht. Um nun wirklich kommunizieren zu können, müssen vor dem fork() ein Paar (oder auch mehrere) von zusätzlichen Kanälen geschaffen werde, von denen einer benutzt wird, damit der Elternprozeß Daten an den Kindprozeß senden kann, und ein anderer, damit der Kindprozeß Daten an den Elternprozeß senden kann. Es gibt hierfür zwei verschiedene Systemfunktionen. Die erste heißt pipe() und erzeugt ein Paar von zusammengehörigen Filedeskriptoren, wobei auf dem ersten gelesen und auf dem zweiten geschrieben wird. Der zweite heißt socketpair() und erzeugt zwei Sockets für den gleichen Zweck.

Der fork-Mechanismus löst auf einfache Weise das Problem der Bearbeitung mehrerer paralleler Anfragen. Der Prozeß erzeugt einen Sohn, der auch den Socket erbt, über den die Verbindung zum Client erhalten bleibt. Die Endlosschleife des C-Serverprogramms muß dazu geändert werden:

                                        for (;;) 
                                          {
                                          ForeignSocket = accept(MySocket, &AdrPartnerSocket, &len);
                                       
                                          if (fork() == 0) /* Das ist der Kindprozess */ 
                                            {
                                            MsgLen = recv(ForeignSocket, Puffer, MAXPUF, 0);
                                            send(ForeignSocket, Puffer, MsgLen, 0);
                                            close(ForeignSocket);
                                            exit(0);       /* Kindprozess wird beendet */
                                            }
                                       
                                          close(ForeignSocket); /* der Elternprozess schliesst die Verbindung */
                                          }
                                       

Vorteile:

  • Die Programmierung ist sehr, sehr einfach, da Parent und Child sehr klare, abgesteckte Aufgaben haben.
  • Da Parent und Child völlig unabhängig voneinander sind, sind mit fork() arbeitende Server typischerweise sehr stabil: Stürzt ein Client ab, hat dies keinerlei Konsequenzen für die anderen Childs oder den Parent.
Dem stehen als Nachteile gegenüber:
  • Eine Kommunikation zwischen Parent und den verschiedenen Childs ist nur schwer möglich und schlecht portabel.
  • fork() steht typischerweise nur unter Unix zur Verfügung.

Signale

Mit Signalen können Prozesse veranlaßt werden, von ihrem "normalen" Ablauf abzuweichen. Sie können beispielsweise durch Ausführung eines fehlerhaften Befehls - wie Division durch 0, Zugriff auf einen geschützten Speicherbereich, etc. - verursacht werden, aber auch durch "asynchrone" Ereignisse, wie das Drücken der Taste Ctrl-C, oder dadurch, daß ein Prozeß einem anderen ein Signal zusendet. Letzteres ist beispielsweise nötig, wenn ein Prozeß abgebrochen werden soll, da es in Unix grundsätzlich nicht möglich ist, den Zustand eines Prozesses "von außen" zu verändern. Der Prozeß muß über die gewünschte Zustandsänderung informiert werden, um diese dann selbst durchzuführen. In Linux sind beispielsweise 32 Signale definiert (/usr/include/signum.h). Einige wichtige sind hier aufgelistet:
                                       #define SIGHUP   1  // "Auflegen" - z.B. bei einer Terminalleitung
                                       #define SIGINT   2  // Interrupt - z.B. Ctrl-C
                                       #define SIGILL   4  // Falscher Befehlscode
                                       #define SIGBUS   7  // Busfehler
                                       #define SIGKILL  9  // "Töten" eines Prozesses
                                       #define SIGSEGV 11  // Fehlerhafter Speicherzugriff
                                       #define SIGALRM 14  // Timer-Signal
                                       #define SIGCHLD 17  // "Vater, eines deiner Kinder ist tot"
                                       #define SIGCONT 18  // Prozeß fortsetzen (aus Zustand "stopped")
                                       #define SIGSTOP 19  // Prozeß anhalten -> Zustand "stopped"
                                       
Das Senden eines Signales an einen Prozeß entspricht im Wesentlichen dem Setzen des entsprechenden Bits in einem dafür vorgesehenen Speicherwort des Prozeßkontrollblockes. Der Prozeß kann zu jedem beliebigen Zeitpunkt festlegen, ob beim Empfang eines bestimmten Signales
  • die vom System standardmäßig vorgesehene Reaktion ausgelöst werden soll (meist Beenden des Prozesses),
  • ob stattdessen eine im Anwenderprogramm bereitgestellte Bearbeitungsroutine durchgeführt wwerden soll,
  • ob das Signal ignoriert wird.
Letzteres ist allerdings nicht bei allen Signalen möglich.

Was macht man, wenn beispielsweise 25 Kindprozesse aktiv sind, und sich im Prinzip jeder jederzeit beenden kann, man aber nicht die Übersicht verlieren will? Wenn der Kindprozeß stirbt, schickt er dem Elternprozeß ein Signal, SIGCHLD. Solange der Elternprozeß dieses Signal nicht annimmt, kann der Kindprozeß nicht aus der Prozeßtabelle entfernt werden, obwohl es nicht mehr aktiv ist. Solche Prozesse nennt man "Zombie-Prozesse". Erst wenn der Elternprozeß mit waitpid() oder wait() das Signal des Kindes beachtet und dessen Rückgabewert entgegengenommen hat, wird das Kind aus der Prozeßtabelle entfernt. Stirbt hingegen der Elternprozeß und verwaist das Kind, so erbt der Prozeß mit Prozeß-ID 1 - in aller Regel init - diesen Prozeß. Die PPID wird entsprechend abgeändert.

Wenn man für SIGCHLD einen Signalhandler setzt, kann das Problem ganz einfach gelöst werden. Dazu müssen wir uns aber zuerst mit Signalen beschäftigen. Signale sind die wohl einfachste Form der Prozeßkommunikation. Jeder Prozeß kann seinen Kindern und auch allen anderen Prozessen desselben Anwenders Signale schicken. Ein Prozeß mit Root-Rechten kann jedem Prozeß Signale schicken. Wann immer ein Prozeß vom Scheduler aktiviert wird oder vom Aufruf einer Systemfunktion zurückkehrt, wird nachgesehen, ob irgendwelche Signale angekommen sind, und gegebenenfalls die hierfür eingetragenen Signalhandler aktiviert. Man kann alle Signale mit Ausnahme von SIGKILL und SIGSTOP ignorieren. Signalhandler, laufen unter besonderen Bedingungen, weshalb sie so klein und einfach wie möglich gehalten werden sollten.

Sehen wir uns hierzu das folgende Perl-Programm an:

                                       #!/usr/bin/perl -w
                                       
                                       my $count = 0;
                                       
                                       $SIG{INT} = sub
                                         {
                                         $count++;
                                         warn "Oops! Das ist schon die Unterbrechung $count\n";
                                         };
                                       
                                       while ($count < 5)
                                         {
                                         print "Ratzepuehh!\n";
                                         sleep(3);
                                         }
                                       
Das Programm gibt alle drei Sekunden "Ratzepuehh!" aus und schläft dann weiter. Immer wenn die Taste Control-C gedrückt wird, löst dies einen Interrupt aus. Für diesen Interrupt (Signal INT) wurde ein Signalhandler installiert, der eine Warnung ausgibt und die ANzahl der Unterbrechungen hochzählt. Nach mehr als fünf Umterbrechungen beendet sich der Prozess. Der magische Hash %SIG enthält zu jedem Signal eine Subroutine, die aufgerufen wird, wenn dieses Signal ankommt. Mit der Anweisung $SIG{INT} = sub { ... } setzen wir einen eigenen Signalhandler für das Signal INT. Für die Aktionen eines Signalhandlers bieten sich folgende Möglichkeiten:

$SIG{INT} = 'IGNORE';Ignoriert SIGINT
$SIG{INT} = 'DEFAULT';Setzt die Default-Action für SIGINT
$SIG{INT} = \&catcher;führt den Code in sub catcher aus
$SIG{INT} = sub { $counter++; };führt den Code der anonymen sub aus

Signale sind asynchrone Ereignisse. Das laufende Programm wird unterbrochen und die Anweisungen im Signalhandler werden ausgeführt. Je nachdem wo sich Ihr Programm im Code gerade befindet, wenn ein Signal eintritt, können unterschiedliche Ereignisse auftreten. Perl ist nicht reentrant, zumindest nicht im Bereich der Low-Level-Systemzugriffe. Wenn ein Signal auftaucht, während Perl seine interne Datenstruktur ändert (z.B. malloc) ist ein Absturz die Regel. Auch deshalb sollten Signalhandler so kurz und einfach wie möglich sein. Probieren wir ein Beispiel (in Perl) mit mehreren Prozessen:

                                       #!/usr/bin/perl -w
                                       $|=1;
                                       
                                       my ($i, $pid, $time);
                                       my %child_pids = (); # Hash fuer Prozessnummern
                                       
                                       # Signalhandler fuer Childs
                                       $SIG{CHLD} = sub 
                                         {
                                         my $pid=wait();
                                         print "Terminated: $pid\n";
                                         delete $child_pids{$pid};
                                         };
                                       
                                       # Machen wir mal 10 Kinder
                                       for($i = 0; $i < 10; $i++)
                                         {
                                         $pid = fork();
                                         if($pid == 0) # KIND
                                           {
                                           sleep rand(20);
                                           exit(0);
                                           }
                                         else          # ELTERN
                                           {
                                           print "$pid wurde gestartet\n";
                                           $child_pids{$pid} = 1; # merken
                                           }
                                         }
                                       
                                       # Warten, bis alle Kinder terminiert sind
                                       $time = 0;
                                       while(0 + keys(%child_pids)) 
                                         {
                                         print "TIME: $time\n";
                                         sleep 1;
                                         $time++;
                                         }
                                       
Der Signalhandler wird jedesmal beim Terminieren eines Kindes aufgerufen, da der Erlternprozeß ein SIGCHLD-Signal erhält. Der Aufruf von wait() beseitigt dann alle Spuren des Kindes (wobei hier auch der Rückgabewert ignoriert wird). wait() wartet ja auf das Ende eines Kindprozesses und liefert dessen ID zurück. Man sollte erwarten, daß dieses Programm korrekt arbeitet. Es kommt je nach Rechner häufiger oder seltener vor, daß der Elternprozeß nicht mitbekommt, daß ein Kind terminiert ist, und am Schluß ewig wartet. Das hängt damit zusammen, daß
  • während ein Signal-Handler läuft, bestimmte Signale blockiert werden, (insbesondere das durch diesen Handler verarbeitete Signal, sonst müßte der Signalhandler reentrant sein).
  • manchmal zwei gleiche Signale kurz hintereinander wie eins gewertet werden können, falls der Prozeß nicht dazwischen Gelegenheit hatte, das erste Signal entgegenzunehmen.
  • Ein Kind wird gestoppt und gleich wieder gestartet.
Man kann sich beispielsweise behelfen, indem der Signalhandler passend erweitert wird. Statt wait() kommt nun waitpid() zum Einsatz. Diese Funktion kann über einen Parameter im Verhalten gesteuert werden. Werte für diesen Parameter befinden sich im POSIX-Modul, weshalb dieses im folgenden Programm eingebunden wird. Der Parameter WNOHANG versetzt waitpid() in den "nonblocking mode". Die Funktion liefert entweder die ID eines terminierten Kindes oder -1. falls keines existiert. Ein anderer nützlicher Wert ist WUNTRACED, der PIDs von gestoppten und terminierten Kindern liefert. Im obigen Programm muß also nur der Signalhandler geändert werden:
                                       #!/usr/bin/perl -w
                                       
                                       use POSIX ":sys_wait_h";
                                       
                                       $|=1;
                                       
                                       my ($i, $pid, $time);
                                       my %child_pids = ();
                                       
                                       $SIG{CHLD} = sub 
                                         {
                                         my($pid);
                                         foreach $pid (keys(%child_pids))
                                           {
                                           if(waitpid($pid,WNOHANG))
                                       	  {
                                       	  print "Terminated: $pid\n";
                                       	  delete $child_pids{$pid};
                                             }
                                           }
                                         };
                                       
                                       # Machen wir mal 10 Kinder
                                       for($i = 0; $i < 10; $i++)
                                         {
                                         $pid = fork();
                                         if($pid == 0) # KIND
                                           {
                                           sleep rand(20);
                                           exit(0);
                                           }
                                         else          # ELTERN
                                           {
                                           print "$pid wurde gestartet\n";
                                           $child_pids{$pid} = 1; # merken
                                           }
                                         }
                                       
                                       # Warten, bis alle Kinder terminiert sind
                                       $time = 0;
                                       while(0 + keys(%child_pids)) 
                                         {
                                         print "TIME: $time\n";
                                         sleep 1;
                                         $time++;
                                         }
                                       
Ein grundsätzliches Problem mit Kindprozessen ist, daß man stets damit rechnen muß, daß Eltern- oder Kindprozeß aus unterschiedlichsten Gründen verstirbt, und sei es nur, weil der Anwender ihm ein SIGKILL geschickt hat. Für einen Elternprozeß ist es relativ einfach, verstorbene Kinder auszumachen. Verwaiste Kinder werden hingegen nicht per Signal benachrichtigt.

BSD und POSIX-konforme Systeme verfügen über verläßliche Signale. Manche Systeme, z. B. (ältere) System V verfügen über keine zuverlässige Bibliothek zur Signalbehandlung. Für solche Systeme (und ggf. aus Portabilitätsgründen) müssen Sie die Signalhandler nach jedem Auftreten des Signals neu installieren.

                                       #!/bin/perl
                                       
                                       # globale Variablen initalisieren
                                       my $sig = '';
                                       
                                       # ALLE Signale erhalten einen Signalhandler
                                       @sigs = keys %SIG;
                                       for (@sigs) 
                                         { $SIG{$_} = \&catcher; }
                                       
                                       # Signalhandler
                                       sub catcher 
                                         {
                                         $sig = shift; 
                                         print STDERR "SIGNAL $sig \n";
                                         # reinstall handler
                                         $SIG{$sig} = \&catcher;           
                                         }
                                       

DIPLOMARBEITEN UND BÜCHER

Diplomarbeiten zum Runterladen:

Suche im Katalog:
Architektur / Raumplanung
Betriebswirtschaft - Funktional
Erziehungswissenschaften
Geowissenschaften
Geschichtswissenschaften
Informatik
Kulturwissenschaften
Medien- und Kommunikationswissenschaften
Medizin
Psychologie
Physik
Rechtswissenschaft
Soziale Arbeit
Sozialwissenschaften


JOBS
HOME | E-LEARNING | SITEMAP | LOGIN AUTOREN | SUPPORT | FAQ | KONTAKT | IMPRESSUM
Virtual University in: Italiano - Français - English - Español
VirtualUniversity, WEB-SET Interactive GmbH, www.web-set.com, 6301 Zug

Partner:   Seminare7.de - PCopen.de - HTMLopen.de - WEB-SET.com - YesMMS.com - Ausbildung24.ch - Manager24.ch - Job und Karriere