Betriebssysteme: Drei einfache Teile. Teil 3: Prozess-API (Übersetzung)

Einführung in Betriebssysteme

Hey Habr! Ich möchte Sie auf eine Reihe von Artikeln und Übersetzungen einer meiner Meinung nach interessanten Literatur aufmerksam machen – OSTEP. In diesem Material wird ausführlich auf die Arbeit unixähnlicher Betriebssysteme eingegangen, nämlich auf die Arbeit mit Prozessen, verschiedenen Schedulern, Speicher und anderen ähnlichen Komponenten, aus denen ein modernes Betriebssystem besteht. Das Original aller Materialien können Sie hier einsehen hier. Bitte beachten Sie, dass die Übersetzung unprofessionell (ziemlich frei) angefertigt wurde, aber ich hoffe, dass ich die allgemeine Bedeutung beibehalten habe.

Laborarbeiten zu diesem Thema finden Sie hier:

Andere teile:

Sie können auch auf meinem Kanal vorbeischauen Telegramm =)

Alarm! Für diese Vorlesung gibt es ein Labor! Suchen Github

Prozess-API

Schauen wir uns ein Beispiel für die Erstellung eines Prozesses in einem UNIX-System an. Dies geschieht durch zwei Systemaufrufe Gabel() и exec ().

Rufen Sie fork() auf

Betriebssysteme: Drei einfache Teile. Teil 3: Prozess-API (Übersetzung)

Stellen Sie sich ein Programm vor, das einen fork()-Aufruf durchführt. Das Ergebnis seiner Ausführung wird wie folgt sein.

Betriebssysteme: Drei einfache Teile. Teil 3: Prozess-API (Übersetzung)

Zuerst geben wir die Funktion main() ein und geben den String auf dem Bildschirm aus. Die Zeile enthält die Prozesskennung, die im Original aufgerufen wird PID oder Prozesskennung. Dieser Bezeichner wird in UNIX verwendet, um auf einen Prozess zu verweisen. Der nächste Befehl ruft fork() auf. An diesem Punkt wird eine nahezu exakte Kopie des Prozesses erstellt. Für das Betriebssystem sieht es so aus, als würden zwei Kopien desselben Programms auf dem System ausgeführt, was wiederum die Funktion fork() beendet. Der neu erstellte untergeordnete Prozess (in Bezug auf den übergeordneten Prozess, der ihn erstellt hat) wird ab der Funktion main() nicht mehr ausgeführt. Es sollte beachtet werden, dass ein untergeordneter Prozess keine exakte Kopie des übergeordneten Prozesses ist; insbesondere verfügt er über einen eigenen Adressraum, eigene Register, einen eigenen Zeiger auf ausführbare Anweisungen und dergleichen. Daher wird der an den Aufrufer der Funktion fork() zurückgegebene Wert unterschiedlich sein. Insbesondere erhält der übergeordnete Prozess den PID-Wert des untergeordneten Prozesses als Rückgabe und der untergeordnete Prozess erhält einen Wert gleich 2. Mithilfe dieser Rückgabecodes können Sie dann Prozesse trennen und jeden von ihnen dazu zwingen, seine eigene Arbeit zu erledigen . Die Ausführung dieses Programms ist jedoch nicht streng definiert. Nach der Aufteilung in zwei Prozesse beginnt das Betriebssystem, diese zu überwachen und ihre Arbeit zu planen. Bei Ausführung auf einem Single-Core-Prozessor arbeitet einer der Prozesse, in diesem Fall der übergeordnete Prozess, weiter und der untergeordnete Prozess übernimmt dann die Kontrolle. Beim Neustart kann die Situation anders sein.

Rufen Sie wait() auf

Betriebssysteme: Drei einfache Teile. Teil 3: Prozess-API (Übersetzung)

Betrachten Sie das folgende Programm. In diesem Programm aufgrund des Vorhandenseins eines Anrufs warten() Der übergeordnete Prozess wartet immer auf den Abschluss des untergeordneten Prozesses. In diesem Fall erhalten wir eine streng definierte Textausgabe auf dem Bildschirm

Betriebssysteme: Drei einfache Teile. Teil 3: Prozess-API (Übersetzung)

exec()-Aufruf

Betriebssysteme: Drei einfache Teile. Teil 3: Prozess-API (Übersetzung)

Betrachten Sie die Herausforderung exec (). Dieser Systemaufruf ist nützlich, wenn wir ein völlig anderes Programm ausführen möchten. Hier rufen wir an execvp() um das WC-Programm auszuführen, bei dem es sich um ein Wortzählprogramm handelt. Was passiert, wenn exec() aufgerufen wird? Diesem Aufruf werden der Name der ausführbaren Datei und einige Parameter als Argumente übergeben. Anschließend werden der Code und die statischen Daten aus dieser ausführbaren Datei geladen und das eigene Segment mit dem Code überschrieben. Die verbleibenden Speicherbereiche wie Stack und Heap werden neu initialisiert. Danach führt das Betriebssystem einfach das Programm aus und übergibt ihm eine Reihe von Argumenten. Wir haben also keinen neuen Prozess erstellt, sondern einfach das aktuell laufende Programm in ein anderes laufendes Programm umgewandelt. Nach der Ausführung des exec()-Aufrufs im Nachkommen sieht es so aus, als ob das ursprüngliche Programm überhaupt nicht ausgeführt wurde.

Diese Startkomplikation ist für eine Unix-Shell völlig normal und ermöglicht es dieser Shell, nach dem Aufruf Code auszuführen Gabel(), aber vor dem Anruf exec (). Ein Beispiel für einen solchen Code wäre die Anpassung der Shell-Umgebung an die Anforderungen des zu startenden Programms, bevor es gestartet wird.

Schale - nur ein Benutzerprogramm. Sie zeigt Ihnen die Einladungszeile und wartet darauf, dass Sie etwas hineinschreiben. Wenn Sie dort in den meisten Fällen den Namen eines Programms schreiben, findet die Shell dessen Speicherort, ruft die Methode fork() auf und ruft dann eine Art von exec() auf, um einen neuen Prozess zu erstellen und darauf zu warten, dass er mit a abgeschlossen wird wait()-Aufruf. Wenn der untergeordnete Prozess beendet wird, kehrt die Shell vom Aufruf von wait() zurück, gibt die Eingabeaufforderung erneut aus und wartet auf die Eingabe des nächsten Befehls.

Durch die Aufteilung von fork() und exec() kann die Shell beispielsweise Folgendes tun:
wc-Datei > neue_Datei.

In diesem Beispiel wird die Ausgabe des WC-Programms in eine Datei umgeleitet. Die Art und Weise, wie die Shell dies erreicht, ist recht einfach: Sie erstellt vor dem Aufruf einen untergeordneten Prozess exec (), schließt die Shell die Standardausgabe und öffnet die Datei neue Datei, also alle Ausgaben des weiteren laufenden Programms wc wird in eine Datei statt auf einen Bildschirm umgeleitet.

Unix-Pipe werden auf ähnliche Weise implementiert, mit dem Unterschied, dass sie einen Pipe()-Aufruf verwenden. In diesem Fall wird der Ausgabestrom des Prozesses mit einer Pipe-Warteschlange im Kernel verbunden, mit der der Eingabestrom eines anderen Prozesses verbunden wird.

Source: habr.com

Kommentar hinzufügen