Warum verzweigt sich die Shell, wenn ein Prozess von einer Shell aus gestartet wird, bevor der Prozess ausgeführt wird?
grep blabla foo
Warum 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/13
Starten pts/14
)
quelle
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).bash -c 'grep foo bar'
und wenn Sie exec aufrufen, gibt es eine Form der Optimierung, die bash automatisch für SieGemäß der
pts
, überprüfen Sie es selbst: in einer Schale, LaufUm deine Prozess-ID (PID) zu kennen, habe ich zum Beispiel
Dann laufen zum Beispiel
sleep 60
und dann in einem anderen TerminalAlso nein, im Allgemeinen haben Sie die gleiche Menge mit dem Prozess verbunden. (Beachten Sie, dass dies Ihre ist,
sleep
weil es Ihre Shell als übergeordnetes Element hat).quelle
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 foo
er überexec()
direkt im Elternteil aufgerufen würdegrep blabla foo
.Lassen Sie uns jedoch allgemein über
exec()
und sprechenfork()
. Der Hauptgrund für ein solches Verhalten ist, dass diesfork()/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 vonbash
) 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 derexec()
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 erstellenfork()
und diesen Prozess über zu übernehmenexecve()
. Die interaktive Shell PID 1156 würde einfork()
untergeordnetes Element über PID 1157 erzeugen und dann aufrufenexecve("/bin/df",["df","-h"],&environment)
, wodurch/bin/df -h
sie 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 | grep
erstellen müssen, müssen Sie zwei Dateideskriptoren erstellen (das ist das Lese- und Schreibende der Pipe, das vonpipe()
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 perdup2()
Call auf dasstdout
aka fd 1 kopieren (wenn Write-Ende also fd 4 ist, tun wir dasdup2(4,1)
). Wenn esexec()
zu einem Spawndf
kommt, denkt der Child-Prozess an nichtsstdout
und schreibt an ihn, ohne zu wissen (es sei denn, er prüft aktiv), dass seine Ausgabe tatsächlich eine Pipe durchläuft. Gleicher Prozess geschiehtgrep
, außer unsfork()
, mit fd 3 Lese Ende des Rohres nehmen unddup(3,0)
vor dem Laichengrep
mitexec()
. 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 Befehlensource
. Unterschalen erfordernfork()
.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>'
. Obwohlfork()/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 :Daher verwenden viele Shells (nicht nur
bash
), umexec()
zu ermöglichen, dassbash -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: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: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
quelle
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.
quelle