Unterschiede zwischen Gabel und Exec


199

Was sind die Unterschiede zwischen forkund exec?


3
Eine gute, detaillierte Zusammenfassung der Funktionen von Fork, Exec und anderen Prozesssteuerungen finden Sie unter yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland,

9
@Justin, weil wir wollen , SO werden der Platz für die Programmierung Fragen zu gehen.
Paxdiablo

4
@ Polaris878: oh, das tut es jetzt! : D
Janusz Lenar

so forkist im Grunde Klonen: O
Sebastian Hojas

Antworten:


363

Die Verwendung forkund execVeranschaulichung des Geistes von UNIX bietet eine sehr einfache Möglichkeit, neue Prozesse zu starten.

Der forkAufruf erstellt im Grunde genommen ein Duplikat des aktuellen Prozesses, das in fast jeder Hinsicht identisch ist . Es wird nicht alles kopiert (z. B. Ressourcenbeschränkungen in einigen Implementierungen), aber die Idee ist, eine möglichst enge Kopie zu erstellen.

Der neue Prozess (untergeordnet) erhält eine andere Prozess-ID (PID) und hat die PID des alten Prozesses (übergeordnet) als übergeordnete PID (PPID). Da die beiden Prozesse jetzt genau denselben Code ausführen, können sie anhand des Rückkehrcodes von fork- das Kind erhält 0, das Elternteil erhält die PID des Kindes - erkennen, welcher Code welcher ist . Dies alles ist natürlich vorausgesetzt, dass der forkAufruf funktioniert. Wenn nicht, wird kein untergeordnetes Element erstellt und das übergeordnete Element erhält einen Fehlercode.

Der execAufruf ist eine Möglichkeit, den gesamten aktuellen Prozess durch ein neues Programm zu ersetzen. Es lädt das Programm in den aktuellen Prozessbereich und führt es vom Einstiegspunkt aus.

So, forkund execwerden oft in Folge als Kind eines aktuellen Prozesses ausgeführt wird, ein neues Programm zu bekommen. Shells tun dies normalerweise immer dann, wenn Sie versuchen, ein Programm wie find- die Shell-Gabeln - auszuführen. Dann lädt das Kind das findProgramm in den Speicher und richtet alle Befehlszeilenargumente, Standard-E / A usw. ein.

Sie müssen jedoch nicht zusammen verwendet werden. Es ist für ein Programm für sich forkselbst völlig akzeptabel, execwenn das Programm beispielsweise sowohl übergeordneten als auch untergeordneten Code enthält (Sie müssen vorsichtig sein, was Sie tun, jede Implementierung kann Einschränkungen aufweisen). Dies wurde (und wird immer noch) häufig für Dämonen verwendet, die einfach einen TCP-Port und forkeine Kopie von sich selbst abhören , um eine bestimmte Anforderung zu verarbeiten, während die Eltern wieder zuhören.

In ähnlichen Programme , die sie sind fertig wissen und wollen einfach nur ein anderes Programm nicht laufen müssen fork, execund dann waitfür das Kind. Sie können das Kind einfach direkt in ihren Prozessraum laden.

Einige UNIX-Implementierungen sind optimiert forkund verwenden das, was sie Copy-on-Write nennen. Dies ist ein Trick, um das Kopieren des Prozessbereichs zu verzögern, forkbis das Programm versucht, etwas in diesem Bereich zu ändern. Dies ist nützlich für Programme, die nur verwenden forkund nicht exec, da sie nicht den gesamten Prozessbereich kopieren müssen.

Wenn das exec wird genannt folgende fork(und das ist , was meist geschieht), führt dazu , dass ein Schreiben in den Prozessraum und es wird dann für das Kind Prozess kopiert.

Beachten Sie, dass es eine ganze Familie von ist execGespräche ( execl, execle, execveund so weiter) , aber execin Zusammenhang bedeutet hier eine von ihnen.

Das folgende Diagramm zeigt die typische fork/execOperation, bei der die bashShell zum Auflisten eines Verzeichnisses mit dem lsBefehl verwendet wird:

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V

52

fork()teilt den aktuellen Prozess in zwei Prozesse auf. Mit anderen Worten, Ihr schönes lineares, leicht zu denkendes Programm wird plötzlich zu zwei separaten Programmen, auf denen ein Code ausgeführt wird:

 int pid = fork();

 if (pid == 0)
 {
     printf("I'm the child");
 }
 else
 {
     printf("I'm the parent, my child is %i", pid);
     // here we can kill the child, but that's not very parently of us
 }

Dies kann Sie umhauen. Jetzt haben Sie einen Code mit einem ziemlich identischen Status, der von zwei Prozessen ausgeführt wird. Der untergeordnete Prozess erbt den gesamten Code und den Speicher des Prozesses, der ihn gerade erstellt hat, einschließlich der Stelle, an der der fork()Anruf gerade aufgehört hat. Der einzige Unterschied ist der fork()Rückkehrcode, der Ihnen sagt, ob Sie der Elternteil oder das Kind sind. Wenn Sie der Elternteil sind, ist der Rückgabewert die ID des Kindes.

execist etwas einfacher zu verstehen, Sie müssen nur execeinen Prozess mit der ausführbaren Zieldatei ausführen, und Sie haben nicht zwei Prozesse, die denselben Code ausführen oder denselben Status erben. Wie @Steve Hawkins sagt, execkann verwendet werden, nachdem Sie forkim aktuellen Prozess die ausführbare Zieldatei ausgeführt haben.


6
Es gibt auch die Bedingung, wann pid < 0und der fork()Anruf fehlgeschlagen ist
Jonathan Fingland

3
Das macht mich überhaupt nicht fertig :-) Jedes Mal, wenn eine gemeinsam genutzte Bibliothek oder DLL verwendet wird, wird ein Code ausgeführt, der von zwei Prozessen ausgeführt wird.
Paxdiablo

31

Ich denke, einige Konzepte aus "Advanced Unix Programming" von Marc Rochkind waren hilfreich, um die verschiedenen Rollen von fork()/ zu verstehen exec(), insbesondere für jemanden, der an das Windows- CreateProcess()Modell gewöhnt ist :

Ein Programm ist eine Sammlung von Anweisungen und Daten, die in einer regulären Datei auf der Festplatte gespeichert sind. (ab 1.1.2 Programme, Prozesse und Threads)

.

Um ein Programm auszuführen, wird der Kernel zunächst aufgefordert, einen neuen Prozess zu erstellen. Dies ist eine Umgebung, in der ein Programm ausgeführt wird. (auch ab 1.1.2 Programme, Prozesse und Threads)

.

Es ist unmöglich, die Systemaufrufe exec oder fork zu verstehen, ohne die Unterscheidung zwischen einem Prozess und einem Programm vollständig zu verstehen. Wenn diese Begriffe für Sie neu sind, können Sie zurückgehen und Abschnitt 1.1.2 lesen. Wenn Sie jetzt fortfahren können, fassen wir die Unterscheidung in einem Satz zusammen: Ein Prozess ist eine Ausführungsumgebung, die aus Anweisungs-, Benutzerdaten- und Systemdatensegmenten sowie vielen anderen zur Laufzeit erworbenen Ressourcen besteht Während ein Programm eine Datei ist, die Anweisungen und Daten enthält, die zum Initialisieren der Anweisungs- und Benutzerdatensegmente eines Prozesses verwendet werden. (ab 5.3 execSystemaufrufe)

Sobald Sie die Unterscheidung zwischen einem Programm und einem Prozess verstanden haben, können Sie das Verhalten fork()und die exec()Funktion wie folgt zusammenfassen:

  • fork() Erstellt ein Duplikat des aktuellen Prozesses
  • exec() Ersetzt das Programm im aktuellen Prozess durch ein anderes Programm

(Dies ist im Wesentlichen eine vereinfachte 'für Dummies'- Version von Paxdiablos viel detaillierterer Antwort )


29

Fork erstellt eine Kopie eines aufrufenden Prozesses. folgt im Allgemeinen der Struktur Geben Sie hier die Bildbeschreibung ein

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(Für untergeordneten Prozesstext (Code), Daten entspricht der Stapel dem aufrufenden Prozess.) Der untergeordnete Prozess führt Code im if-Block aus.

EXEC ersetzt den aktuellen Prozess durch den Code, die Daten und den Stapel des neuen Prozesses. folgt im Allgemeinen der Struktur Geben Sie hier die Bildbeschreibung ein

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(Nach dem Exec-Aufruf löscht der Unix-Kernel den Text, die Daten, den Stapel des untergeordneten Prozesses und füllt ihn mit foo-prozessbezogenen Texten / Daten.) Der untergeordnete Prozess hat also einen anderen Code (foo-Code {nicht identisch mit übergeordnetem Code}).


1
Es hat nichts mit der Frage zu tun, aber verursacht dieser Code oben keine Race-Bedingung, wenn der untergeordnete Prozess seinen Code zuerst beendet? In diesem Fall würde der Elternprozess für immer herumhängen und darauf warten, dass das Kind sich selbst beendet, oder?
Stdout

7

Sie werden zusammen verwendet, um einen neuen untergeordneten Prozess zu erstellen. Beim Erstellen forkwird zunächst eine Kopie des aktuellen Prozesses (des untergeordneten Prozesses) erstellt. Wird execdann aus dem untergeordneten Prozess heraus aufgerufen, um die Kopie des übergeordneten Prozesses durch den neuen Prozess zu "ersetzen".

Der Prozess läuft ungefähr so ​​ab:

child = fork();  //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail

if (child < 0) {
    std::cout << "Failed to fork GUI process...Exiting" << std::endl;
    exit (-1);
} else if (child == 0) {       // This is the Child Process
    // Call one of the "exec" functions to create the child process
    execvp (argv[0], const_cast<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}

2
In der siebten Zeile wird erwähnt, dass die Funktion exec () den untergeordneten Prozess erstellt. Ist dies wirklich der Fall, weil fork () den
untergeordneten

4

fork () erstellt eine Kopie des aktuellen Prozesses, wobei die Ausführung im neuen untergeordneten Element unmittelbar nach dem Aufruf von fork () beginnt. Nach fork () sind sie bis auf den Rückgabewert der Funktion fork () identisch. (RTFM für weitere Details.) Die beiden Prozesse können dann noch weiter voneinander abweichen, wobei einer den anderen nicht stören kann, außer möglicherweise über gemeinsam genutzte Dateihandles.

exec () ersetzt den aktuellen Prozess durch einen neuen. Es hat nichts mit fork () zu tun, außer dass exec () häufig auf fork () folgt, wenn ein anderer untergeordneter Prozess gestartet werden soll, anstatt den aktuellen zu ersetzen.


3

Der Hauptunterschied zwischen fork()und exec()ist, dass

Der fork()Systemaufruf erstellt einen Klon des aktuell ausgeführten Programms. Das ursprüngliche Programm setzt die Ausführung mit der nächsten Codezeile nach dem Funktionsaufruf fork () fort. Der Klon startet auch die Ausführung in der nächsten Codezeile. Schauen Sie sich den folgenden Code an, den ich von http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/ erhalten habe.

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

Dieses Programm deklariert eine auf Null gesetzte Zählervariable, bevor fork()ing. Nach dem Fork-Aufruf werden zwei Prozesse parallel ausgeführt, wobei beide ihre eigene Version des Zählers erhöhen. Jeder Prozess wird vollständig ausgeführt und beendet. Da die Prozesse parallel ablaufen, können wir nicht wissen, welche zuerst abgeschlossen werden. Wenn Sie dieses Programm ausführen, wird etwas Ähnliches wie das unten gezeigte gedruckt, obwohl die Ergebnisse von Lauf zu Lauf variieren können.

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

Die exec()Familie der Systemaufrufe ersetzt den aktuell ausgeführten Code eines Prozesses durch einen anderen Code. Der Prozess behält seine PID bei, wird jedoch zu einem neuen Programm. Betrachten Sie beispielsweise den folgenden Code:

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

Dieses Programm ruft die execvp()Funktion auf, um ihren Code durch das Datumsprogramm zu ersetzen. Wenn der Code in einer Datei mit dem Namen exec1.c gespeichert ist, führt die Ausführung zu der folgenden Ausgabe:

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

Das Programm gibt die Zeile ―Ready to exec () aus. . . ‖ Ersetzt nach dem Aufruf der Funktion execvp () den Code durch das Datumsprogramm. Beachten Sie, dass die Zeile -. . . hat es funktioniert‖ wird nicht angezeigt, da zu diesem Zeitpunkt der Code ersetzt wurde. Stattdessen sehen wir die Ausgabe der Ausführung von "Datum -u".


1

Geben Sie hier die Bildbeschreibung einfork()::

Es wird eine Kopie des laufenden Prozesses erstellt. Der laufende Prozess wird als übergeordneter Prozess und der neu erstellte Prozess als untergeordneter Prozess bezeichnet . Die Art und Weise, die beiden zu unterscheiden, besteht darin, den zurückgegebenen Wert zu betrachten:

  1. fork() Gibt die Prozesskennung (pid) des untergeordneten Prozesses im übergeordneten Prozess zurück

  2. fork() gibt im Kind 0 zurück.

exec()::

Es initiiert einen neuen Prozess innerhalb eines Prozesses. Es lädt ein neues Programm in den aktuellen Prozess und ersetzt das vorhandene.

fork()+ exec():

Wenn Sie ein neues Programm starten, müssen Sie zunächst fork()einen neuen Prozess erstellen und dann exec()(dh in den Speicher laden und ausführen) die Programmbinärdatei ausführen, die ausgeführt werden soll.

int main( void ) 
{
    int pid = fork();
    if ( pid == 0 ) 
    {
        execvp( "find", argv );
    }

    //Put the parent to sleep for 2 sec,let the child finished executing 
    wait( 2 );

    return 0;
}

0

Das beste Beispiel zu verstehen , das fork()und exec()Konzept ist die Schale , Programm die Befehlsinterpreter , dass Benutzer führen typischerweise nach der Protokollierung in dem Shell interpretiert System das erste Wort der Kommandozeile als Befehlsnamen

Bei vielen Befehlen führen die Shell- Gabeln und der untergeordnete Prozess den dem Namen zugeordneten Befehl aus, wobei die verbleibenden Wörter in der Befehlszeile als Parameter für den Befehl behandelt werden.

Die Shell erlaubt drei Arten von Befehlen. Erstens kann ein Befehl eine ausführbare Datei sein , die Objektcode enthält, der durch Kompilieren des Quellcodes erzeugt wird (z. B. ein C-Programm). Zweitens kann ein Befehl eine ausführbare Datei sein, die eine Folge von Shell-Befehlszeilen enthält. Schließlich kann ein Befehl ein interner Shell-Befehl sein (anstelle einer ausführbaren Datei ex-> cd , ls usw.).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.