| FOTO | AUTO | EDV | AUDIO |

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]:

  • Die eindeutige Prozess-ID (PID)[cite: 1].
  • Den aktuellen Zustand des Prozesses[cite: 1].
  • Die Inhalte der CPU-Register (damit der Prozess nach einer Unterbrechung exakt dort weitermachen kann, wo er aufgehört hat).
  • Eine Liste der zugewiesenen Betriebsmittel (z. B. offene Dateien, Netzwerk-Sockets)[cite: 1].
  • Speichermanagement-Informationen (welche RAM-Bereiche gehören zum Prozess).
  • Scheduling-Informationen und Prioritäten[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]:

  • WARTEND (bzw. rechenbereit): Der Prozess befindet sich im Arbeitsspeicher und wartet lediglich auf die Zuteilung eines Prozessors[cite: 1]. Er hat alle benötigten Betriebsmittel – bis auf die CPU.
  • LAUFEND (rechnend): Der Prozess ist aktuell einem Prozessor zugeordnet, und seine Befehle werden von der CPU in Hardware ausgeführt[cite: 1]. Pro CPU-Kern kann immer nur genau ein Prozess im Zustand LAUFEND sein.
  • UNTERBROCHEN: Der Prozess wurde durch das Betriebssystem (oder einen anderen Prozess) hart unterbrochen[cite: 1]. Er wird zeitweise nicht mehr in den Zustand LAUFEND versetzt, bis er wieder aufgesetzt und in den Zustand WARTEND überführt wird[cite: 1].
  • BLOCKIERT: Der Prozess kann nicht weiterarbeiten, weil er auf ein externes Ereignis wartet (z. B. auf Daten von der langsamen Festplatte oder auf eine Netzwerkanfrage)[cite: 1]. In dieser Zeit gibt er die CPU freiwillig ab, um Ressourcen zu sparen. Sobald das Ereignis eintritt (meist gemeldet durch einen Hardware-Interrupt), wechselt er wieder in den Zustand WARTEND.
  • SUSPENDIERT: Der Prozess wurde komplett aus dem Hauptspeicher entfernt und auf die Festplatte (Swap) ausgelagert[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]:

  • Nicht unterbrechende (non-preemptive / kooperative) Scheduler: Ein Prozess behält die CPU so lange, bis er sie von sich aus wieder freigibt oder bis er durch eine I/O-Anforderung blockiert[cite: 1]. (Historisches Beispiel: Windows 3.11. Ein abgestürztes Programm riss das ganze System in den Abgrund, weil es die CPU nie wieder hergab).
  • Unterbrechende (preemptive) Scheduler: Die CPU wird dem Prozess von vornherein nur für eine bestimmte Zeitspanne zugeteilt[cite: 1]. Ist die Zeit abgelaufen, entzieht das Betriebssystem (unterstützt durch einen Hardware-Timer-Interrupt) dem Prozess die CPU gewaltsam[cite: 1]. Moderne Systeme wie Linux oder Windows 11 sind strikt preemptiv.

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]:

  • Erzeugen eines Prozesses: Laden eines Programms von der Festplatte in den RAM[cite: 1].
  • Allokieren: Zuweisung des benötigten Speichers[cite: 1].
  • Verwaltung: Vergabe einer eindeutigen Prozess-ID (PID) und Anlegen eines PCB in der globalen Prozesstabelle[cite: 1].
  • Ressourcen: Reservieren der benötigten Betriebsmittel (z. B. I/O-Geräte)[cite: 1].
  • Priorisierung: Vergabe und dynamische Änderung einer Priorität[cite: 1].
  • Statuswechsel: Blockieren eines Prozesses (bei I/O), Aufwecken eines blockierten Prozesses (wenn das I/O-Event beendet ist), Suspendieren und Wiederaufnehmen eines Prozesses[cite: 1].
  • Terminierung: Löschen eines Prozesses[cite: 1]. Dies beinhaltet das Löschen aller Einträge aus den Systemtabellen, die strikte Freigabe von Speicher und Ressourcen (z. B. Schließen aller offenen Dateideskriptoren) sowie im Unix-Kontext oftmals das kaskadierende Löschen aller Abkömmlinge (Kindprozesse, Enkelprozesse)[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]