Was sind die Unterschiede zwischen fork
und exec
?
Die Verwendung von fork
und exec
veranschaulicht den Geist von UNIX, indem es eine sehr einfache Möglichkeit bietet, neue Prozesse zu starten.
Der fork
-Aufruf macht im Wesentlichen ein Duplikat des aktuellen Prozesses aus, wobei fast in jeder Hinsicht identisch ist (in einigen Implementierungen wird nicht alles kopiert, z. B. die Ressourcengrenzwerte, aber es geht darum, eine möglichst nahe 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
feststellen, welcher der beiden ist. Das Kind erhält 0, der Elternteil erhält die PID des Kindes. Dies ist natürlich alles vorausgesetzt, der Aufruf von fork
funktioniert - wenn nicht, wird kein untergeordnetes Element erstellt und das übergeordnete Element erhält einen Fehlercode.
Der Aufruf exec
ist eine Möglichkeit, den gesamten aktuellen Prozess durch ein neues Programm zu ersetzen. Es lädt das Programm in den aktuellen Prozessraum und führt es vom Einstiegspunkt aus aus.
Daher werden fork
und exec
häufig nacheinander verwendet, um ein neues Programm als untergeordnetes Element eines aktuellen Prozesses auszuführen. Shells tun dies normalerweise, wenn Sie versuchen, ein Programm wie find
auszuführen - die Shell verzahnt sich, dann lädt das Kind das find
-Programm in den Speicher, wobei alle Befehlszeilenargumente, Standard-E/A usw. eingerichtet werden.
Sie müssen jedoch nicht zusammen verwendet werden. Es ist durchaus akzeptabel, dass ein Programm fork
selbst ohne exec
ing ist, wenn 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 für Daemons, die einfach einen TCP - Port abhören und fork
eine Kopie von sich selbst verwenden, um eine bestimmte Anforderung zu verarbeiten, während das übergeordnete Element wieder zuhören möchte.
Ebenso müssen Programme, die wissen, dass sie fertig sind und nur ein anderes Programm ausführen möchten, nicht fork
, exec
und dann wait
für das Kind benötigen. Sie können das Kind einfach direkt in ihren Prozessraum laden.
Einige UNIX-Implementierungen haben ein optimiertes fork
, das das verwendet, was sie als Copy-on-Write bezeichnen. Dies ist ein Trick, um das Kopieren des Prozessbereichs in fork
zu verzögern, bis das Programm versucht, etwas in diesem Bereich zu ändern. Dies ist nützlich für Programme, die nur fork
und nicht exec
verwenden, da sie nicht den gesamten Prozessbereich kopieren müssen.
Wenn exec
nach fork
aufgerufen wird (und dies geschieht meistens), führt dies zu einem Schreibvorgang in den Prozessbereich, und es wird dann für den untergeordneten Prozess kopiert.
Beachten Sie, dass es eine ganze Familie von exec
-Aufrufen gibt (execl
, execle
, execve
und so weiter), aber exec
bedeutet hier jeden von ihnen.
Das folgende Diagramm zeigt die typische fork/exec
-Operation, bei der die bash
-Shell zum Auflisten eines Verzeichnisses mit dem Befehl ls
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
fork()
teilt den aktuellen Prozess in zwei Prozesse auf. Oder anders ausgedrückt: Ihr Nice lineares Programm, das leicht zu verstehen ist, wird plötzlich zu zwei separaten Programmen, die einen Code ausführen:
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
}
Das kann dir den Atem versetzen. Jetzt haben Sie einen Code, bei dem ein ziemlich identischer Status von zwei Prozessen ausgeführt wird. Der untergeordnete Prozess erbt den gesamten Code und Speicher des Prozesses, den er gerade erstellt hat, einschließlich des Beginns des fork()
-Aufrufs. Der einzige Unterschied ist der Rückgabecode fork()
, der Ihnen mitteilt, ob Sie Elternteil oder Kind sind. Wenn Sie das übergeordnete Element sind, ist der Rückgabewert die ID des untergeordneten Elements.
exec
ist ein bisschen leichter zu verstehen. Sie sagen einfach exec
, dass sie einen Prozess mit der Ziel-Programmdatei ausführen, und Sie haben nicht zwei Prozesse, die denselben Code ausführen oder den gleichen Status erben. Wie @Steve Hawkins sagt, kann exec
verwendet werden, nachdem Sie fork
verwendet haben, um die aktuelle Zieldatei im aktuellen Prozess auszuführen.
Ich denke, einige Konzepte aus "Advanced Unix Programming" von Marc Rochkind waren hilfreich, um die verschiedenen Rollen von fork()
exec()
zu verstehen, insbesondere für jemanden, der das Windows CreateProcess()
-Modell verwendet hat:
Ein _/program ist eine Sammlung von Anweisungen und Daten, die in einer regulären Datei auf der Festplatte gespeichert werden. (ab 1.1.2 Programme, Prozesse und Threads)
.
Um ein Programm auszuführen, wird der Kernel zunächst aufgefordert, ein neues process zu erstellen. Hierbei handelt es sich um eine Umgebung, in der ein Programm ausgeführt wird. (auch ab 1.1.2 Programme, Prozesse und Threads)
.
Es ist unmöglich, die Exec- oder Fork-Systemaufrufe zu verstehen, ohne die Unterscheidung zwischen einem Prozess und einem Programm vollständig zu verstehen. Wenn diese Bedingungen für Sie neu sind, möchten Sie möglicherweise Abschnitt 1.1.2 erneut lesen. Wenn Sie jetzt fortfahren möchten, 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 erfassten Ressourcen besteht Ein Programm ist jedoch eine Datei mit Anweisungen und Daten, die zum Initialisieren der Anweisungs- und Benutzerdatensegmente eines Prozesses verwendet werden. (ab 5.3
exec
Systemaufrufe)
Wenn Sie den Unterschied zwischen einem Programm und einem Prozess verstanden haben, kann das Verhalten von fork()
und exec()
function wie folgt zusammengefasst werden:
fork()
erstellt ein Duplikat des aktuellen Prozessesexec()
ersetzt das Programm im aktuellen Prozess durch ein anderes Programm(Dies ist im Wesentlichen eine vereinfachte "for Dummies" -Version der viel detaillierteren Antwort von paxdiablo )
Gabel erstellt eine Kopie eines aufrufenden Prozesses. folgt im Allgemeinen der Struktur
int cpid = fork( );
if (cpid = = 0)
{
//child code
exit(0);
}
//parent code
wait(cpid);
// end
(Für untergeordneten Prozesstext (Code) ist data, stack der aufrufende Prozess.). Der untergeordnete Prozess führt Code im if-Block aus.
EXEC ersetzt den aktuellen Prozess durch Code, Daten, Stack des neuen Prozesses Im Allgemeinen folgt die Struktur
int cpid = fork( );
if (cpid = = 0)
{
//child code
exec(foo);
exit(0);
}
//parent code
wait(cpid);
// end
(Nach dem Aufruf des Unix-Kernel durch exec wird der untergeordnete Prozesstext gelöscht, Daten werden gestapelt und mit foo-prozessbezogenen Text/Daten gefüllt.). Der untergeordnete Prozess hat daher einen anderen Code (foo's code {nicht identisch mit dem übergeordneten})
Sie werden zusammen verwendet, um einen neuen untergeordneten Prozess zu erstellen. Beim Aufruf von fork
wird zunächst eine Kopie des aktuellen Prozesses (des untergeordneten Prozesses) erstellt. Dann wird exec
vom untergeordneten Prozess aus 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
}
fork () erstellt eine Kopie des aktuellen Prozesses. Die Ausführung im neuen untergeordneten Element beginnt unmittelbar nach dem Aufruf von fork (). Nach der Gabel () sind sie identisch mit Ausnahme des Rückgabewerts der Gabel () - Funktion. (RTFM für weitere Details.) Die beiden Prozesse können dann noch weiter auseinander gehen, wobei einer den anderen nicht beeinflussen kann, außer möglicherweise über gemeinsam genutzte Dateizugriffspunkte.
exec () ersetzt den aktuellen Prozess durch einen neuen. Es hat nichts mit fork () zu tun, außer, dass exec () häufig fork () folgt, wenn ein anderer untergeordneter Prozess gestartet werden soll, anstatt den aktuellen zu ersetzen.
Der Hauptunterschied zwischen fork()
und exec()
ist, dass
Der Systemaufruf fork()
erstellt einen Klon des aktuell laufenden Programms. Das ursprüngliche Programm setzt die Ausführung mit der nächsten Codezeile nach dem Aufruf der Funktion fork () fort. Der Klon beginnt auch mit der Ausführung in der nächsten Codezeile. Sehen 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 Zählervariable, die vor fork()
ing auf Null gesetzt wird. Nach dem Fork-Aufruf laufen zwei Prozesse parallel, die 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, welcher Prozess zuerst abgeschlossen wird. Wenn Sie dieses Programm ausführen, wird ein Ausdruck ähnlich dem unten gezeigten gedruckt, die Ergebnisse können jedoch von Lauf zu Lauf variieren.
--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 von Systemaufrufen ersetzt den aktuell ausgeführten Code eines Prozesses durch einen anderen Code. Der Prozess behält seine PID bei, aber er wird zu einem neuen Programm. Betrachten Sie zum Beispiel 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 Funktion execvp()
auf, um den Code durch das Datumsprogramm zu ersetzen. Wenn der Code in einer Datei mit dem Namen exec1.c gespeichert ist, wird bei der Ausführung die folgende Ausgabe erzeugt:
Ready to exec()...
Tue Jul 15 20:17:53 UTC 2008
Das Programm gibt die Zeile ―Ready to exec () aus. . . ‖ Und nach dem Aufruf der Funktion execvp () wird der Code durch das Datumsprogramm ersetzt. Beachten Sie, dass die Zeile -. . . hat es funktioniert‖ wird nicht angezeigt, da der Code an dieser Stelle ersetzt wurde. Stattdessen sehen wir die Ausgabe von ―date -u.‖
Es wird eine Kopie des laufenden Prozesses erstellt. Der laufende Prozess heißt übergeordneter Prozess und der neu erstellte Prozess heißt untergeordneter Prozess . Die Möglichkeit, die beiden zu unterscheiden, besteht darin, den zurückgegebenen Wert zu betrachten:
fork()
gibt die Prozesskennung (pid) des untergeordneten Prozesses im übergeordneten Prozess zurück
fork()
gibt 0 im Kind 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 zuerst fork()
, einen neuen Prozess erstellen und dann exec()
(d. H. In den Speicher laden und ausführen), die Programm-Binärdatei, 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;
}
Das beste Beispiel für das Konzept von fork()
und exec()
ist dasShell, das Befehlsinterpreterprogramm, das Benutzer normalerweise nach dem Anmelden am System ausführen. Die Shell interpretiert das erste Wort von Befehlszeile als Befehl Name
Bei vielen Befehlen führen der Shell forks und der untergeordnete Prozess execs den Befehl aus, der dem Namen zugeordnet ist, der die übrigen Wörter in der Befehlszeile als Parameter für den Befehl.
Die Shell erlaubt drei Arten von Befehlen. Erstens kann ein Befehl eine ausführbare Datei sein , die Objektcode enthält, der durch Kompilierung von Quellcode (beispielsweise einem C-Programm) erzeugt wurde. 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.).