Was sind die Unterschiede zwischen fork
und exec
?
fork
ist im Grunde Klonen: O
Was sind die Unterschiede zwischen fork
und exec
?
fork
ist im Grunde Klonen: O
Antworten:
Die Verwendung fork
und exec
Veranschaulichung des Geistes von UNIX bietet eine sehr einfache Möglichkeit, neue Prozesse zu starten.
Der fork
Aufruf 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 fork
Aufruf funktioniert. Wenn nicht, wird kein untergeordnetes Element erstellt und das übergeordnete Element erhält einen Fehlercode.
Der exec
Aufruf 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, fork
und exec
werden 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 find
Programm 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 fork
selbst völlig akzeptabel, exec
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 (und wird immer noch) häufig für Dämonen verwendet, die einfach einen TCP-Port und fork
eine 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
, exec
und dann wait
für das Kind. Sie können das Kind einfach direkt in ihren Prozessraum laden.
Einige UNIX-Implementierungen sind optimiert fork
und verwenden das, was sie Copy-on-Write nennen. Dies ist ein Trick, um das Kopieren des Prozessbereichs zu verzögern, fork
bis das Programm versucht, etwas in diesem Bereich zu ändern. Dies ist nützlich für Programme, die nur verwenden fork
und 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 exec
Gespräche ( execl
, execle
, execve
und so weiter) , aber exec
in Zusammenhang bedeutet hier eine von ihnen.
Das folgende Diagramm zeigt die typische fork/exec
Operation, bei der die bash
Shell zum Auflisten eines Verzeichnisses mit dem ls
Befehl 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. 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.
exec
ist etwas einfacher zu verstehen, Sie müssen nur exec
einen 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, exec
kann verwendet werden, nachdem Sie fork
im aktuellen Prozess die ausführbare Zieldatei ausgeführt haben.
pid < 0
und der fork()
Anruf fehlgeschlagen ist
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
exec
Systemaufrufe)
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 Prozessesexec()
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 )
Fork 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), 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
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}).
Sie werden zusammen verwendet, um einen neuen untergeordneten Prozess zu erstellen. Beim Erstellen fork
wird zunächst eine Kopie des aktuellen Prozesses (des untergeordneten Prozesses) erstellt. Wird exec
dann 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
}
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.
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".
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:
fork()
Gibt die Prozesskennung (pid) des untergeordneten Prozesses im übergeordneten Prozess zurück
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;
}
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.).