Modul 2: Prozesse, Threads und die galaktische Bürokratie

1. Einleitung: Der Funke des Lebens

Liebe Anhalter, ein auf einer Festplatte liegendes Programm ist nicht mehr als eine leblose Ansammlung von Nullen und Einsen – wie ein schlafender Androide im Frachtraum eines Raumschiffs. Erst das Betriebssystem haucht diesem Code Leben ein.

In der Informatik definieren wir dieses „lebende“ Programm als Prozess[cite: 1]. Ein Programm ist lediglich die statische Lösung einer Programmieraufgabe, die sich klassischerweise in zwei problemorientierte Teile gliedert: Die Befehle (den Codebereich oder Textbereich) und die Programmdaten (den Datenbereich)[cite: 1].

Ein Prozess hingegen ist die hochdynamische Ausführung dieses Codes. Er besitzt einen aktuellen Zustand, einen Programmzähler (Program Counter), der auf den nächsten auszuführenden Befehl zeigt, CPU-Register und einen Stack (Stapelspeicher) für lokale Variablen und Funktionsaufrufe.

1.1 Der Process Control Block (PCB)

Wie behält der Kernel (z. B. unter Linux) den Überblick über Tausende dieser Prozesse? Er nutzt eine gewaltige bürokratische Datenstruktur, den sogenannten Process Control Block (PCB)[cite: 1]. In der Linux-Welt kennen wir diese Struktur als task_struct. Der PCB ist quasi die Personalakte des Prozesses und enthält alle relevanten Informationen[cite: 1]:

2. Threads: Die leichtgewichtigen Arbeiter

Prozesse sind ressourcenhungrig. Wenn ein Prozess eine Aufgabe parallelisieren möchte, könnte er einen komplett neuen Prozess starten (Klonen via fork()). Das kostet jedoch viel Speicher und Zeit, da das OS den gesamten PCB und den virtuellen Adressraum kopieren muss.

Die elegante Lösung des Universums lautet: Threads. Ein Thread ist ein „kleines oder leichtes Programm“[cite: 1]. Er stellt eine Erweiterung des klassischen Prozessmodells dar[cite: 1]. Der entscheidende physikalische und logische Unterschied: Mehrere Threads teilen sich denselben Betriebsmittel-Pool und den Prozesskontext (insbesondere den gemeinsamen Adressraum für Code und globale Daten) eines Prozesses[cite: 1]. Allerdings – und das ist essenziell für die fehlerfreie Programmausführung – besitzt jeder Thread seinen eigenen Stapelspeicher (Stack) und einen eigenen Programmzähler[cite: 1].

Warum sind Threads so schnell? Da sich Threads den Speicheradressraum teilen, muss bei einem Wechsel zwischen zwei Threads desselben Prozesses (Thread Context Switch) der TLB (Translation Look-aside Buffer) der MMU nicht geleert (geflusht) werden. Das spart extrem viel Rechenzeit. Der Nachteil? Da es keinen Speicherschutz zwischen Threads eines Prozesses gibt, kann ein wild gewordener Thread die Daten eines anderen Threads überschreiben. Programmierer müssen hier zwingend mit Synchronisationsmechanismen (wie Mutexen) arbeiten.

3. Das Prozessmodell: Zustände eines digitalen Lebens

Ein Universalprozessor (CPU) kann physikalisch immer nur exakt einen Prozess zur gleichen Zeit verarbeiten[cite: 1]. Wir haben aber Hunderte Prozesse, die gefühlt gleichzeitig laufen. Das Betriebssystem simuliert diesen Anschein von Parallelität, indem es Prozesse nur teilweise ausführt, sie unterbricht, später wieder aufsetzt und fortführt[cite: 1].

Dieses ständige Wechseln erfordert eine strikte Definition der Lebenszustände. Das klassische Prozessmodell definiert folgende Phasen[cite: 1]:

Prozessübergänge

4. Dispatcher und Kontextwechsel (Context Switch)

Der Wechsel zwischen diesen Zuständen ist reine Magie. Das Modul, das diese Magie ausführt, heißt Dispatcher[cite: 1]. Der Dispatcher dient dazu, bei einem sogenannten Kontextwechsel dem derzeit aktiven Prozess die CPU zu entziehen und anschließend dem nächsten, wartenden Prozess die CPU zuzuteilen[cite: 1].

Was passiert bei einem Kontextwechsel exakt? 1. Die CPU erhält einen Interrupt (z. B. vom Timer-Chip). 2. Der Dispatcher stoppt den aktuellen Prozess. 3. Er sichert den kompletten aktuellen Zustand der CPU (alle Register, Programmzähler, Status-Flags) in den PCB des gerade gestoppten Prozesses. 4. Er lädt den zuvor gesicherten Zustand (aus dem PCB) des nächsten Prozesses in die Hardware-Register der CPU. 5. Er leert (falls nötig) den TLB, damit der neue Prozess nicht fälschlicherweise in fremdem Speicher liest. 6. Er springt zum Programmzähler des neuen Prozesses und lässt diesen weiterlaufen.

Dies ist ein purer Overhead. Ein System, das zu oft den Kontext wechselt (Thrashing), verbringt mehr Zeit mit der reinen Verwaltung der Prozesse als mit der tatsächlichen Berechnung von Nutzdaten.

5. Prozess-Scheduling: Wer darf rechnen?

Die Entscheidung, welcher Prozess als Nächstes an die Reihe kommt (wer den Zustand „ready“ verlässt und „running“ wird), trifft der Scheduler im Rahmen der Warteschlangenorganisation[cite: 1]. Der Scheduler bereitet die Operation „assign“ vor, woraufhin der Dispatcher aufgerufen wird[cite: 1].

Wir unterscheiden zwei grundlegende Architektur-Typen von Schedulern[cite: 1]:

Zudem unterscheidet man in der Theorie zwischen work-conserving (das Umschalten zwischen Prozessen nimmt nur eine vernachlässigbar geringe Zeit in Anspruch) und nicht work-conserving Strategien[cite: 1].

5.1 Scheduling-Strategien in der Praxis

Wie wählt der Scheduler aus? Das Skript definiert hier die absoluten Grundlagen, die jeder Systemadministrator verstanden haben muss[cite: 1]:

A. First come, first served (FCFS): Wer zuerst kommt, wird zuerst bedient[cite: 1]. Kommen zwei Prozesse exakt gleichzeitig an, entscheidet der Zufall[cite: 1]. Das ist brutal ineffizient für interaktive Desktop-Systeme. Ein winziger Tastatur-Treiber müsste warten, bis ein gigantischer Datenbank-Export abgeschlossen ist.

B. Round Robin (Zeitscheibenverfahren): Die wahrscheinlich bekannteste Methode. Jeder Prozess erhält eine feste, kleine Zeitspanne (das Quantum) zugeordnet[cite: 1]. Nach Ablauf dieser Zeitspanne wird der Prozess verdrängt, hinten in die Warteschlange (Ringpuffer) eingereiht, und der nächste Prozess erhält die CPU (zyklische Zuteilung)[cite: 1]. In dieser reinen Form haben alle Prozesse die gleiche Priorität[cite: 1]. * Das Problem der Quantenlänge: Ist das Quantum zu groß, sinkt die Interaktivität (das System reagiert träge). Ist es zu klein, verschwendet das System seine gesamte Rechenzeit mit Kontextwechseln. Die Zeitspanne kann konstant sein oder dynamisch variieren, abhängig von der aktuellen Prozessorbelastung[cite: 1].

Das Round Robin Zeitscheibenverfahren

C. Prioritätssteuerung: Jedem Prozess wird eine feste oder dynamische Priorität zugeordnet[cite: 1]. Ein Prozess mit niedrigerer Priorität darf erst auf die CPU, wenn absolut kein Prozess höherer Priorität mehr auf Abarbeitung wartet[cite: 1]. Ein neu eintreffender, hochpriorisierter Prozess verdrängt einen aktiven, niedrigpriorisierten Prozess sofort[cite: 1]. * Gefahr: Das sogenannte „Aushungern“ (Starvation). Wenn ständig neue, wichtige Prozesse eintreffen, kommen unwichtige Hintergrund-Dienste nie an die Reihe. (Gegenmittel ist hier das Aging: Die Priorität eines wartenden Prozesses wird über die Zeit langsam erhöht).

6. Die Werkzeuge des Kernels: Prozessoperationen

Das Betriebssystem fungiert als allmächtiger Verwalter. Es besitzt eine Reihe von fundamentalen System-Operationen zur Steuerung und Kontrolle des Prozess-Ökosystems[cite: 1]. Zu diesen Operationen gehören unter anderem[cite: 1]:

Wenn ihr auf einem Linux-Server den Befehl ps aux oder htop eingebt, schaut ihr direkt in die Ergebnisse dieser Operationen. Ihr seht die PIDs, den Status, den Speicherverbrauch und die vom Scheduler zugewiesene Rechenzeit in Echtzeit.


Quellen & Materialien
Alle fachlichen Grundlagen dieses Moduls basieren auf dem Skript „Betriebssysteme“ zur Erlangung des Grades Bachelor (German-Baltic Management School).[cite: 1]