Was ist der Zweck von fork ()?

86

In vielen Programmen und Manpages von Linux habe ich Code mit gesehen fork(). Warum müssen wir verwenden fork()und was ist ihr Zweck?

Senfo
quelle
150
Damit all diese Speisephilosophen nicht verhungern.
Kenj0418

Antworten:

105

fork()So erstellen Sie neue Prozesse in Unix. Wenn Sie anrufen fork, erstellen Sie eine Kopie Ihres eigenen Prozesses mit einem eigenen Adressraum . Auf diese Weise können mehrere Aufgaben unabhängig voneinander ausgeführt werden, als hätten sie jeweils den vollen Speicher der Maschine für sich.

Hier sind einige Verwendungsbeispiele für fork:

  1. Ihre Shell verwendet forkdie Programme, die Sie über die Befehlszeile aufrufen.
  2. Webserver wie Apache werden verwendet fork, um mehrere Serverprozesse zu erstellen, von denen jeder Anforderungen in seinem eigenen Adressraum verarbeitet. Wenn einer stirbt oder Speicher verliert, sind andere davon nicht betroffen, sodass er als Mechanismus für die Fehlertoleranz fungiert.
  3. Google Chrome verwendet fork, um jede Seite in einem separaten Prozess zu behandeln. Dadurch wird verhindert, dass clientseitiger Code auf einer Seite Ihren gesamten Browser herunterfährt.
  4. forkwird verwendet, um Prozesse in einigen parallelen Programmen (wie denen, die mit MPI geschrieben wurden) zu erzeugen . Beachten Sie, dass sich dies von der Verwendung von Threads unterscheidet , die keinen eigenen Adressraum haben und in einem Prozess vorhanden sind.
  5. Skriptsprachen werden forkindirekt verwendet, um untergeordnete Prozesse zu starten. Beispielsweise verarbeiten Sie jedes Mal, wenn Sie einen Befehl wie subprocess.Popenin Python verwenden, forkein untergeordnetes Element und lesen dessen Ausgabe. Dadurch können Programme zusammenarbeiten.

Die typische Verwendung forkin einer Shell sieht ungefähr so ​​aus:

int child_process_id = fork();
if (child_process_id) {
    // Fork returns a valid pid in the parent process.  Parent executes this.

    // wait for the child process to complete
    waitpid(child_process_id, ...);  // omitted extra args for brevity

    // child process finished!
} else {
    // Fork returns 0 in the child process.  Child executes this.

    // new argv array for the child process
    const char *argv[] = {"arg1", "arg2", "arg3", NULL};

    // now start executing some other program
    exec("/path/to/a/program", argv);
}

Die Shell erzeugt einen untergeordneten Prozess mit execund wartet auf dessen Abschluss. Anschließend wird die eigene Ausführung fortgesetzt. Beachten Sie, dass Sie die Gabel nicht auf diese Weise verwenden müssen. Sie können immer viele untergeordnete Prozesse erzeugen, wie dies bei einem parallelen Programm der Fall ist, und jeder kann gleichzeitig ein Programm ausführen. Grundsätzlich verwenden Sie jedes Mal, wenn Sie neue Prozesse in einem Unix-System erstellen fork(). Schauen Sie sich das Windows-Äquivalent an CreateProcess.

Wenn Sie mehr Beispiele und eine längere Erklärung wünschen, hat Wikipedia eine anständige Zusammenfassung. Im Folgenden finden Sie einige Folien zur Funktionsweise von Prozessen, Threads und Parallelität in modernen Betriebssystemen.

Todd Gamblin
quelle
Aufzählungszeichen 5: "oft"? Nur "oft"? Welche verwenden es nicht oder unter welchen Umständen wird fork () nicht verwendet - also auf Systemen, die fork () unterstützen.
Jonathan Leffler
19
Seltsamerweise heißt es CreateProcess () - diese verrückten Windows-Typen :-)
paxdiablo
2
Bisher noch nie realisiert, dass "Shell Fork verwendet, um die von Ihnen über die Befehlszeile aufgerufenen Programme auszuführen"!
Lazer
1
Slides Link ist gebrochen
piertoni
1
Alle Antworten besagen, dass dies fork()der Weg ist, um einen neuen Prozess unter UNIX zu erstellen, aber um pedantisch zu sein, gibt es mindestens einen anderen : posix_spawn().
Davislor
15

Mit fork () erstellt Unix neue Prozesse. An dem Punkt, an dem Sie fork () aufgerufen haben, wird Ihr Prozess geklont, und zwei verschiedene Prozesse setzen die Ausführung von dort aus fort. Bei einem von ihnen, dem Kind, gibt fork () 0 zurück. Bei dem anderen, dem Elternteil, gibt fork () die PID (Prozess-ID) des Kindes zurück.

Wenn Sie beispielsweise Folgendes in eine Shell eingeben, ruft das Shell-Programm fork () auf und führt dann den Befehl aus, den Sie (in diesem Fall telnetd) im untergeordneten Element übergeben haben, während das übergeordnete Element die Eingabeaufforderung ebenfalls erneut anzeigt als Nachricht, die die PID des Hintergrundprozesses angibt.

$ telnetd &

Aus dem Grund, warum Sie neue Prozesse erstellen, kann Ihr Betriebssystem viele Dinge gleichzeitig erledigen. Aus diesem Grund können Sie ein Programm ausführen und während der Ausführung zu einem anderen Fenster wechseln und etwas anderes tun.

Daniel C. Sobral
quelle
@varDumper Guter Fang!
Daniel C. Sobral
9

fork () wird verwendet, um einen untergeordneten Prozess zu erstellen. Wenn eine fork () - Funktion aufgerufen wird, wird ein neuer Prozess erzeugt und der fork () - Funktionsaufruf gibt einen anderen Wert für das Kind und das Elternteil zurück.

Wenn der Rückgabewert 0 ist, wissen Sie, dass Sie der untergeordnete Prozess sind, und wenn der Rückgabewert eine Zahl ist (die zufällig die untergeordnete Prozess-ID ist), wissen Sie, dass Sie der übergeordnete Prozess sind. (und wenn es sich um eine negative Zahl handelt, ist die Verzweigung fehlgeschlagen und es wurde kein untergeordneter Prozess erstellt.)

http://www.yolinux.com/TUTORIALS/ForkExecProcesses.html

Wadih M.
quelle
1
Es sei denn, der Rückgabewert ist -1. In diesem Fall ist fork () fehlgeschlagen.
Jonathan Leffler
8

Mit fork () wird grundsätzlich ein untergeordneter Prozess für den Prozess erstellt, in dem Sie diese Funktion aufrufen. Immer wenn Sie ein fork () aufrufen, wird eine Null für die untergeordnete ID zurückgegeben.

pid=fork()
if pid==0
//this is the child process
else if pid!=0
//this is the parent process

Auf diese Weise können Sie verschiedene Aktionen für Eltern und Kind bereitstellen und die Multithreading-Funktion verwenden.

Kirchenschiff
quelle
6

fork () erstellt einen neuen untergeordneten Prozess, der mit dem übergeordneten identisch ist. Alles, was Sie danach im Code ausführen, wird von beiden Prozessen ausgeführt - sehr nützlich, wenn Sie beispielsweise einen Server haben und mehrere Anforderungen bearbeiten möchten.

Wolkenkopf
quelle
Warum erstellst du ein Kind, das mit dem Elternteil identisch ist? Was nützt es?
1
Es ist wie beim Aufbau einer Armee gegen einen einzelnen Soldaten. Sie geben an, dass Ihr Programm mehr Anforderungen gleichzeitig verarbeiten kann, anstatt nacheinander.
Cloudhead
fork () gibt 0 für das Kind und die PID des Kindes für das Elternteil zurück. Das Kind kann dann einen Aufruf wie exec () verwenden, um seinen Status durch ein neues Programm zu ersetzen. So werden Programme gestartet.
Todd Gamblin
Die Prozesse sind nahezu identisch, es gibt jedoch viele subtile Unterschiede. Die offensichtlichen Unterschiede sind die aktuelle PID und die übergeordnete PID. Es gibt Probleme im Zusammenhang mit gehaltenen Sperren und gehaltenen Semaphoren. Die fork () - Handbuchseite für POSIX listet 25 Unterschiede zwischen dem Elternteil und dem Kind auf.
Jonathan Leffler
2
@kar: Sobald Sie zwei Prozesse haben, können diese getrennt von dort fortgesetzt werden und einer von ihnen könnte sich selbst (exex ()) durch ein anderes Programm vollständig ersetzen.
Vatine
4

Sie müssen Fork wahrscheinlich nicht für die tägliche Programmierung verwenden, wenn Sie Anwendungen schreiben.

Selbst wenn Sie möchten, dass Ihr Programm ein anderes Programm startet, um eine Aufgabe zu erledigen, gibt es andere einfachere Schnittstellen, die Gabel hinter den Kulissen verwenden, wie "System" in C und Perl.

Wenn Sie beispielsweise möchten, dass Ihre Anwendung ein anderes Programm wie bc startet, um eine Berechnung für Sie durchzuführen, können Sie es mit 'system' ausführen. Das System führt einen 'Fork' aus, um einen neuen Prozess zu erstellen, und einen 'Exec', um diesen Prozess in bc umzuwandeln. Sobald bc abgeschlossen ist, gibt das System die Kontrolle an Ihr Programm zurück.

Sie können auch andere Programme asynchron ausführen, aber ich kann mich nicht erinnern, wie.

Wenn Sie Server, Shells, Viren oder Betriebssysteme schreiben, möchten Sie mit größerer Wahrscheinlichkeit Fork verwenden.

Alex Brown
quelle
Danke für system(). Ich habe darüber gelesen, fork()weil ich möchte, dass mein C-Code ein Python-Skript ausführt.
Bean Taxi
4

Gabel schafft neue Prozesse. Ohne Fork hätten Sie ein Unix-System, das nur init ausführen könnte.

Stephen
quelle
4

Der Systemaufruf fork () wird zum Erstellen von Prozessen verwendet. Es akzeptiert keine Argumente und gibt eine Prozess-ID zurück. Der Zweck von fork () besteht darin, einen neuen Prozess zu erstellen, der zum untergeordneten Prozess des Aufrufers wird. Nachdem ein neuer untergeordneter Prozess erstellt wurde, führen beide Prozesse die nächste Anweisung nach dem Systemaufruf fork () aus. Deshalb müssen wir den Elternteil vom Kind unterscheiden. Dies kann durch Testen des zurückgegebenen Werts von fork () erfolgen:

Wenn fork () einen negativen Wert zurückgibt, war die Erstellung eines untergeordneten Prozesses nicht erfolgreich. fork () gibt eine Null an den neu erstellten untergeordneten Prozess zurück. fork () gibt einen positiven Wert, die Prozess-ID des untergeordneten Prozesses, an das übergeordnete Element zurück. Die zurückgegebene Prozess-ID ist vom Typ pid_t, der in sys / types.h definiert ist. Normalerweise ist die Prozess-ID eine Ganzzahl. Darüber hinaus kann ein Prozess die Funktion getpid () verwenden, um die diesem Prozess zugewiesene Prozess-ID abzurufen. Daher kann nach dem Systemaufruf von fork () ein einfacher Test feststellen, welcher Prozess das untergeordnete Element ist. Bitte beachten Sie, dass Unix eine exakte Kopie des Adressraums der Eltern erstellt und diese dem Kind gibt. Daher haben der übergeordnete und der untergeordnete Prozess separate Adressräume.

Lassen Sie es uns anhand eines Beispiels verstehen, um die obigen Punkte klar zu machen. In diesem Beispiel werden übergeordnete und untergeordnete Prozesse nicht unterschieden.

#include  <stdio.h>
#include  <string.h>
#include  <sys/types.h>

#define   MAX_COUNT  200
#define   BUF_SIZE   100

void  main(void)
{
     pid_t  pid;
     int    i;
     char   buf[BUF_SIZE];

     fork();
     pid = getpid();
     for (i = 1; i <= MAX_COUNT; i++) {
          sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
          write(1, buf, strlen(buf));
     } 
}

Angenommen, das obige Programm wird bis zum Aufruf von fork () ausgeführt.

Wenn der Aufruf von fork () erfolgreich ausgeführt wird, erstellt Unix zwei identische Kopien von Adressräumen, eine für das übergeordnete und das andere für das untergeordnete Element. Beide Prozesse beginnen ihre Ausführung bei der nächsten Anweisung nach dem Aufruf von fork (). In diesem Fall beginnen beide Prozesse ihre Ausführung bei der Zuweisung

pid = .....;

Beide Prozesse starten ihre Ausführung direkt nach dem Systemaufruf fork (). Da beide Prozesse identische, aber separate Adressräume haben, haben diese vor dem Aufruf von fork () initialisierten Variablen in beiden Adressräumen dieselben Werte. Da jeder Prozess seinen eigenen Adressraum hat, sind alle Änderungen unabhängig von den anderen. Mit anderen Worten, wenn das übergeordnete Element den Wert seiner Variablen ändert, wirkt sich die Änderung nur auf die Variable im Adressraum des übergeordneten Prozesses aus. Andere durch fork () -Aufrufe erstellte Adressräume sind nicht betroffen, obwohl sie identische Variablennamen haben.

Was ist der Grund für die Verwendung von write anstelle von printf? Dies liegt daran, dass printf () "gepuffert" ist, was bedeutet, dass printf () die Ausgabe eines Prozesses zusammenfasst. Während die Ausgabe für den übergeordneten Prozess gepuffert wird, kann das Kind auch printf verwenden, um einige Informationen auszudrucken, die ebenfalls gepuffert werden. Da die Ausgabe nicht sofort an den Bildschirm gesendet wird, erhalten Sie möglicherweise nicht die richtige Reihenfolge des erwarteten Ergebnisses. Schlimmer noch, die Ausgabe der beiden Prozesse kann auf seltsame Weise gemischt werden. Um dieses Problem zu beheben, können Sie das "ungepufferte" Schreiben verwenden.

Wenn Sie dieses Programm ausführen, wird möglicherweise Folgendes auf dem Bildschirm angezeigt:

................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
     ................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
     ................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
     ................

Die Prozess-ID 3456 kann diejenige sein, die dem Elternteil oder dem Kind zugewiesen ist. Aufgrund der Tatsache, dass diese Prozesse gleichzeitig ausgeführt werden, werden ihre Ausgangsleitungen auf ziemlich unvorhersehbare Weise vermischt. Darüber hinaus wird die Reihenfolge dieser Zeilen vom CPU-Scheduler festgelegt. Wenn Sie dieses Programm erneut ausführen, erhalten Sie möglicherweise ein völlig anderes Ergebnis.

CPP-Codierer
quelle
3
Anstatt den Text zu kopieren, hätten Sie auch einen Link kommentieren können: csl.mtu.edu/cs4411.ck/www/NOTES/process/fork/create.html
chaitanya lakkundi
3

Multiprocessing ist für die Datenverarbeitung von zentraler Bedeutung. Beispielsweise kann Ihr IE oder Firefox einen Prozess zum Herunterladen einer Datei für Sie erstellen, während Sie noch im Internet surfen. Oder während Sie ein Dokument in einem Textverarbeitungsprogramm ausdrucken, können Sie immer noch verschiedene Seiten betrachten und damit noch etwas bearbeiten.

Unpolarität
quelle
3

Fork () wird verwendet, um neue Prozesse zu erstellen, wie jeder Körper geschrieben hat.

Hier ist mein Code, der Prozesse in Form eines Binärbaums erstellt. Er fordert Sie auf, die Anzahl der Ebenen zu scannen, bis zu denen Sie Prozesse im Binärbaum erstellen möchten

#include<unistd.h> 
#include<fcntl.h> 
#include<stdlib.h>   
int main() 
{
int t1,t2,p,i,n,ab;
p=getpid();                
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);                
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)    
{        
    t1=fork();

    if(t1!=0)
        t2=fork();        
    if(t1!=0 && t2!=0)        
        break;            
    printf("child pid %d   parent pid %d\n",getpid(),getppid());fflush(stdout);
}   
    waitpid(t1,&ab,0);
    waitpid(t2,&ab,0);
return 0;
}

AUSGABE

  enter the number of levels
  3
  root 20665
  child pid 20670   parent pid 20665
  child pid 20669   parent pid 20665
  child pid 20672   parent pid 20670
  child pid 20671   parent pid 20670
  child pid 20674   parent pid 20669
  child pid 20673   parent pid 20669
Anil Kumar Arya
quelle
2

Zuerst muss man verstehen, was ein Systemaufruf von fork () ist. Lassen Sie mich erklären

  1. Der Systemaufruf fork () erstellt das genaue Duplikat des übergeordneten Prozesses. Er erstellt das Duplikat des übergeordneten Stapels, des Heaps, der initialisierten Daten und der nicht initialisierten Daten und teilt den Code im schreibgeschützten Modus mit dem übergeordneten Prozess.

  2. Der Fork-Systemaufruf kopiert den Speicher auf der Basis von Copy-on-Write. Dies bedeutet, dass das Kind eine virtuelle Speicherseite erstellt, wenn ein Kopieren erforderlich ist.

Jetzt Zweck der Gabel ():

  1. Fork () kann an dem Ort verwendet werden, an dem es eine Arbeitsteilung gibt, wie ein Server mehrere Clients verarbeiten muss. Daher muss der Elternteil die Verbindung regelmäßig akzeptieren. Der Server gibt also einen Fork für jeden Client aus, um Lese- und Schreibvorgänge auszuführen.
Sandeep_black
quelle
1

fork()wird verwendet, um einen untergeordneten Prozess zu erzeugen. Normalerweise wird es in ähnlichen Situationen wie beim Threading verwendet, es gibt jedoch Unterschiede. Im Gegensatz zu Threads fork()werden ganze separate Prozesse erstellt. Dies bedeutet, dass das Kind und das Elternteil, während sie an dem fork()aufgerufenen Punkt direkte Kopien voneinander sind, vollständig getrennt sind und weder auf den Speicherplatz des anderen zugreifen können (ohne zu den normalen Problemen zu wechseln) Sie gehen, um auf den Speicher eines anderen Programms zuzugreifen.

fork()wird immer noch von einigen Serveranwendungen verwendet, hauptsächlich von solchen, die als Root auf einem * NIX-Computer ausgeführt werden und Berechtigungen löschen, bevor Benutzeranforderungen verarbeitet werden. Es gibt noch einige andere Anwendungsfälle, aber die meisten Menschen sind jetzt zum Multithreading übergegangen.

Matthew Scharley
quelle
2
Ich verstehe die Wahrnehmung nicht, dass "die meisten Menschen" zum Multithreading übergegangen sind. Prozesse sind hier, um zu bleiben, ebenso wie Threads. Niemand ist von beiden "weitergezogen". Bei der parallelen Programmierung sind die größten und gleichzeitigsten Codes Mehrprozessprogramme mit verteiltem Speicher (z. B. MapReduce und MPI). Dennoch würden sich die meisten Menschen für OpenMP oder ein Shared-Memory-Paradigma für einen Multicore-Computer entscheiden, und GPUs verwenden heutzutage Threads, aber darüber hinaus gibt es noch viel mehr. Ich wette jedoch, dass mehr Codierer auf dieser Site auf der Server-Seite auf Prozessparallelität stoßen als auf Multithreading.
Todd Gamblin
1

Die Gründe für fork () im Vergleich zu einer exec () - Funktion zum Initiieren eines neuen Prozesses werden in einer Antwort auf eine ähnliche Frage zum Unix-Stack-Austausch erläutert .

Da Fork den aktuellen Prozess kopiert, sind im Wesentlichen alle verschiedenen möglichen Optionen für einen Prozess standardmäßig festgelegt, sodass der Programmierer sie nicht bereitstellt.

Im Windows-Betriebssystem hingegen müssen Programmierer die Funktion CreateProcess verwenden, die VIEL komplizierter ist und das Auffüllen einer vielfältigen Struktur zum Definieren der Parameter des neuen Prozesses erfordert.

Zusammenfassend lässt sich sagen, dass der Grund für das Gabeln (im Vergleich zum Ausführen) die Einfachheit bei der Erstellung neuer Prozesse ist.

Tyler Durden
quelle
0

Fork () -Systemaufruf zum Erstellen eines untergeordneten Prozesses. Es ist ein genaues Duplikat des übergeordneten Prozesses. Fork kopiert Stapelabschnitte, Heapabschnitte, Datenabschnitte, Umgebungsvariablen und Befehlszeilenargumente vom übergeordneten Element.

Siehe: http://man7.org/linux/man-pages/man2/fork.2.html


quelle
0

Mit der Funktion fork () wird ein neuer Prozess erstellt, indem der vorhandene Prozess dupliziert wird, von dem aus er aufgerufen wird. Der vorhandene Prozess, von dem aus diese Funktion aufgerufen wird, wird zum übergeordneten Prozess und der neu erstellte Prozess zum untergeordneten Prozess. Wie bereits erwähnt, handelt es sich bei dem Kind um eine Kopie des Elternteils, es gibt jedoch einige Ausnahmen.

  • Das Kind hat eine eindeutige PID wie jeder andere Prozess, der im Betriebssystem ausgeführt wird.

  • Das Kind hat eine übergeordnete Prozess-ID, die mit der PID des
    Prozesses übereinstimmt , der es erstellt hat.

  • Ressourcenauslastung und CPU-Zeitzähler werden im untergeordneten Prozess auf Null zurückgesetzt.

  • Der Satz anstehender Signale im Kind ist leer.

  • Das Kind erbt keine Timer von seinem Elternteil

Beispiel:

    #include <unistd.h>
    #include <sys/types.h>
    #include <errno.h>
    #include <stdio.h>
    #include <sys/wait.h>
    #include <stdlib.h>

    int var_glb; /* A global variable*/

int main(void)
{
    pid_t childPID;
    int var_lcl = 0;

    childPID = fork();

    if(childPID >= 0) // fork was successful
    {
        if(childPID == 0) // child process
        {
            var_lcl++;
            var_glb++;
            printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
        else //Parent process
        {
            var_lcl = 10;
            var_glb = 20;
            printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
        }
    }
    else // fork failed
    {
        printf("\n Fork failed, quitting!!!!!!\n");
        return 1;
    }

    return 0;
}

Wenn nun der obige Code kompiliert und ausgeführt wird:

$ ./fork

Parent process :: var_lcl = [10], var_glb[20]

Child Process :: var_lcl = [1], var_glb[1]
Usman
quelle