Betriebssysteme: Drei einfache Stücke. Teil 3: Prozess-API (Übersetzung)

Einführung in Betriebssysteme

Hallo, Habr! Ich möchte Ihnen eine Serie von Übersetzungen einer interessanten Literatur — OSTEP — vorstellen. In diesem Material wird die Funktionsweise von Unix-ähnlichen Betriebssystemen ziemlich gründlich behandelt, insbesondere die Arbeit mit Prozessen, verschiedenen Planern, Speicher und anderen ähnlichen Komponenten, die ein modernes Betriebssystem ausmachen. Das Original aller Materialien können Sie hier ansehen hier. Bitte beachten Sie, dass die Übersetzung unprofessionell (relativ frei) durchgeführt wurde, aber ich hoffe, dass ich den allgemeinen Sinn bewahrt habe.

Die Laborarbeiten zu diesem Thema finden Sie hier:

Andere Teile:

Und Sie können auch meinen Kanal auf Telegramm =)

Alarm! zu dieser Vorlesung gibt es ein Labor! Siehe GitHub

Process API

Betrachten wir das Beispiel zur Erstellung eines Prozesses in einem UNIX-System. Dies erfolgt über zwei Systemaufrufe. fork() und exec().

Aufruf von fork()

Betriebssysteme: Drei einfache Stücke. Teil 3: Prozess-API (Übersetzung)

Betrachten wir ein Programm, das den Aufruf von fork() durchführt. Das Ergebnis seiner Ausführung wird wie folgt sein.

Betriebssysteme: Drei einfache Stücke. Teil 3: Prozess-API (Übersetzung)

Zunächst betreten wir die Funktion main() und führen die Ausgabe einer Zeichenkette auf dem Bildschirm aus. Die Zeichenkette enthält die Prozess-ID, die im Original als PID oder Prozessidentifikator bezeichnet wird. Diese ID wird in UNIX verwendet, um auf den Prozess zuzugreifen. Der nächste Befehl wird den Aufruf von fork() ausführen. In diesem Moment wird eine nahezu exakte Kopie des Prozesses erstellt. Für das OS sieht es so aus, als wären zwei Kopien des gleichen Programms gestartet, die ihrerseits die Funktion fork() verlassen werden. Der neu geschaffene Nachfolgeprozess (im Verhältnis zum ihm zugehörigen Elterprozess) wird nicht mehr ab der Funktion main() ausgeführt. Es ist wichtig zu beachten, dass der Nachfolgeprozess keine exakte Kopie des Elterprozesses ist; insbesondere hat er seinen eigenen Adressraum, eigene Register, seinen eigenen Zeiger auf die auszuführenden Instruktionen und ähnliches. Das bedeutet, dass der Rückgabewert des Aufrufers der Funktion fork() unterschiedlich sein wird. So erhält der Elterprozess als Rückgabewert die PID des Kindprozesses, während das Kind den Wert 0 zurückbekommt. Anhand dieser Rückgabewerte können die Prozesse später getrennt werden, und jeder kann seine spezifische Aufgabe ausführen. Die Ausführung dieses Programms ist dabei nicht strikt definiert. Nach der Aufteilung in zwei Prozesse beginnt das OS, sie zu überwachen und ihre Ausführung zu planen. Wenn es auf einem Einzelkernprozessor ausgeführt wird, wird einer der Prozesse, in diesem Fall der Elterprozess, die Ausführung fortsetzen, bevor der Nachfolgeprozess die Kontrolle übernimmt. Bei einem erneuten Start kann die Situation anders sein.

Aufruf von wait()

Betriebssysteme: Drei einfache Stücke. Teil 3: Prozess-API (Übersetzung)

Betrachten wir das folgende Programm. In diesem Programm sorgt der Vorhandensein des Aufrufs wait() dafür, dass der Elterprozess immer auf den Abschluss des Nachfolgeprozesses wartet. In diesem Fall erhalten wir eine strikte Ausgabe des Textes auf dem Bildschirm.

Betriebssysteme: Drei einfache Stücke. Teil 3: Prozess-API (Übersetzung)

Aufruf von exec()

Betriebssysteme: Drei einfache Stücke. Teil 3: Prozess-API (Übersetzung)

Betrachten wir den Aufruf exec(). Dieser Systemaufruf ist nützlich, wenn wir ein völlig anderes Programm starten möchten. Hier werden wir aufrufen execvp() zum Starten des Programms wc, das ein Wortzählprogramm ist. Was passiert bei dem Aufruf von exec()? Dem Aufruf werden als Argumente der Name der ausführbaren Datei und einige Parameter übergeben. Danach erfolgt das Laden des Codes und der statischen Daten aus dieser ausführbaren Datei und das Überschreiben des eigenen Segments mit dem Code. Die anderen Speicherbereiche, wie der Stack und der Heap, werden neu initialisiert. Danach führt das Betriebssystem einfach das Programm aus, indem es ihm eine Menge von Argumenten übergibt. So haben wir keinen neuen Prozess erstellt, sondern das aktuelle gestartete Programm in ein anderes gestartetes Programm transformiert. Nach dem Aufruf von exec() hat der Kindprozess den Eindruck, dass das ursprüngliche Programm gar nicht gestartet wurde.

Diese Komplexität beim Start ist für die Unix-Shell vollkommen normal und ermöglicht es dieser Shell, Code nach dem Aufruf fork(), aber vor dem Aufruf exec()auszuführen. Ein Beispiel für solchen Code könnte die Anpassung der Umgebung der Shell an die Bedürfnisse des zu startenden Programms sein, bevor es direkt gestartet wird.

Shell — ist nur ein Benutzerprogramm. Es zeigt Ihnen die Eingabeaufforderung und wartet, bis Sie etwas eingeben. In den meisten Fällen, wenn Sie den Namen des Programms dort eingeben, findet die Shell dessen Speicherort, ruft die Methode fork() auf und ruft dann, um einen neuen Prozess zu erstellen, einen der exec()-Typen auf und wartet auf dessen Ausführung mit dem Aufruf von wait(). Wenn der Kindprozess abgeschlossen ist, kehrt die Shell aus dem Aufruf von wait() zurück und zeigt die Eingabeaufforderung erneut an und wartet auf die Eingabe des nächsten Befehls.

Die Trennung von fork() & exec() ermöglicht es der Shell, Folgendes zu tun, zum Beispiel:
wc file > new_file.

In diesem Beispiel wird die Ausgabe des Programms wc in eine Datei umgeleitet. Die Art und Weise, wie die Shell dies erreicht, ist recht einfach: Beim Erstellen des Kindprozesses schließt die Shell den Standardausgabestrom und öffnet die Datei exec()new_file , sodass die gesamte Ausgabe des später gestarteten Programmsin die Datei anstelle des Bildschirms umgeleitet wird. wc wird in die Datei umgeleitet, anstatt auf dem Bildschirm angezeigt zu werden.

Unix-Pipeline wird ähnlich umgesetzt, mit dem Unterschied, dass sie den Aufruf pipe() verwenden. In diesem Fall wird der Ausgabe-Stream des Prozesses an die Pipe-Warteschlange angeschlossen, die im Kernel liegt, und der Eingabe-Stream eines anderen Prozesses wird daran angeschlossen.

Quelle: habr.com

60GB SSD 8Gb DDR4