it-swarm.com.de

Warum müssen wir uns teilen, um neue Prozesse zu erstellen?

Wenn wir unter Unix einen neuen Prozess erstellen möchten, verzweigen wir den aktuellen Prozess und erstellen einen neuen untergeordneten Prozess, der genau dem übergeordneten Prozess entspricht. Dann führen wir einen Exec-Systemaufruf durch, um alle Daten aus dem übergeordneten Prozess durch die Daten für den neuen Prozess zu ersetzen.

Warum erstellen wir überhaupt eine Kopie des übergeordneten Prozesses und nicht direkt einen neuen Prozess?

100
sarthak

Die kurze Antwort lautet: fork ist in Unix, weil es zu dieser Zeit einfach war, sich in das bestehende System einzufügen, und weil ein Vorgängersystem in Berkeley das Konzept der Gabeln verwendet hatte.

Von Die Entwicklung des Unix-Time-Sharing-Systems (relevanter Text wurde hervorgehoben ):

Die Prozesssteuerung in ihrer modernen Form wurde innerhalb weniger Tage entworfen und implementiert. Es ist erstaunlich, wie einfach es in das bestehende System passt. Gleichzeitig ist leicht zu erkennen, wie einige der etwas ungewöhnlichen Merkmale des Entwurfs vorhanden sind, gerade weil sie kleine, leicht zu codierende Änderungen an dem darstellten, was existierte . Ein gutes Beispiel ist die Trennung der Fork- und Exec-Funktionen. Das gebräuchlichste Modell für die Erstellung neuer Prozesse ist die Angabe eines Programms für die Ausführung des Prozesses. Unter Unix führt ein gegabelter Prozess weiterhin dasselbe Programm wie sein übergeordnetes Programm aus, bis er eine explizite Ausführung ausführt. Die Trennung der Funktionen ist sicherlich nicht nur Unix vorbehalten, und tatsächlich war sie im Berkeley-Time-Sharing-System vorhanden, das Thompson bekannt war . Dennoch scheint es vernünftig anzunehmen, dass es in Unix existiert, hauptsächlich wegen der Leichtigkeit, mit der Fork implementiert werden kann, ohne viel anderes zu ändern . Das System handhabte bereits mehrere (d. H. Zwei) Prozesse; Es gab eine Prozesstabelle, und die Prozesse wurden zwischen dem Hauptspeicher und der Festplatte ausgetauscht. Die anfängliche Implementierung von Fork ist nur erforderlich

1) Erweiterung der Prozesstabelle

2) Hinzufügen eines Fork-Aufrufs, der den aktuellen Prozess unter Verwendung der bereits vorhandenen Swap-Primitive IO Primitive) in den Disk-Swap-Bereich kopiert und einige Anpassungen an der Prozesstabelle vorgenommen hat.

Tatsächlich erforderte der Gabelaufruf des PDP-7 genau 27 Zeilen Assembly-Code. Natürlich waren andere Änderungen am Betriebssystem und an den Benutzerprogrammen erforderlich, und einige davon waren ziemlich interessant und unerwartet. Aber ein kombinierter Fork-Exec wäre wesentlich komplizierter gewesen , wenn auch nur, weil exec als solches nicht existiert hätte; Seine Funktion wurde bereits von der Shell unter Verwendung expliziter E/A ausgeführt.

Seit diesem Artikel hat sich Unix weiterentwickelt. fork gefolgt von exec ist nicht mehr die einzige Möglichkeit, ein Programm auszuführen.

  • vfork wurde erstellt, um eine effizientere Verzweigung für den Fall zu sein, dass der neue Prozess beabsichtigt, direkt nach der Verzweigung eine Ausführung durchzuführen. Nach dem Ausführen einer vfork teilen sich der übergeordnete und der untergeordnete Prozess denselben Datenbereich, und der übergeordnete Prozess wird angehalten, bis der untergeordnete Prozess entweder ein Programm ausführt oder beendet wird.

  • posix_spawn erstellt einen neuen Prozess und führt eine Datei in einem einzigen Systemaufruf aus. Es sind eine Reihe von Parametern erforderlich, mit denen Sie die geöffneten Dateien des Anrufers selektiv freigeben und seine Signalanordnung und andere Attribute in den neuen Prozess kopieren können.

65
Mark Plotnick

[Ich wiederhole einen Teil meiner Antwort von hier .]

Warum nicht einfach einen Befehl haben, der einen neuen Prozess von Grund auf neu erstellt? Ist es nicht absurd und ineffizient, einen zu kopieren, der nur sofort ersetzt wird?

In der Tat wäre das aus einigen Gründen wahrscheinlich nicht so effizient:

  1. Die von fork() erzeugte "Kopie" ist eine Art Abstraktion, da der Kernel ein copy-on-write System verwendet. ;; Alles, was wirklich erstellt werden muss, ist eine virtuelle Speicherkarte. Wenn die Kopie dann sofort exec() aufruft, müssen die meisten Daten, die kopiert worden wären, wenn sie durch die Aktivität des Prozesses geändert worden wären, nie tatsächlich kopiert/erstellt werden, da der Prozess nichts tut, was dies erfordert verwenden.

  2. Verschiedene wichtige Aspekte des untergeordneten Prozesses (z. B. seine Umgebung) müssen nicht einzeln dupliziert oder basierend auf einer komplexen Analyse des Kontexts usw. festgelegt werden. Es wird lediglich angenommen, dass sie mit denen des aufrufenden Prozesses identisch sind Dies ist das ziemlich intuitive System, mit dem wir vertraut sind.

Um # 1 etwas weiter zu erklären, wird Speicher, der "kopiert" wird, auf den jedoch nie später zugegriffen wird, zumindest in den meisten Fällen nie wirklich kopiert. Eine Ausnahme in diesem Zusammenhang könnte sein, wenn Sie einen Prozess gegabelt haben und dann den übergeordneten Prozess beendet haben, bevor sich das untergeordnete Element durch exec() ersetzt hat. Ich sage könnte, weil ein Großteil des übergeordneten Elements zwischengespeichert werden könnte, wenn genügend freier Speicher vorhanden ist, und ich bin nicht sicher, inwieweit dies ausgenutzt würde (was von der Implementierung des Betriebssystems abhängen würde).

Das macht die Verwendung einer Kopie mehr Natürlich nicht effizienter als die Verwendung einer leeren Tafel - außer dass "die leere Tafel" nicht buchstäblich nichts ist und eine Zuordnung beinhalten muss. Das System könnte eine generische leere/neue Prozessvorlage haben, die es auf die gleiche Weise kopiert.1 aber das würde dann nicht wirklich etwas gegen die Copy-on-Write-Gabel sparen. # 1 zeigt also nur, dass die Verwendung eines "neuen" leeren Prozesses nicht effizienter wäre.

Punkt 2 erklärt, warum die Verwendung der Gabel wahrscheinlich effizienter ist. Die Umgebung eines Kindes wird von seinem Elternteil geerbt, auch wenn es sich um eine völlig andere ausführbare Datei handelt. Wenn der übergeordnete Prozess beispielsweise eine Shell und das untergeordnete ein Webbrowser ist, ist $HOME Für beide immer noch derselbe. Da beide jedoch später geändert werden können, müssen dies zwei separate Kopien sein. Die im Kind wird von der ursprünglichen fork() erzeugt.

1. Eine Strategie, die im wahrsten Sinne des Wortes nicht viel Sinn macht, aber mein Punkt ist, dass das Erstellen eines Prozesses mehr beinhaltet als das Kopieren seines Images von der Festplatte in den Speicher.

36
goldilocks

Ich denke, der Grund, warum Unix nur die Funktion fork hatte, um neue Prozesse zu erstellen, ist ein Ergebnis der nix-Philosophie

Sie bauen eine Funktion auf, die eines gut macht. Es wird ein untergeordneter Prozess erstellt.

Was man mit dem neuen Prozess macht, liegt dann beim Programmierer. Er kann eine der Funktionen exec* Verwenden und ein anderes Programm starten, oder er kann exec nicht verwenden und die beiden Instanzen desselben Programms verwenden, was nützlich sein kann.

So erhalten Sie einen größeren Freiheitsgrad, da Sie verwenden können

  1. gabel ohne Exec *
  2. gabel mit exec * oder
  3. nur exec * ohne Gabel

außerdem müssen Sie sich nur die Funktionsaufrufe fork und exec* merken, was Sie in den 1970er Jahren tun mussten.

6
Raphael Ahrens

Es gibt zwei Philosophien der Prozesserstellung: Gabelung mit Vererbung und Erstellen mit Argumenten. Unix verwendet offensichtlich Fork. (OSE zum Beispiel und VMS verwenden die Methode create.) Unix hat VIELE vererbbare Eigenschaften, und weitere werden regelmäßig hinzugefügt. Durch Vererbung können diese neuen Eigenschaften hinzugefügt werden, ohne bestehende Programme zu ändern! Wenn Sie ein Modell zum Erstellen mit Argumenten verwenden, bedeutet das Hinzufügen neuer Merkmale das Hinzufügen neuer Argumente zum Aufruf create. Das Unix-Modell ist einfacher.

Es bietet auch das äußerst nützliche Fork-without-Exec-Modell, bei dem sich ein Prozess in mehrere Teile aufteilen kann. Dies war damals von entscheidender Bedeutung, als es keine Form von asynchroner E/A gab, und ist nützlich, wenn mehrere CPUs in einem System genutzt werden sollen. (Pre-Threads.) Ich habe dies im Laufe der Jahre viel getan, sogar in letzter Zeit. Im Wesentlichen können mehrere "Programme" in einem einzigen Programm zusammengefasst werden, sodass absolut kein Platz für Beschädigungen oder Versionsinkongruenzen usw. vorhanden ist.

Das Fork/Exec-Modell bietet einem bestimmten Kind auch die Möglichkeit, eine radikal seltsame Umgebung zu erben, die zwischen dem Fork und dem Exec eingerichtet ist. Besonders Dinge wie geerbte Dateideskriptoren. (Eine Erweiterung von stdio fd's.) Das Erstellungsmodell bietet nicht die Möglichkeit, etwas zu erben, das von den Erstellern des Erstellungsaufrufs nicht vorgesehen war.

Einige Systeme können auch die dynamische Kompilierung von nativem Code unterstützen, wobei der Prozess tatsächlich sein eigenes natives Code-Programm schreibt. Mit anderen Worten, es möchte ein neues Programm, das es selbst im laufenden Betrieb schreibt, ohne den Quellcode-/Compiler-/Linker-Zyklus durchlaufen zu müssen und Speicherplatz zu belegen. (Ich glaube, es gibt ein Verilog-Sprachsystem, das dies tut.) Das Fork-Modell unterstützt dies, das Create-Modell normalerweise nicht.

5
Jim Cathey

Die fork () -Funktion kopiert nicht nur den Vaterprozess, sondern gibt einen Wert zurück, der angibt, dass der Prozess der Vater- oder der Sohnprozess ist. In der folgenden Abbildung wird erläutert, wie Sie fork () als Vater und a verwenden können Sohn:

enter image description here

wie gezeigt, wenn der Prozess der Vater ist, gibt fork () die Sohn-Prozess-ID PID zurück, andernfalls wird 0 zurückgegeben.

sie können es beispielsweise verwenden, wenn Sie einen Prozess (Webserver) haben, der die Anforderungen empfängt, und bei jeder Anforderung einen son process erstellen, um diese Anforderung zu verarbeiten. Hier haben der Vater und seine Söhne unterschiedliche Jobs.

Also, keine Kopie eines Prozesses ausführen ist nicht genau das Richtige wie fork ().

2
Nidal

Die E/A-Umleitung lässt sich am einfachsten nach dem Fork und vor der Ausführung implementieren. Das Kind kann, da es sich bewusst ist, dass es das Kind ist, Dateideskriptoren schließen, neue öffnen, dup () oder dup2 (), um sie auf die richtige fd-Nummer usw. zu bringen, ohne das Elternteil zu beeinflussen. Danach und möglicherweise durch Änderungen der gewünschten Umgebungsvariablen (die sich auch nicht auf das übergeordnete Element auswirken) kann das neue Programm in der angepassten Umgebung ausgeführt werden.

0