Versteckter Overhead von Unix/Linux-Forks

Versteckter Overhead von Unix/Linux-Forks

1. Der Ursprung der Gabel

Die Idee des Forkings entstand mehrere Jahre vor dem Erscheinen von UNIX, etwa 1963, sechs Jahre vor der ersten Version von UNIX auf dem PDP-7.
Im Jahr 1963 verfasste der Informatiker Melvin Conway (bekannt für Conways Gesetz) eine Arbeit, in der er die Idee des Forkings formal vorschlug.
Die Idee von Fork wurde ursprünglich von Conway als parallele Mehrprozessorlösung vorgeschlagen, was eine sehr interessante Idee ist. Kurz gesagt, die Fork-Idee stammt aus dem Flussdiagramm.

Schauen wir uns ein allgemeines Flussdiagramm an:

Schauen Sie, der verzweigte Teil des Flussdiagramms, die Gabel, ist so anschaulich!

Die von einem Verzweigungspunkt in einem Flussdiagramm ausgehenden Zweige sind offensichtlich logisch unabhängig, was die Voraussetzung der Parallelität ist. Daher können sie in Form unterschiedlicher Verarbeitungsprozesse ausgedrückt werden. Damals war der Ausdruck nur der Begriff „Prozess“, was nicht dem Konzept „Prozess“ im Sinne moderner Betriebssysteme entsprach.

Der Join-Synchronisationspunkt ist der Punkt, an dem mehrere parallele Prozesse aus irgendeinem Grund synchronisiert werden müssen, also der Punkt, an dem mehrere parallele Prozesse zusammenlaufen. Bisher wurde dieser Punkt in der Multithread-Programmierung immer noch als Join bezeichnet. Beispielsweise die Join-Methode von Java Thread und die pthread_join-Funktion der pthread-Bibliothek.

Im weiteren Sinne bezieht sich Join auch auf Punkte, die seriell durchlaufen werden müssen, wie etwa kritische Abschnitte. Durch die Reduzierung der Anzahl von Join-Punkten wird die parallele Effizienz verbessert.

Schauen wir uns das Originaldiagramm der Gabel in Conways Artikel an:

Eine weitere Neuerung von Conway in diesem Artikel besteht darin, dass er den Verarbeitungsprozess (darauf wird später das Prozesskonzept im Betriebssystem Bezug genommen) und den Prozessor, der den Prozess ausführt (also den CPU-Kern), trennt und so die Planungsebene abstrahiert.

Die allgemeine Idee ist: „Solange die Anzahl der aktiven Prozessoren im System das Minimum der Gesamtanzahl der Prozessoren und der Anzahl der parallelen Verarbeitungsprozesse ist.“ Dies bedeutet, dass der Scheduler alle Prozessoren im Mehrprozessorsystem und alle Verarbeitungsprozesse im System als einheitlichen Ressourcenpool bzw. Verbraucher behandeln und eine einheitliche Planung durchführen kann:

Nachdem Fork bei UNIX eingeführt wurde, wurde dieses Konzept des parallelen Designs mehrerer Prozessoren tief im Kern von UNIX verwurzelt. Diese Idee beeinflusste schließlich UNIX und später Linux bis heute.
關于這個設計思想為什么可以影響UNIX這么久,我想和Conway本人的“Conway's law”不無關系,在這個law中,他提到:Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.

2. Frühe UNIX-Overlay-Technologie

Als nächstes wollen wir einen anderen Kontext von UNIX-Forks betrachten. Das ursprüngliche UNIX von 1969 lief auf eine Weise, die heute sehr merkwürdig erscheint.

Allgemeine Informationen beginnen mit UNIX v6, einer relativ „modernen“ Version, sodass nur wenige Menschen erkennen können, wie das ursprüngliche UNIX aussah. Sogar der Unix-Quellcode, der 1970 auf dem PDP-7 lief und eingesehen werden kann, ist die Version nach der Einführung von Fork, und die Originalversion davor ist fast unmöglich zu finden (Sie können sagen, dass Unix damals nicht Unix hieß, aber wen kümmert das ...).

Das ursprüngliche UNIX war ein Time-Sharing-System mit nur zwei Shell-Prozessen , von denen jeder zu einem Terminal gehörte:

分時系統最初并不是基于進程分時的,那時根本還沒有完整的進程的概念,分時系統是針對終端分時的,而操作員坐在終端前,為了讓每個操作員在操作過程中感覺上是在獨占機器資源,每個終端享受一段時間的時間片,在該時間片內,該終端前的操作員完全享受機器,但是為了公平,超過了時間片,時間片就要給另一個終端。

Das ist alles. Um die Time-Sharing-Funktion zu berücksichtigen, implementierte das ursprüngliche UNIX mindestens zwei Terminals. Beachten Sie, dass das ursprüngliche UNIX weder Fork noch Exec und nicht einmal das Konzept mehrerer Prozesse hatte. Um Time-Sharing zu erreichen, gab es im System nur zwei einfache Shell-Prozesse.

Tatsächlich verwendete das ursprüngliche UNIX eine Tabelle mit nur zwei Elementen, um alle Prozesse zu speichern (das sieht natürlich komisch aus ...). Natürlich ist das Konzept „Tabelle“ hier auch ein abstraktes und einfaches Konzept, da das System damals in PDP-7-Assembler geschrieben war und es später keine Datenstruktur in der Sprache C gab.

Wir betrachten nun, wie ein Shell-Prozess in einem der Terminals funktioniert. Es stellt sich sofort die Frage, wie dieser Shell-Prozess andere Befehlsprogramme ausführt? ?

Wenn das System höchstens zwei Prozesse aufnehmen kann und ein Terminal nur über einen Shell-Prozess verfügt, was soll der Shell-Prozess des Terminals tun, wenn er andere Befehlsprogramme ausführt? Diese Frage bedarf einiger Überlegungen ...

Hinweis: Bewerten Sie die erste Version von UNIX aus dem Jahr 1969 nicht mit modernen Augen. Aus heutiger Sicht muss die Ausführung eines Programms einen neuen Prozess generieren. Dies ist bei der ersten Version von UNIX offensichtlich nicht korrekt.

Die Antwort ist, dass es überhaupt nicht notwendig ist, einen neuen Prozess zu erstellen. Laden Sie einfach den Befehlsprogrammcode in den Speicher und überschreiben Sie den Shell-Prozesscode! Wenn der Befehl ausgeführt wird, überschreibt der Shell-Code den Befehlsprogrammcode. Für ein einzelnes Terminal führt das System tatsächlich ständig die folgende Überschreibschleife aus (aus dem Abschnitt „Prozesssteuerung“ des Dokuments):

Dies war jedoch der Fall, bevor Fork in UNIX eingeführt wurde. Auf einem Terminal läuft immer derselbe Prozess. Mal führt er den Shell-Code aus, mal den Code eines bestimmten Kommandoprogramms. So sieht der Aufbau eines Overlay-Programms aus (das Bild stammt aus dem Buch „FreeBSD Operating System Design and Implementation“):

Allerdings war diese Logik damals noch nicht in einem Exec-Systemaufruf gekapselt, und die Befehle wurden alle explizit von jedem Prozess ausgeführt:

  • Damit die Shell das Befehlsprogramm ausführen kann, führt die Shell selbst eine Festplatten-E/A aus, um das Befehlsprogramm zu laden und sich selbst zu überschreiben.
  • Wenn die Ausführung des Befehlsprogramms abgeschlossen ist, ruft exit die interne Festplatten-E/A auf, um das Shell-Programm zu laden.

Die Exec-Logik ist Teil des Shell-Programms und da sie von allen Kommandoprogrammen verwendet wird, ist sie auch im Exit-Aufruf gekapselt.

3. Das Auftreten von Fork vor seiner Einführung in UNIX

Im Jahr 1963 schlug Melvin Conway die Fork-Idee vor, um Prozesse parallel auf mehreren Prozessoren auszuführen.

Die Thompson-Version von UNIX aus dem Jahr 1969 hatte nur zwei Shell-Prozesse, die zur Ausführung von Befehlen Overlaying-Technologie nutzten.

Bisher haben wir folgende Auftritte gesehen:

Die Thompson-Version von UNIX hat keinen Fork, kein Exec, kein Wait und der einzige bibliotheksfunktionsähnliche Exit unterscheidet sich stark vom aktuellen Exit-Systemaufruf. Offensichtlich ist die Thompson-Version von UNIX kein Multiprozesssystem, sondern nur ein einfaches Time-Sharing-System mit zwei Terminals, das ausgeführt werden kann!

1. Die Geburt des UNIX-Forks

Wie wurde Fork in UNIX eingeführt?

Dies muss mit den inhärenten Problemen der Thompson-Version von UNIX beginnen, die Overlay-Technologie verwendet. Sehen wir uns das Originalpapier an:

Um diese Probleme zu lösen, fiel Thompson eine sehr einfache Lösung ein:

  • Behalten Sie den Shell-Prozess resident, anstatt ihn zu zerstören. Wenn der Befehl ausgeführt wird, lagern Sie ihn auf die Festplatte aus.

Offensichtlich kann das Befehlsprogramm den Shell-Prozess nicht überschreiben. Die Lösung besteht darin, eine Technik namens „Swapping“ zu verwenden.

Sowohl Swapping- als auch Overlaying-Technologien sind eigentlich Lösungen für das Problem der Multiprozessnutzung von begrenztem Speicher. Der Unterschied liegt in der Richtung:

  • Unter Overlay-Technologie versteht man das Überschreiben des aktuellen Prozessspeicherabbilds durch ein anderes Prozessdatenträgerabbild.
  • Mit Swapping-Technologie wird das Speicherabbild eines Prozesses auf die Festplatte ausgelagert und das Festplattenabbild eines anderen Prozesses geladen.

Die Verwendung von Swapping zur Lösung des Überschreibungsproblems bedeutet die Erstellung eines neuen Prozesses:

  • Führt das Befehlsprogramm in einem neuen Prozess aus.

UNIX muss geändert werden und zwei Quotenprozesstabellen sind offensichtlich nicht ausreichend. Die Lösung ist natürlich nicht schwer:

Wenn es um Effizienz geht, ist Kopieren schlimmer als Kreativität. Der direkteste Weg, einen neuen Prozess zu erstellen, besteht darin, den aktuellen Shell-Prozess zu kopieren und ihn im kopierten neuen Prozess zu überschreiben. Das Befehlsprogramm überschreibt den kopierten neuen Prozess und der aktuelle Terminal-Shell-Prozess wird auf die Festplatte ausgelagert, damit er intakt bleibt.

Durch die Kombination von Overlay und Swap ist UNIX der Modernisierung einen Schritt näher!

Nachdem die Lösung zum Kopieren des aktuellen Prozesses festgelegt wurde, stellt sich als nächstes die Frage, wie der Prozess kopiert werden soll.

Kommen wir nun zurück zu den Gabeln.

Nachdem Conway die Fork-Idee vorgeschlagen hatte, war sofort ein Prototyp für die Fork-Implementierung verfügbar (wie Conway selbst sagte, schlug er nur eine Idee vor, die möglicherweise existierte, implementierte sie jedoch nicht). Project Genie gilt als eines der vollständigeren Systeme zur Fork-Implementierung.

Der Fork des Project Genie-Systems kopiert den Prozess nicht einfach blind, sondern verfügt über eine feinkörnige Kontrolle über den Fork-Prozess, beispielsweise wie viel Speicherplatz zugewiesen werden soll, welche erforderlichen Ressourcen kopiert werden sollen usw. Offensichtlich zielt der Fork von Project Genie auf Conways parallele Multiprozessorlogik ab.

Wie das alte Sprichwort sagt, ist Kopieren schlimmer als Erstellen. Wenn UNIX das Kopieren von Prozessen implementieren möchte, gibt es eine fertige Vorlage, Project Genie. Allerdings ist die Abspaltung von Project Genie zu kompliziert und zu ausgefeilt für UNIX. UNIX benötigt offensichtlich keine derart ausgefeilte Steuerung. UNIX möchte lediglich, dass der abgespaltene neue Prozess überschrieben wird, anstatt ihn parallele Logik auf mehreren Prozessoren ausführen zu lassen.

Mit anderen Worten: UNIX hat einfach die Implementierung der Kopierlogik von Fork übernommen, um etwas anderes zu erreichen.

Daher hat UNIX Fork sehr grob implementiert! Das heißt, den übergeordneten Prozess vollständig kopieren. Dies ist der Fork-Systemaufruf, den wir bis jetzt noch verwenden:

Abkürzungen nehmen:

  • Fork ist nicht dazu gedacht, den neuen Prozess zu überschreiben, warum sollten Sie das sonst tun? Fork ermöglicht es Ihnen, den Programmfluss für die parallele Verarbeitung aufzuteilen.

Der UNIX-Fork war geboren!

Lassen Sie uns die Situation vor der Geburt des UNIX-Forks Revue passieren lassen:

Werfen wir einen Blick auf die Szene nach der Geburt von Fork:

Damit begann für UNIX offiziell der Modernisierungsprozess, der bis heute andauert.

2. UNIX Fork-Exec

Zu exec gibt es nicht viel zu sagen. Es handelt sich eigentlich um eine Kapselung der oben erwähnten Überschreiblogik. Danach müssen Programmierer die Überschreiblogik nicht mehr selbst schreiben, sondern können den exec-Systemaufruf direkt aufrufen.

So entstand die klassische UNIX-Fork-Exec-Sequenz.

3. UNIX-Fork/Exec/Exit/Warten

Es ist erwähnenswert, dass sich die Semantik von „exit“ nach der Einführung von „fork“ in UNIX dramatisch geändert hat.

Da es in der ursprünglichen Thompson-Version von UNIX aus dem Jahr 1969 nur einen Prozess pro Terminal gab, bedeutete dies, dass das Überschreiben immer zwischen dem Shell-Programm und einem Befehlsprogramm erfolgte:

  • Die Shell führt Befehl A aus: Befehlsprogramm A überschreibt den Shell-Code im Speicher.
  • Befehl A wird ausgeführt: Die Shell überschreibt den Speichercode des abgeschlossenen Befehls A.

Nach der Einführung von Fork überschrieb die Shell beim Ausführen eines Befehls zwar immer noch den gegabelten Shell-Unterprozess mit einem bestimmten Befehlsprogramm, aber wenn der Befehl ausgeführt wurde, konnte die Exit-Logik der Shell nicht mehr erlauben, das aktuelle Befehlsprogramm zu überschreiben, da die Shell nie beendet wurde. Es wurde einfach als übergeordneter Prozess auf die Festplatte ausgelagert (später, als der Speicher groß genug war, um mehrere Prozesse aufzunehmen, war nicht einmal das Auslagern erforderlich).

Wer wird also let beenden, um den aktuellen Prozess zu überschreiben?

Die Antwort lautet: Es muss nicht überschrieben werden. Der wörtlichen Bedeutung von exit zufolge muss es sich lediglich selbst beenden.

Gemäß dem Prinzip der Verwaltung eigener Ressourcen muss exit lediglich die Ressourcen bereinigen, die ihm zugewiesen wurden. Bereinigen Sie beispielsweise Ihren eigenen Speicherplatz und einige andere Datenstrukturen.

Für den untergeordneten Prozess selbst gilt: Da er vom übergeordneten Prozess generiert wird, wird er auch vom übergeordneten Prozess verwaltet und freigegeben. Somit entstand formal der klassische vierteilige Satz von UNIX-Prozessmanagementelementen:

Dies ist das Ende dieses Artikels über den versteckten Overhead von Unix/Linux-Forks. Weitere Informationen zu Unix/Linux-Forks finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, Sie werden 123WORDPRESS.COM auch in Zukunft unterstützen! , ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Detaillierte Erläuterung des Linux-Indexknoten-Inode
  • Linux: Kein Speicherplatz mehr auf Gerät 500 – Fehler aufgrund voller Inodes
  • Details zur Linux-Netzwerkeinrichtung
  • So implementieren Sie mit MyCat die Lese-/Schreibtrennung von MySQL-Master und -Slave unter Linux
  • Informationen zu UDP in Linux
  • Linux-Swap-Partition (ausführliche Erklärung)
  • C++-Netzwerkprogrammierung unter Linux, Epoll-Technologie und IOCP-Modell unter Windows
  • Wie viele Ports kann ein Linux-Server maximal öffnen?
  • Details zu Linux-Dateideskriptoren, Dateizeigern und Inodes

<<:  Detaillierte Erklärung der in JavaScript integrierten Objekte, Mathematik und Zeichenfolgen

>>:  HTML-Code für Multiheader-Tabellen

Artikel empfehlen

Codebeispiele für allgemeine Vorgänge bei der Docker-Image-Verwaltung

Spiegelung ist auch eine der Kernkomponenten von ...

Docker-Verbindung – MongoDB-Implementierungsprozess und Codebeispiele

Nachdem der Container gestartet wurde Melden Sie ...

So legen Sie die Tabellenbreite in IE8 und Chrome fest

Wenn die oben genannten Einstellungen in IE8 und C...

Grundkenntnisse in HTML: ein erstes Verständnis von Webseiten

HTML ist die Abkürzung für Hypertext Markup Langua...

Lernen Sie einfach verschiedene SQL-Joins

Mit der SQL JOIN-Klausel können Zeilen aus zwei o...

So aktualisieren Sie die Knotenversion unter CentOs manuell

1. Suchen Sie das entsprechende NodeJS-Paket unte...

Detaillierte Erklärung der Nginx-Konfigurationsdatei

Die Hauptkonfigurationsdatei von Nginx ist nginx....

Verwendung und Verständnis von MySQL-Triggern

Inhaltsverzeichnis 1. Was ist ein Auslöser? 2. Er...

Implementierungscode zur Installation von vsftpd in Ubuntu 18.04

Installieren Sie vsftpd $ sudo apt-get installier...