Bitte erläutern Sie die Funktion exec () und ihre Familie


97

Was ist die exec()Funktion und ihre Familie? Warum wird diese Funktion verwendet und wie funktioniert sie?

Bitte erklären Sie diese Funktionen.


4
Versuchen Sie erneut, Stevens zu lesen und klären Sie, was Sie nicht verstehen.
Vlabrecque

Antworten:


244

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 finddas 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 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

12
Vielen Dank für diese ausführliche Erklärung :)
Faizan

2
Vielen Dank für die Shell-Referenz mit dem Suchprogramm. Genau das, was ich verstehen musste.
Benutzer

Warum wird execdas 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?
Ray

@ Ray, ich habe es immer als natürliche Erweiterung angesehen. Wenn Sie denken , execals 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.
Paxdiablo

Ich verstehe, was Sie meinen, wenn Sie unter "natürlicher Ausdehnung" etwas im Sinne von "organischem Wachstum" verstehen. Es scheint, dass eine Umleitung hinzugefügt wurde, um die Programmersetzungsfunktion zu unterstützen, und ich kann sehen, dass dieses Verhalten im entarteten Fall eines execAufrufs 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 execumgeschaltet wird - verschwindet und Sie ein nützliches Artefakt haben, das das aktuelle Programm umleitet, das nicht execumgeschaltet oder gestartet wird in irgendeiner Weise - stattdessen.
Ray

36

Funktionen in der exec () -Familie haben unterschiedliche Verhaltensweisen:

  • l: Argumente werden als Liste von Zeichenfolgen an main () übergeben.
  • v: Argumente werden als Array von Zeichenfolgen an main () übergeben.
  • p: Pfad / e, um nach dem neuen laufenden Programm zu suchen
  • e: Die Umgebung kann vom Anrufer angegeben werden

Sie können sie mischen, deshalb haben Sie:

  • int execl (const char * path, const char * arg, ...);
  • int execlp (Datei const char *, Datei const char * arg, ...);
  • int execle (const char * Pfad, const char * arg, ..., char * const envp []);
  • int execv (const char * path, char * const argv []);
  • int execvp (const char * Datei, char * const argv []);
  • int execvpe (const char * Datei, char * const argv [], char * const envp []);

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

1
Interessanterweise haben Sie 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.
Jonathan Leffler

Und zu Ihrer Verteidigung sehe ich, dass die Linux-Manpage für 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().
Jonathan Leffler

18

Durch die execFunktionsfamilie 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 lsProgramm 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 forkSystemaufruf verwendet. Um ein Programm auszuführen, ohne das Original zu ersetzen, müssen Sie forkdann exec.


Danke, das war wirklich hilfreich. Ich mache gerade ein Projekt, bei dem wir exec () verwenden müssen, und Ihre Beschreibung hat mein Verständnis gefestigt.
TwilightSparkleTheGeek

7

Was ist die Exec-Funktion und ihre Familie.

Die execFunktion Familie ist alle Funktionen verwendet , um eine Datei auszuführen, wie execl, execlp, execle, execv, und execvp.Sie sind alle Frontends für execveund 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 .


7

execwird oft in Verbindung mit verwendet fork, worüber ich gesehen habe, dass Sie auch gefragt haben, deshalb werde ich dies in diesem Sinne diskutieren.

execverwandelt 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 execbesteht darin, dass viele der Ressourcen, die der Betriebssystemkern überprüft, um festzustellen, ob die Datei, an die Sie execals Programmargument übergeben werden (erstes Argument), vom aktuellen Benutzer (Benutzer-ID des Prozesses) ausführbar ist den execAnruf 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 argvund die envpDaten, die im execAufruf ü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, execsind weiterhin für das neue Programm geöffnet und haben dieselbe Prozess-ID, aber das aufgerufene Programm execwird 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 intWerte 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.


4

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.


6
Nicht ganz. Er ersetzt das aktuelle Prozessbild mit einem neuen Prozessabbild. Der Prozess ist der gleiche Prozess mit derselben PID, derselben Umgebung und derselben Dateideskriptortabelle. Was sich geändert hat, ist der gesamte virtuelle Speicher und der CPU-Status.
JeremyP

@JeremyP "Der gleiche Dateideskriptor" war hier wichtig, er erklärt, wie die Umleitung in Shells funktioniert! Ich war verwirrt darüber, wie die Umleitung funktionieren kann, wenn exec alles überschreibt! Danke
FUD

4

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.

  • Ein Wert ungleich Null (Prozess-ID des untergeordneten Elements) wird an das übergeordnete Element zurückgegeben.
  • Ein Wert von Null wird an das untergeordnete Element zurückgegeben.
  • Falls das Kind aufgrund von Problemen wie wenig Speicher nicht erfolgreich erstellt wurde, wird -1 an fork () zurückgegeben.

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:

  1. PID - Sowohl das Kind als auch das Elternteil haben eine unterschiedliche Prozess-ID.
  2. Ausstehende Signale - Das Kind erbt die ausstehenden Signale der Eltern nicht. Beim Erstellen ist es für den untergeordneten Prozess leer.
  3. Speichersperren - Das Kind erbt die Speichersperren seiner Eltern nicht. Speichersperren sind Sperren, mit denen ein Speicherbereich gesperrt werden kann. Dieser Speicherbereich kann dann nicht auf die Festplatte übertragen werden.
  4. Datensatzsperren - Das Kind erbt die Datensatzsperren seiner Eltern nicht. Datensatzsperren sind einem Dateiblock oder einer gesamten Datei zugeordnet.
  5. Die Auslastung der Prozessressourcen und die verbrauchte CPU-Zeit werden für das untergeordnete Element auf Null gesetzt.
  6. Das Kind erbt auch keine Timer vom Elternteil.

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?

  1. Der Elternteil kann seinem Kind eine Aufgabe zuweisen und warten, bis die Aufgabe abgeschlossen ist. Dann kann es eine andere Arbeit tragen.
  2. Sobald das untergeordnete Element beendet ist, werden alle mit dem untergeordneten Element verknüpften Ressourcen mit Ausnahme des Prozesssteuerungsblocks freigegeben. Jetzt befindet sich das Kind im Zombie-Zustand. Mit wait () kann der Elternteil den Status des Kindes abfragen und dann den Kernel bitten, die Leiterplatte freizugeben. Falls Eltern kein Warten verwenden, bleibt das Kind im Zombie-Status.

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 ().

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.