Modul 3: Prozesskommunikation, Hierarchien & Deadlocks – Der galaktische Stillstand
1. Einleitung: Kein Prozess lebt im luftleeren Raum
Willkommen zu Tag 3! Wir wissen nun, dass das Betriebssystem das Universum in handliche, kleine Illusionen namens Prozesse unterteilt. Doch ein Prozess allein ist wie ein Vogonisches Raumschiff ohne Formular für den Hyperraumantrieb – er kommt nicht weit.
In modernen Multitasking-Betriebssystemen teilen sich laufende Prozesse die vorhandenen Ressourcen[cite: 1]. Bei gleichzeitigem Zugriff zweier Prozesse auf dieselbe Ressource kann es zu schwer lokalisierbaren Fehlern kommen[cite: 1]. Prozesse müssen koordiniert werden – sie müssen eine Hierarchie bilden, miteinander reden und dabei aufpassen, dass sie sich nicht in einem unendlichen Deadlock gegenseitig aus dem Universum aussperren.
2. Die Genealogie der Prozesse: Eltern, Kinder und Waisen
Prozesse entstehen in der Linux/Unix-Welt nicht aus dem Nichts. Sie werden geboren – oder vielmehr geklont.
Ein Prozess kann einen neuen Prozess starten (in Unix klassischerweise durch die Systemaufrufe fork oder spawn)[cite: 1]. Wir nennen den erzeugenden Prozess den Elternprozess (Parent Process) und den neu erschaffenen Prozess den Kindprozess (Child Process)[cite: 1].
Das Skript definiert hierzu strenge Stammbaum-Regeln[cite: 1]:
- Jeder Kindprozess hat exakt einen Elternprozess[cite: 1].
- Ein Elternprozess kann mehrere Kindprozesse besitzen[cite: 1].
- Eltern- und Kindprozess können miteinander kommunizieren[cite: 1].
- Wird ein Elternprozess beendet, beenden sich normalerweise auch alle seine Kindprozesse[cite: 1].
Praxis-Szenario: Wenn ihr euch per SSH auf einem RHEL 9.2 Server einloggt, startet der SSH-Daemon (sshd) einen Kindprozess für eure Shell (z.B. bash). Tippt ihr dort top ein, ruft die Bash fork() auf, erzeugt ein Kind und überschreibt dessen Speicherbereich via exec() mit dem Programm top. Schießt ihr eure Bash (den Elternprozess) ab, stirbt auch euer top-Prozess.
2.1 Identität und Prozessrechte
Jeder dieser Prozesse benötigt Berechtigungen, um auf Dateien oder Sockets zuzugreifen. Im Verlauf seiner Existenz besitzt ein Prozess zwei wesentliche Benutzeridentifikationen[cite: 1]:
- Die reale Benutzer-ID: Sie bezeichnet den Benutzer, der für den Ablauf des Prozesses verantwortlich ist, und sollte konstant bleiben[cite: 1]. (Das seid in der Regel ihr, wenn ihr das Programm startet).
- Die effektive Benutzer-ID: Sie bestimmt die momentanen Rechte des Prozesses in Bezug auf Dateizugriffe und kann sich beliebig oft ändern[cite: 1].
Wie ändert sich diese effektive ID? Wenn ein Programm besondere Rechte braucht (z.B. um das eigene Passwort in der geschützten Datei /etc/shadow zu ändern), greift ein genialer Unix-Mechanismus: das setuid-Bit. Falls die Zugriffsrechte einer ausführbaren Programmdatei das setuid-Bit enthalten, wird die effektive Benutzer-ID des laufenden Prozesses einfach mit der Eigentümerkennung des Programms überschrieben[cite: 1]. Der Prozess hat dann plötzlich die Rechte des Eigentümers (z.B. root), solange er läuft[cite: 1].
3. Inter-Process Communication (IPC): Wie Prozesse miteinander sprechen
Wir wissen, dass das Betriebssystem den RAM mithilfe der Memory Management Unit (MMU) virtuell zerlegt. Jeder Prozess lebt in seinem völlig isolierten, virtuellen Adressraum. Das ist hervorragend für die Sicherheit, aber ein Albtraum für die Kommunikation.
Für eine intelligente und fehlerfreie Synchronisierung paralleler Prozesse ist eine effektive Kommunikation zwingend notwendig[cite: 1]. Unser Handout definiert hierfür mehrere primäre Kommunikationskanäle[cite: 1]:
- Gemeinsame Dateien: Prozesse schreiben Daten in eine Datei auf der Festplatte, die von anderen Prozessen gelesen wird[cite: 1]. (Langsam, aber persistent).
- Gemeinsame Speicherbereiche (Shared Memory): Prozesse können gemeinsame Datenbereiche und Variablen im RAM anlegen und gemeinsam nutzen[cite: 1]. Das ist der schnellste IPC-Weg, erfordert aber höchste Disziplin bei der Synchronisierung.
- Pipes: Direkte, unidirektionale Datenkanäle zwischen zwei Prozessen[cite: 1]. Ein Prozess schreibt Daten in den Kanal, der andere liest sie wieder aus (FIFO-Prinzip)[cite: 1].
- Signale: Ereignisse, die einen Software-Interrupt erzeugen[cite: 1]. (Wenn ihr am Terminal
STRG+Cdrückt, sendet das OS einSIGINT-Signal an den Prozess). - Nachrichten (Message Queues): Werden vom Betriebssystem verwaltet und stellen ein gemeinsam nutzbares Transportmittel (z. B. Mailboxen) zur Verfügung, auf die Prozesse über Systemaufrufe zugreifen[cite: 1].
- Streams: Logisch gesehen haben sie dieselbe Aufgabe wie lokale Pipes, dienen aber der Kommunikation über Rechnernetze[cite: 1].
- Prozedurfernaufrufe (RPC - Remote Procedure Call): Ein Prozess ruft eine Prozedur auf, die in einem völlig anderen Prozess (oft auf einem anderen Server) angesiedelt ist – also weit über seine eigenen Adressgrenzen hinweg[cite: 1]. Dies ist die absolute Basis für Client-Server-Beziehungen in verteilten Systemen[cite: 1].
4. Synchronisation: Die Kunst des Ausweichens
Wenn zwei Prozesse über Shared Memory miteinander kommunizieren, entsteht ein Risiko. Stellen wir uns vor, Prozess A und Prozess B wollen beide den Status eines Checkmk-Agenten in denselben Speicherbereich schreiben. Wenn beide gleichzeitig schreiben, entsteht Datensalat.
Wegen dieser Abhängigkeiten müssen kooperierende Multitaskingprozesse synchronisiert werden[cite: 1]. Das Skript unterscheidet zwei Klassen von Abhängigkeiten[cite: 1]:
- Zeitkritisch: Die Prozesse konkurrieren um die Nutzung gemeinsamer, exklusiver Ressourcen[cite: 1] (Mutual Exclusion).
- Kritisch: Die Prozesse sind voneinander datenabhängig[cite: 1] (Prozess B kann erst rechnen, wenn Prozess A sein Ergebnis geliefert hat).
5. Deadlocks: Der absolute Stillstand
Wenn die Synchronisation fehlschlägt, betreten wir die dunkle Seite der Informatik: den Deadlock. Ein Deadlock (Verklemmung) ist ein Zustand, in dem mehrere Prozesse dauerhaft aufeinander warten, weil jeder eine Ressource hält, die der andere zwingend benötigt.
Welche Prozesse sind an einem Deadlock beteiligt? Es sind jene Prozesse, die mehrere Betriebsmittel mit jeweils exklusivem Zugriff benötigen[cite: 1]. Damit ein Deadlock überhaupt entstehen kann, müssen laut Systemtheorie vier notwendige Bedingungen gleichzeitig erfüllt sein[cite: 1]:
1. **Mutual Exclusion (Wechselseitiger Ausschluss):** Ressourcen können nicht geteilt werden (z. B. über Druckerspooler aufgelöst)[cite: 1]. 2. **Belegungs- und Wartebedingung (Hold and Wait):** Ein Prozess hält bereits Ressourcen und darf weitere anfordern[cite: 1]. 3. **Unterbrechbarkeitsbedingung (No Preemption):** Dem Prozess können einmal zugeteilte Betriebsmittel nicht gewaltsam vom System entzogen werden[cite: 1]. 4. **Zyklische Wartebedingung (Circular Wait):** Es entsteht ein geschlossener Zyklus im Betriebsmittelgraph (A wartet auf B, B wartet auf C, C wartet auf A)[cite: 1].
Zusätzlich gibt es die hinreichende Bedingung, dass keine externe Betriebsmittelinstanz (die den Knoten auflösen könnte) bestehen darf[cite: 1].
5.1 Verhungern im Livelock
Eine perfide Abwandlung ist der Livelock[cite: 1]. Hier wurde ein Betriebsmittel vergeben, und es besteht eigentlich keine logische Verhinderung[cite: 1]. Die Ressourcen könnten irgendwann freigegeben werden, aber der Zeitpunkt ist völlig unbekannt[cite: 1]. Ein klassisches Beispiel ist striktes Prioritätsscheduling, bei dem langwierige, niedrig priorisierte Prozesse schlichtweg „verhungern“ (Starvation), weil sie immer wieder durch höher priorisierte Prozesse benachteiligt werden[cite: 1].
6. Die Bekämpfung von Deadlocks
Wie bewahren wir unsere Server vor dem totalen Freeze? Das Skript liefert vier klassische Vorgehensweisen, um Deadlocks aufzulösen oder zu umgehen[cite: 1]:
1. **Der Vogel-Strauß-Algorithmus:** Einfach ignorieren![cite: 1] (Klingt absurd, ist aber in Desktop-Systemen oft der Standard, da die mathematische Vorabprüfung extrem rechenintensiv ist). 2. **Erkennen und Beheben:** Das System erlaubt Deadlocks, baut aber z. B. regelmäßig den Graphen aller Anforderungen, erkennt Zyklen topologisch und beseitigt sie durch den **Abbruch** der beteiligten Prozesse und die sofortige Freigabe der belegten Betriebsmittel (ähnlich wie optimistische Sperrverfahren bei Datenbanken)[cite: 1]. 3. **Konzeptionelles Vermeiden:** Eine der vier notwendigen Bedingungen wird durch das Design komplett eliminiert[cite: 1]. 4. **Dynamisches Verhindern:** Vor jeder Zuteilung wird dynamisch geprüft, ob das System in einen Deadlock geraten könnte[cite: 1].
6.1 Dynamisches Verhindern: Der Bankieralgorithmus
Eines der berühmtesten Verfahren zur Erkennung sicherer Systemzustände bei der Ressourcenverteilung ist der Banker-Algorithmus (Bankieralgorithmus)[cite: 1]. Das Prinzip ist dem echten Leben entlehnt: Ein Banker hat eine bestimmte Menge an Ressourcen (z. B. 10 Einheiten)[cite: 1]. Jeder Kunde (Prozess) hat ein festgelegtes Kreditlimit, bis zu dem er Ressourcen anfordern darf[cite: 1]. Der Banker achtet darauf, dass er immer so viele Ressourcen in der Hinterhand behält, dass er das größte vorhandene Limit gerade noch bedienen kann[cite: 1].
Ein Kunde bekommt seine angeforderte Ressource nur dann, falls der Banker danach noch genügend Einheiten hat, um mindestens einem der Kunden sein komplettes Limit zuzuteilen[cite: 1]. * Das Problem der Realität: Jeder Prozess müsste im Voraus exakt wissen, wie viele Einheiten eines Betriebsmittels er maximal während seiner Abarbeitung benötigen wird[cite: 1]. In einem offenen Linux-Betriebssystem ist das schlichtweg unmöglich.
6.2 Deadlock-Erkennung durch Preclaiming
Ein ähnliches Konzept ist das Preclaiming, bekannt aus Datenbanksystemen (DBMS)[cite: 1]. Ein Prozess darf erst dann seinen nächsten Schritt ausführen, wenn alle von ihm angeforderten Betriebsmittel vollständig verfügbar sind[cite: 1]. Algorithmisch bedeutet dies eine zyklische Prüfung (für jeden Prozess), ob die erforderlichen Betriebsmittel vorhanden sind[cite: 1].
6.3 Vermeidung durch Lineare Ordnung
Was passiert, wenn wir Betriebsmittel linear nach Prioritäten ordnen[cite: 1]? Prozesse können zwar alle Betriebsmittel anfordern, müssen sich aber zwingend an die Nummerierungsreihenfolge halten[cite: 1]. Wenn ein Prozess ein Betriebsmittel höherer Ordnung besitzt, ist es ihm ausgeschlossen, ein Betriebsmittel niedrigerer Ordnung anzufordern, das bereits von einem anderen Prozess belegt ist[cite: 1]. Damit wird ein zyklisches Warten (und somit die vierte Deadlock-Bedingung) schlichtweg unmöglich[cite: 1]! Das Problem liegt hier in der Praxis: Es ist extrem schwer, reale und abstrakte Betriebsmittel (wie Festplatten-Spoolbereiche) sauber zu ordnen und zu nummerieren[cite: 1].
Quellen & Materialien
Alle fachlichen Grundlagen dieses Moduls basieren auf dem Skript „Betriebssysteme“ zur Erlangung des Grades Bachelor (German-Baltic Management School).[cite: 1]

