Was ist die exec()
Funktion und ihre Familie? Warum wird diese Funktion verwendet und wie funktioniert sie?
Bitte erklären Sie diese Funktionen.
Was ist die exec()
Funktion und ihre Familie? Warum wird diese Funktion verwendet und wie funktioniert sie?
Bitte erklären Sie diese Funktionen.
Antworten:
Vereinfacht ausgedrückt haben Sie unter UNIX das Konzept von Prozessen und Programmen. Ein Prozess ist eine Umgebung, in der ein Programm ausgeführt wird.
Die einfache Idee hinter dem UNIX-Ausführungsmodell ist, dass Sie zwei Operationen ausführen können.
Das erste ist to fork()
, das einen brandneuen Prozess erstellt, der ein Duplikat (meistens) des aktuellen Programms einschließlich seines Status enthält. Es gibt einige Unterschiede zwischen den beiden Prozessen, die es ihnen ermöglichen, herauszufinden, welches der Eltern und welches das Kind ist.
Das zweite ist to exec()
, das das Programm im aktuellen Prozess durch ein brandneues Programm ersetzt.
Aus diesen beiden einfachen Operationen kann das gesamte UNIX-Ausführungsmodell erstellt werden.
Um dem oben Gesagten weitere Details hinzuzufügen:
Die Verwendung von fork()
undexec()
Veranschaulichung des Geistes von UNIX bietet eine sehr einfache Möglichkeit, neue Prozesse zu starten.
Der fork()
Aufruf macht eine nahezu doppelte Kopie des aktuellen Prozesses, die in fast jeder Hinsicht identisch ist ( in einigen Implementierungen wird nicht alles kopiert, z. B. Ressourcenbeschränkungen, aber die Idee ist, eine möglichst enge Kopie zu erstellen). Nur ein Prozess ruft auf, fork()
aber zwei Prozesse kehren von diesem Aufruf zurück - klingt bizarr, ist aber wirklich sehr elegant
Der neue Prozess (als untergeordnetes Element bezeichnet) erhält eine andere Prozess-ID (PID) und hat die PID des alten Prozesses (des übergeordneten Prozesses) als übergeordnete PID (PPID).
Da die beiden Prozesse jetzt genau denselben Code ausführen, müssen sie erkennen können, welcher welcher ist - der Rückkehrcode von fork()
liefert diese Informationen - das Kind erhält 0, das Elternteil erhält die PID des Kindes (wenn das fork()
fehlschlägt, nein Kind wird erstellt und das Elternteil erhält einen Fehlercode).
Auf diese Weise kennt der Elternteil die PID des Kindes und kann mit ihm kommunizieren, ihn töten, darauf warten usw. (das Kind kann seinen übergeordneten Prozess immer mit einem Aufruf an finden getppid()
).
Der exec()
Aufruf ersetzt den gesamten aktuellen Inhalt des Prozesses durch ein neues Programm. 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
das auszuführen - die Shell gabelt sich, dann lädt das Kind dasfind
Programm in den Speicher und richtet alle Befehlszeilenargumente, Standard-E / A usw. ein.
Sie müssen jedoch nicht zusammen verwendet werden. Es ist durchaus akzeptabel, dass ein Programm fork()
ohne Folgendes aufgerufen wird, 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 abhören und eine Kopie von sich selbst abspalten, um eine bestimmte Anforderung zu verarbeiten, während die Eltern wieder zuhören. In diesem Fall enthält das Programm sowohl den übergeordneten als auch den untergeordneten Code.
In ähnlichen Programme , die sie sind fertig wissen und wollen einfach nur ein anderes Programm nicht laufen müssen fork()
, exec()
und dann wait()/waitpid()
für das Kind. Sie können das Kind einfach direkt in ihren aktuellen Prozessraum mit laden exec()
.
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 unter Linux fork()
nur eine Kopie der Seitentabellen und eine neue Aufgabenstruktur erstellt werden, exec()
wird die Arbeit des "Trennens" des Speichers der beiden Prozesse erledigt.
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, bevor Änderungen erlaubt sind.
Linux hat auch eine vfork()
noch optimierte, die fast alles zwischen den beiden Prozessen teilt . Aus diesem Grund gibt es bestimmte Einschränkungen, was das Kind tun kann, und der Elternteil hält an, bis das Kind anruft exec()
oder _exit()
.
Das übergeordnete Element muss gestoppt werden (und das untergeordnete Element darf nicht von der aktuellen Funktion zurückkehren), da die beiden Prozesse sogar denselben Stapel gemeinsam nutzen. Dies ist etwas effizienter für den klassischen Anwendungsfall von fork()
unmittelbar gefolgt von exec()
.
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
exec
das Dienstprogramm zum Umleiten der E / A des aktuellen Prozesses verwendet? Wie wurde der Fall "null", in dem exec ohne Argument ausgeführt wird, für diese Konvention verwendet?
exec
als die Mittel , um das aktuelle Programm (die Shell) in diesem Prozess mit einem anderen zu ersetzen, dann nicht , dass anderen Programm angeben , sie zu ersetzen mit einfach meinen Sie nicht wollen , sie zu ersetzen.
exec
Aufrufs ohne Programm erhalten bleibt . In diesem Szenario ist dies jedoch etwas seltsam, da die ursprüngliche Nützlichkeit der Umleitung für ein neues Programm - ein Programm, das tatsächlich exec
umgeschaltet wird - verschwindet und Sie ein nützliches Artefakt haben, das das aktuelle Programm umleitet, das nicht exec
umgeschaltet oder gestartet wird in irgendeiner Weise - stattdessen.
Funktionen in der exec () -Familie haben unterschiedliche Verhaltensweisen:
Sie können sie mischen, deshalb haben Sie:
Für alle ist das ursprüngliche Argument der Name einer Datei, die ausgeführt werden soll.
Weitere Informationen finden Sie in der Manpage exec (3) :
man 3 exec # if you are running a UNIX system
execve()
Ihre Liste, die von POSIX definiert wird , verpasst und hinzugefügt execvpe()
, die nicht von POSIX definiert wurde (hauptsächlich aus Gründen des historischen Präzedenzfalls; sie vervollständigt den Funktionsumfang). Ansonsten eine hilfreiche Erklärung der Namenskonvention für die Familie - eine nützliche Ergänzung zu paxdiablo 'eine Antwort, die mehr über die Funktionsweise der Funktionen erklärt.
execvpe()
(et al.) Nicht aufgeführt ist execve()
; Es hat eine eigene, separate Manpage (zumindest unter Ubuntu 16.04 LTS) - der Unterschied besteht darin, dass die anderen exec()
Familienfunktionen in Abschnitt 3 (Funktionen) aufgeführt sind, während execve()
sie in Abschnitt 2 (Systemaufrufe) aufgeführt sind. Grundsätzlich werden alle anderen Funktionen in der Familie im Sinne eines Aufrufs an implementiert execve()
.
Durch die exec
Funktionsfamilie führt Ihr Prozess ein anderes Programm aus und ersetzt das alte Programm, das er ausgeführt hat. Dh wenn du anrufst
execl("/bin/ls", "ls", NULL);
Anschließend wird das ls
Programm mit der Prozess-ID, dem aktuellen Arbeitsverzeichnis und dem Benutzer / der Gruppe (Zugriffsrechte) des aufgerufenen Prozesses ausgeführt execl
. Danach läuft das ursprüngliche Programm nicht mehr.
Um einen neuen Prozess zu starten, wird der fork
Systemaufruf verwendet. Um ein Programm auszuführen, ohne das Original zu ersetzen, müssen Sie fork
dann exec
.
Was ist die Exec-Funktion und ihre Familie.
Die exec
Funktion Familie ist alle Funktionen verwendet , um eine Datei auszuführen, wie execl
, execlp
, execle
, execv
, und execvp
.Sie sind alle Frontends für execve
und bieten verschiedene Methoden der es aufgerufen wird .
Warum wird diese Funktion verwendet?
Exec-Funktionen werden verwendet, wenn Sie eine Datei (ein Programm) ausführen (starten) möchten.
und wie funktioniert es.
Sie überschreiben das aktuelle Prozessabbild mit dem von Ihnen gestarteten. Sie ersetzen (durch Beenden) des aktuell ausgeführten Prozesses (der den Befehl exec aufgerufen hat) durch den neuen Prozess, der gestartet wurde.
Weitere Details: siehe diesen Link .
exec
wird oft in Verbindung mit verwendet fork
, worüber ich gesehen habe, dass Sie auch gefragt haben, deshalb werde ich dies in diesem Sinne diskutieren.
exec
verwandelt den aktuellen Prozess in ein anderes Programm. Wenn Sie jemals Doctor Who gesehen haben, dann ist dies wie wenn er sich regeneriert - sein alter Körper wird durch einen neuen Körper ersetzt.
Dies geschieht mit Ihrem Programm und exec
besteht darin, dass viele der Ressourcen, die der Betriebssystemkern überprüft, um festzustellen, ob die Datei, an die Sie exec
als Programmargument übergeben werden (erstes Argument), vom aktuellen Benutzer (Benutzer-ID des Prozesses) ausführbar ist den exec
Anruf tätigen) und wenn ja, ersetzt es die Zuordnung des virtuellen Speichers des aktuellen Prozesses durch einen virtuellen Speicher des neuen Prozesses und kopiert die argv
und die envp
Daten, die im exec
Aufruf übergeben wurden, in einen Bereich dieser neuen Zuordnung des virtuellen Speichers. Hier können auch einige andere Dinge passieren, aber die Dateien, die für das aufgerufene Programm geöffnet waren, exec
sind weiterhin für das neue Programm geöffnet und haben dieselbe Prozess-ID, aber das aufgerufene Programm exec
wird beendet (es sei denn, exec ist fehlgeschlagen).
Der Grund dafür ist, dass Sie durch Trennen der Ausführung eines neuen Programms in zwei Schritte wie diesen einige Dinge zwischen den beiden Schritten tun können. Am häufigsten müssen Sie sicherstellen, dass im neuen Programm bestimmte Dateien als bestimmte Dateideskriptoren geöffnet sind. (Denken Sie hier daran, dass Dateideskriptoren nicht mit FILE *
, sondern int
Werte sind, die der Kernel kennt.) Auf diese Weise können Sie:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
Dies führt zum Laufen:
/bin/echo "hello world" > ./output_file.txt
von der Befehlsshell.
Die exec(3,3p)
Funktionen ersetzen den aktuellen Prozess durch einen anderen. Das heißt, der aktuelle Prozess wird gestoppt , und stattdessen wird ein anderer ausgeführt, der einige der Ressourcen übernimmt, über die das ursprüngliche Programm verfügte.
Wenn ein Prozess fork () verwendet, erstellt er eine doppelte Kopie von sich selbst und diese Duplikate werden zum untergeordneten Element des Prozesses. Fork () wird mithilfe des Systemaufrufs clone () unter Linux implementiert, der zweimal vom Kernel zurückgegeben wird.
Lassen Sie uns dies anhand eines Beispiels verstehen:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
Im Beispiel haben wir angenommen, dass exec () nicht im untergeordneten Prozess verwendet wird.
Ein Elternteil und ein Kind unterscheiden sich jedoch in einigen der PCB-Attribute (Process Control Block). Diese sind:
Aber was ist mit dem Kindergedächtnis? Wird ein neuer Adressraum für ein Kind erstellt?
Die Antworten in nein. Nach dem fork () teilen sich Eltern und Kind den Speicheradressraum des Elternteils. Unter Linux sind diese Adressräume in mehrere Seiten unterteilt. Nur wenn das Kind auf eine der übergeordneten Speicherseiten schreibt, wird ein Duplikat dieser Seite für das Kind erstellt. Dies wird auch als "Kopieren beim Schreiben" bezeichnet (Kopieren von übergeordneten Seiten nur, wenn das Kind darauf schreibt).
Lassen Sie uns das Schreiben beim Schreiben anhand eines Beispiels verstehen.
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
Aber warum ist eine Kopie beim Schreiben notwendig?
Eine typische Prozesserstellung erfolgt durch die Kombination von fork () - exec (). Lassen Sie uns zunächst verstehen, was exec () tut.
Die Funktionsgruppe Exec () ersetzt den Adressraum des Kindes durch ein neues Programm. Sobald exec () innerhalb eines untergeordneten Elements aufgerufen wird, wird ein separater Adressraum für das untergeordnete Element erstellt, der sich grundlegend von dem des übergeordneten Adressraums unterscheidet.
Wenn mit fork () keine Kopie des Schreibmechanismus verknüpft wäre, wären für das untergeordnete Element doppelte Seiten erstellt worden, und alle Daten wären auf die untergeordneten Seiten kopiert worden. Das Zuweisen von neuem Speicher und das Kopieren von Daten ist ein sehr teurer Prozess (kostet Zeit und andere Systemressourcen des Prozessors). Wir wissen auch, dass das Kind in den meisten Fällen exec () aufrufen wird und dies den Speicher des Kindes durch ein neues Programm ersetzen würde. Die erste Kopie, die wir gemacht haben, wäre also eine Verschwendung gewesen, wenn die Kopie beim Schreiben nicht da gewesen wäre.
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
Warum wartet ein Elternteil auf einen untergeordneten Prozess?
Warum ist ein Systemaufruf exec () erforderlich?
Es ist nicht erforderlich, exec () mit fork () zu verwenden. Wenn sich der Code, den das Kind ausführt, in dem mit dem Elternteil verknüpften Programm befindet, wird exec () nicht benötigt.
Denken Sie jedoch an Fälle, in denen das Kind mehrere Programme ausführen muss. Nehmen wir das Beispiel eines Shell-Programms. Es unterstützt mehrere Befehle wie find, mv, cp, date usw. Ist es richtig, den mit diesen Befehlen verknüpften Programmcode in ein Programm aufzunehmen oder das Kind diese Programme bei Bedarf in den Speicher laden zu lassen?
Es hängt alles von Ihrem Anwendungsfall ab. Sie haben einen Webserver, der eine Eingabe x gegeben hat, die die 2 ^ x an die Clients zurückgibt. Für jede Anforderung erstellt der Webserver ein neues untergeordnetes Element und fordert es zur Berechnung auf. Schreiben Sie ein separates Programm, um dies zu berechnen und exec () zu verwenden? Oder schreiben Sie einfach Berechnungscode in das übergeordnete Programm?
Normalerweise umfasst eine Prozesserstellung eine Kombination aus den Aufrufen fork (), exec (), wait () und exit ().