Warum rufen Muscheln fork () auf?

32

Warum verzweigt sich die Shell, wenn ein Prozess von einer Shell aus gestartet wird, bevor der Prozess ausgeführt wird?

grep blabla fooWarum kann die Shell beispielsweise bei Benutzereingaben nicht einfach exec()grep ohne eine untergeordnete Shell aufrufen ?

Wenn sich eine Shell innerhalb eines GUI-Terminalemulators verzweigt, startet sie dann einen anderen Terminalemulator? (wie das pts/13Starten pts/14)

user3122885
quelle

Antworten:

34

Wenn Sie eine execFamilienmethode aufrufen , wird kein neuer Prozess erstellt, sondern execder aktuelle Prozessspeicher, der aktuelle Befehlssatz usw. werden durch den Prozess ersetzt, den Sie ausführen möchten.

Als Beispiel möchten Sie grepexec verwenden. bashist ein Prozess (mit separatem Speicher, Adressraum). Wenn Sie jetzt aufrufen exec(grep), ersetzt exec den Speicher, den Adressraum, den Befehlssatz usw. des aktuellen Prozesses durch grep'sDaten. Das bedeutet bash, dass es keinen Prozess mehr gibt. Infolgedessen können Sie nach Abschluss des grepBefehls nicht mehr zum Terminal zurückkehren . Deshalb kehren die Methoden der exec-Familie niemals zurück. Sie können nach exec keinen Code mehr ausführen. es ist nicht erreichbar.

Shantanu
quelle
Fast in Ordnung --- Ich habe Terminal durch bash ersetzt. ;-)
Rmano
2
BTW, Sie können sagen , bash ausführen grep ohne erste gabeln, mit dem Befehl exec grep blabla foo. In diesem speziellen Fall ist es natürlich nicht sehr nützlich (da Ihr Terminalfenster nur geschlossen wird, sobald das grep beendet ist), aber es kann gelegentlich nützlich sein (z. B. wenn Sie eine andere Shell starten, z. B. über ssh) / sudo / screen und beabsichtigen nicht, zum ursprünglichen zurückzukehren, oder wenn der Shell-Prozess, auf dem Sie dies ausführen, eine Sub-Shell ist, die sowieso nie mehr als einen Befehl ausführen soll).
Ilmari Karonen
7
Befehlssatz hat eine ganz bestimmte Bedeutung. Und es ist nicht die Bedeutung, in der Sie es verwenden.
Andrew Savinykh
@IlmariKaronen Dies ist nützlich in Wrapper-Skripten, in denen Sie Argumente und die Umgebung für einen Befehl vorbereiten möchten. Und der Fall, den Sie erwähnt haben, in dem bash nie mehr als einen Befehl ausführen soll, ist tatsächlich so, bash -c 'grep foo bar'und wenn Sie exec aufrufen, gibt es eine Form der Optimierung, die bash automatisch für Sie
ausführt
3

Gemäß der pts, überprüfen Sie es selbst: in einer Schale, Lauf

echo $$ 

Um deine Prozess-ID (PID) zu kennen, habe ich zum Beispiel

echo $$
29296

Dann laufen zum Beispiel sleep 60und dann in einem anderen Terminal

(0)samsung-romano:~% ps -edao pid,ppid,tty,command | grep 29296 | grep -v grep
29296  2343 pts/11   zsh
29499 29296 pts/11   sleep 60

Also nein, im Allgemeinen haben Sie die gleiche Menge mit dem Prozess verbunden. (Beachten Sie, dass dies Ihre ist, sleepweil es Ihre Shell als übergeordnetes Element hat).

Rmano
quelle
2

TL; DR : Weil dies die optimale Methode ist, um neue Prozesse zu erstellen und die Kontrolle über die interaktive Shell zu behalten

fork () wird für Prozesse und Pipes benötigt

Um den spezifischen Teil dieser Frage zu beantworten , würde ein Elternteil das Vorhandensein seiner PID mit allen Ressourcen übernehmen , wenn grep blabla fooer über exec()direkt im Elternteil aufgerufen würde grep blabla foo.

Lassen Sie uns jedoch allgemein über exec()und sprechen fork(). Der Hauptgrund für ein solches Verhalten ist, dass dies fork()/exec()die Standardmethode zum Erstellen eines neuen Prozesses unter Unix / Linux ist, und dies ist keine bashspezifische Sache. Diese Methode war von Anfang an in Kraft und wurde von bereits existierenden Betriebssystemen dieser Zeit beeinflusst. Die Antwort von goldilocks auf eine verwandte Frage etwas zu paraphrasieren , fork()um einen neuen Prozess zu erstellen, ist einfacher, da der Kernel beim Zuweisen von Ressourcen weniger Arbeit hat und viele Eigenschaften (wie Dateideskriptoren, Umgebung usw.) alle können vom übergeordneten Prozess (in diesem Fall von bash) geerbt werden .

Zweitens können Sie in Bezug auf interaktive Shells keine externen Befehle ausführen, ohne zu forken. Um eine ausführbare Datei zu starten, die sich auf der Festplatte befindet (z. B. /bin/df -h), müssen Sie eine der exec()Familienfunktionen aufrufen , z. B. execve()die das übergeordnete Element durch den neuen Prozess ersetzen, die PID und die vorhandenen Dateideskriptoren übernehmen usw. Für die interaktive Shell möchten Sie, dass das Steuerelement an den Benutzer zurückgegeben wird und die übergeordnete interaktive Shell fortgesetzt wird. Daher ist es am besten, einen Unterprozess über zu erstellen fork()und diesen Prozess über zu übernehmen execve(). Die interaktive Shell PID 1156 würde ein fork()untergeordnetes Element über PID 1157 erzeugen und dann aufrufen execve("/bin/df",["df","-h"],&environment), wodurch /bin/df -hsie mit PID 1157 ausgeführt wird. Jetzt muss die Shell nur noch warten, bis der Prozess beendet ist und die Steuerung an sie zurückgibt.

Wenn Sie beispielsweise eine Pipe zwischen zwei oder mehr Befehlen df | greperstellen müssen, müssen Sie zwei Dateideskriptoren erstellen (das ist das Lese- und Schreibende der Pipe, das von pipe()syscall stammt), und dann lassen Sie zwei neue Prozesse diese erben. Das ist erledigt, indem Sie einen neuen Prozess forken und dann das Write-Ende der Pipe per dup2()Call auf das stdoutaka fd 1 kopieren (wenn Write-Ende also fd 4 ist, tun wir das dup2(4,1)). Wenn es exec()zu einem Spawn dfkommt, denkt der Child-Prozess an nichts stdoutund schreibt an ihn, ohne zu wissen (es sei denn, er prüft aktiv), dass seine Ausgabe tatsächlich eine Pipe durchläuft. Gleicher Prozess geschieht grep, außer uns fork(), mit fd 3 Lese Ende des Rohres nehmen und dup(3,0)vor dem Laichen grepmitexec(). Der gesamte übergeordnete Prozess ist noch vorhanden und wartet darauf, die Steuerung wiederherzustellen, sobald die Pipeline abgeschlossen ist.

Bei eingebauten Befehlen funktioniert die Shell im Allgemeinen nicht fork(), mit Ausnahme von Befehlen source. Unterschalen erfordern fork().

Kurz gesagt, dies ist ein notwendiger und nützlicher Mechanismus.

Nachteile von Gabeln und Optimierungen

Dies ist jetzt anders bei nicht interaktiven Shells , wie z bash -c '<simple command>'. Obwohl fork()/exec()es sich um eine optimale Methode handelt, bei der Sie viele Befehle verarbeiten müssen, ist es eine Verschwendung von Ressourcen, wenn Sie nur einen einzigen Befehl haben. Um Stéphane Chazelas aus diesem Beitrag zu zitieren :

Das Verzweigen ist teuer, in Bezug auf CPU-Zeit, Speicher, zugewiesene Dateideskriptoren ... Es ist nur eine Verschwendung von Ressourcen, wenn ein Shell-Prozess nur darauf wartet, dass ein anderer Prozess beendet wird. Außerdem ist es schwierig, den Beendigungsstatus des separaten Prozesses, der den Befehl ausführen würde (z. B. wenn der Prozess beendet wird), korrekt zu melden.

Daher verwenden viele Shells (nicht nur bash), um exec()zu ermöglichen, dass bash -c ''dies von diesem einzelnen einfachen Befehl übernommen wird. Und genau aus den oben genannten Gründen ist es besser, Pipelines in Shell-Skripten zu minimieren. Oft sieht man Anfänger, die so etwas machen:

cat /etc/passwd | cut -d ':' -f 6 | grep '/home'

Natürlich wird dies fork()3 Prozesse. Dies ist ein einfaches Beispiel. Betrachten Sie jedoch eine große Datei im Gigabyte-Bereich. Mit einem Prozess wäre es weitaus effizienter:

awk -F':' '$6~"/home"{print $6}' /etc/passwd

Verschwendung von Ressourcen kann tatsächlich eine Form von Denial - of - Service - Angriff sein, insbesondere Gabel Bomben über Shell - Funktionen erstellt , die sich in der Pipeline aufrufen, die mehrere Kopien von sich selbst Gabeln. Heutzutage wird dies durch die Begrenzung der maximalen Anzahl von Prozessen in cgroups auf systemd gemildert , die Ubuntu seit Version 15.04 ebenfalls verwendet.

Das heißt natürlich nicht, dass das Gabeln nur schlecht ist. Es ist nach wie vor ein nützlicher Mechanismus, den Sie nach Möglichkeit vermeiden sollten, fork()wenn Sie mit weniger Prozessen und folglich weniger Ressourcen und damit einer besseren Leistung davonkommen können.

Siehe auch

Sergiy Kolodyazhnyy
quelle
1

Für jeden Befehl (Beispiel: grep), den Sie an der Bash-Eingabeaufforderung absetzen, möchten Sie tatsächlich einen neuen Prozess starten und nach der Ausführung zur Bash-Eingabeaufforderung zurückkehren.

Wenn der Shell-Prozess (bash) exec () aufruft, um grep auszuführen, wird der Shell-Prozess durch grep ersetzt. Grep funktioniert einwandfrei, aber nach der Ausführung kann das Steuerelement nicht zur Shell zurückkehren, da der Bash-Prozess bereits ersetzt wurde.

Aus diesem Grund ruft bash fork () auf, was den aktuellen Prozess nicht ersetzt.

FlowRaja
quelle