Prozesse klonen mit fork()

Inhalt:

Houston, wir haben ein Problem!

Wir sollen 100 mal das gleiche machen – aber bitte gleichzeitig! Ein Beispiel, wo so etwas vorkommen könnte ist ein Server, der 100 Verbindungen gleichzeitig eingehen kann. Oder es sollen in der Bioinformatik 100 Sequenzen gleichzeitig berechnet werden.

Um das Prinzip zu erklären, wie man dieses Problem lösen kann, werde ich mich auf die Erzeugung von 2 parallelen Prozessen beschränken.

Was ist ein Prozess?

Ein Prozess ist ein laufendes Programm. Er besteht aus
  • statischen Daten wie TEXT, BSS, DATA
  • dynamischen Daten wie STACK, HEAP, Register-Belegung,...

Prozesse klonen mit fork()

Ein Prozess kann mit dem Systemaufruf fork() geklont werden. Das heißt, es wird eine identische Kopie des ursprünglichen Prozesses erzeugt. fork() erwartet keine Parameter und gibt einen numerischen Wert (Integer) zurück. Das Duplikat, das als Child-Prozess bezeichnet wird, übernimmt die aktuellen Werte aller Variablen und weiterer Datenstrukturen.

Man kann sich das ganze bildlich so vorstellen: Ein Mann lässt sich klonen. Der Klon erwacht im Zimmer nebenan und hat die gleichen Erinnerungen wie das Original; einschließlich dem Betreten des anderen Zimmers, in dem noch das Original ist. Die Kopie denkt dann "Ups, bin ich vorhin nicht in das andere Zimmer gegangen? Und wer ist der nette Herr in dem anderen Zimmer?".

Original und Kopie arbeiten aber ab dem fork() unabhängig voneinander. Das heißt, die Kopie kann in die Stadt einkaufen gehen, ohne dass das Original etwas davon mitbekommt - und umgekehrt. Ab dieser Stelle hat also jeder der beiden Prozesse alle bisher benutzten Variablen mit ihren aktuellen Belegungen zur Verfügung; Änderungen dieser Belegungen wirken sich aber nur noch innerhalb des eigenen Prozesses aus, der andere Prozess bekommt davon nichts mit. Nur Filehandels werden nicht kopiert - beide Prozesse schreiben und lesen aus den bzw. in die selben Filehandles.

+-------------+                  +-------------+       +-------------+
|             |                  |             |       |             |
|             |                  |             |       |             |
|  Ursprungs- |       =>         |   Child-    |   +   |   Parent-   |
|   prozess   |                  |   prozess   |       |   prozess   |
|             |                  |             |       |             |
+-------------+                  +-------------+       +-------------+

Um Probleme zu vermeiden, müssen Parent- und Child-Prozess wissen, wer von ihnen wer ist. Dieses Problem wird mit den sogenannten Prozess-IDs (kurz: PID) gelöst. Dies sind eindeutige positive Ganzzahlen.

So setze ich fork() ein:

#! /usr/bin/perl

use strict;
use warnings;

my $x = 4;
$x++;

my $pid = fork();

if (!defined $pid) {
    die "Could not fork";
}
elsif($pid == 0){
  # Hier bin ich im Child-Prozess
  $x++;
  print "Ich bin das Kind:  ",$$,"\tx:$x\n";
  exit(0);
}
else{
  # Hier bin ich im Parent-Prozess
  print "Ich bin der Vater: ",$$,"\tx:$x\n";
  wait();
}

Ausgabe:
(reneeb) Mo Mär 21 14:12:54 [/bin/bash]
~/entwicklung 18> perl fork.pl
Ich bin das Kind:  23160        x:6
Ich bin der Vater: 23159        x:5

Interprozesskommunikation

Diese Unabhängigkeit kann aber auch zu einem Problem werden. Was ist, wenn Original und Kopie miteinander kommunizieren müssen?

Die Kommunikation kann man über verschiedene Wege sicherstellen. Z.B. über Shared Memory, Signale oder Pipes.

Pipes kann man sich als "Röhre" vorstellen, über die in nur eine Richtung kommuniziert werden kann.

Dies sieht so aus:
#! /usr/bin/perl

use strict;
use warnings;

pipe(READER,WRITER);

my $pid = fork();

if($pid == 0){
  # Hier bin ich im Child-Prozess
  close READER;
  my $ort = 'bei Freunden';
  print "Ich bin ",$ort,"\n";
  print WRITER $ort;
  exit(0);
}
else{
  # Hier bin ich im Parent-Prozess
  close WRITER;
  while(my $line = <READER>){
    print "Ach, Du bist also ",$line,"\n";
  }
  wait();
}

Ausgabe:
(reneeb) Mo Mär 21 14:27:32 [/bin/bash]
~/entwicklung 21> perl fork.pl
Ich bin bei Freunden
Ach, Du bist also bei Freunden

Der Parent-Prozess lauscht so lange an der Pipes, bis der Child-Prozess etwas in die Pipe schreibt.

Ergänzungen, Kommentare

fork() in anderen Sprachen

C++:

1. Programm:
#include<iostream> // fuer cout
#include<unistd.h> // fuer getpid fork 

using namespace std;

int main(){

  int x = 4;
  x++;

  int pid;
  pid=fork(); // Prozess teilen

  if(pid<0){ // wenn Fehler bei fork
    cout << "Fehler bei fork" << endl;
    exit(1);
  }

  if(pid==0){ // wenn Kind
    x++;
    cout << "Ich bin das Kind:  " << getpid() <<"\tx: "<< x << endl;
    exit(0); // Kind mit 0 beenden
  }
  else if(pid>0){ // wenn Vater
    cout << "Ich bin der Vater: " << getpid() <<"\tx: "<< x << endl;
    exit(0); // Vaterl mit 0 beenden
  }
}

Ausgabe:
(reneeb) Thu Mar 24 15:22:13 [-bash]
~/entwicklung 181> fork
Ich bin der Vater: 14362        x: 5
Ich bin das Kind:  14363        x: 6

2. Programm:
#include<iostream> // fuer cout
#include<unistd.h> // fuer getpid fork

using namespace std;

int main(){

  int fds[2];
  pipe(fds);

  char ort[] = "bei Freunden";
  int BUFSIZE = strlen(ort);

  int pid;
  pid=fork(); // Prozess teilen

  if(pid<0){ // wenn Fehler bei fork
    cout << "Fehler bei fork" << endl;
    exit(1);
  }

  if(pid==0){ // wenn Kind
    close(fds[0]);
    cout << "Ich bin " << ort << endl;
    write(fds[1],ort,BUFSIZE);
    exit(0); // Kind mit 0 beenden
  }
  else if(pid>0){ // wenn Vater
    // die Standardeingabe des Vaters mit der lesbaren Seite der Pipe
    // (also fds[0]) verbunden. Dazu dient die Systemfunktion dup2()
    // Der Wert 0 steht dabei für den Filedeskriptor der Standardeingabe.
    // Das andere Ende der Pipe wird im Kind nicht benötigt und daher geschlossen
    dup2(fds[0],0);
    close(fds[1]);
    int length;
    char antwort[BUFSIZE];

    do{
      read(fds[0],antwort,BUFSIZE);
      cout << "Du bist also bei " << antwort << endl;
    }while(length > 0);
    wait(0); // Vaterl mit 0 beenden
  }
}

Ausgabe:
(reneeb) Thu Mar 24 15:22:13 [-bash]
~/entwicklung 186> fork
Ich bin bei Freunden
Du bist also bei bei Freunde

Java:

In Java gibt es erst seit Java 5 eine Umsetzung von fork(). Dort heißt die Klasse ProcessBuilder?

- ReneeBaecker 24 Mar 2005

Mehrere Child-Prozesse erzeugen und kommunizieren

Es kommt auch vor, dass man mehrere Child-Prozesse erzeugen will und mit diesen kommunizieren muss. Dafür kann man mehrmals forken und die Pipes müssen zu jedem Child extra aufgebaut werden!! Ein neuer Child kann nicht auf eine Pipe zugreifen, über die der Vater-Prozess schon mit einem anderen Child-Prozess kommuniziert.

Das ganze sieht dann so aus:
#! /usr/bin/perl

use strict;
use warnings;
use POSIX ":sys_wait_h";


fork_it();


#--------------------------------------------------------------------------------#
#                                Subroutines                                     #
#--------------------------------------------------------------------------------#

##
#  fork_it starts several processes for domain-requests
##
sub fork_it{

    for (1..5){
      pipe(READER,WRITER);
      my $pid=fork();
      if($pid==-1){
        warn($!);
        last;
      }
      if($pid){
        close WRITER;
        while(my $line = <READER>){
          # lese aus der Pipe
        }
        $pids{$pid}=1;
      }
      else{
        close READER;
        print WRITER "irgendwas\n";
        exit(0);
      }
    }
    while(keys %pids){
      my $pid=waitpid( -1, WNOHANG );
      die "$!" if $pid == -1;
      delete $pids{$pid};
    }
  }
}# end fork_it

CPAN-Module zum Thema fork

Proc::Fork
Werde ich in den nächsten Tagen mal testen. Sieht ganz interessant aus...

- ReneeBaecker 05 Apr 2005

TinaMueller - 14 Jul 2003 - Abfrage auf undef hinzugefügt

Kommentare werden am besten in folgender Form vorgenommen, damit sie im Inhaltsverzeichnis angezeigt werden (natürlich ohne das <verbatim>):
---+++ Main.??? - 14 Jul 2003 - Betreff

UtilFaqSubForm edit

Titel Prozesse klonen mit fork()
Autor ReneeBaecker
Bereich FaqAllgemeines
Tags
Topic revision: 2009-01-21, TinaMueller
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um Rückmeldung.