mirror of
https://github.com/theoleuthardt/hwr-notes.git
synced 2026-06-06 01:11:08 +00:00
docs: add obsidian hwr docs
This commit is contained in:
parent
b2636f4b92
commit
850aa3455d
245 changed files with 30757 additions and 0 deletions
112
Betriebssysteme/Zusammenfassung_IPC.md
Normal file
112
Betriebssysteme/Zusammenfassung_IPC.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Zusammenfassung: IPC
|
||||
## 1. Grundlagen und Motivation
|
||||
Prozesse benötigen häufig einen Informationsaustausch (Beispiel: Ein E-Mail-Programm Mail User Agent sendet Daten an den Mail Transfer Agent). Da jeder Prozess grundsätzlich einen eigenen, isolierten Adressraum besitzt, ist eine direkte Kommunikation nicht trivial.
|
||||
**Das Grundprinzip:**
|
||||
- Für die Kommunikation ist gemeinsam genutzter Speicher (Shared Memory) zwingend erforderlich. Dies kann ein Bereich im Kernel-Adressraum sein oder eine Datei auf der Festplatte.
|
||||
- Moderne Betriebssysteme erlauben es Prozessen, Teile ihres Adressraums explizit für andere freizugeben.
|
||||
**Prozesse vs. Threads in der IPC:**
|
||||
- Threads desselben Prozesses teilen sich bereits automatisch den Adressraum.
|
||||
- Wenn zwei Prozesse einen gemeinsamen Speicherbereich nutzen, verhalten sie sich technisch fast identisch zu zwei Threads.
|
||||
- Daher gilt der Leitsatz: **Interprozesskommunikation ist im Grunde immer Interthreadkommunikation.**
|
||||
## 2. Das Problem: Race-Conditions (Wettlaufsituationen)
|
||||
**Definition:** Eine Race-Condition entsteht, wenn mehrere Prozesse/Threads gleichzeitig auf gemeinsam beschreibbare Ressourcen zugreifen und das Endergebnis von der zufälligen zeitlichen Reihenfolge der Ausführung abhängt.
|
||||
**Gefahren:**
|
||||
- Race-Conditions führen zu semantischen Fehlern, die schwer zu finden sind (keine Syntaxfehler!).
|
||||
- Der Code funktioniert meistens korrekt, stürzt aber sporadisch ab („Heisenbugs").
|
||||
### Beispiel 1: Das Logdatei-Problem (Der Absturz)
|
||||
Zwei Prozesse (A und B) wollen in dieselbe Logdatei schreiben:
|
||||
1. Prozess A liest den Status: „Datei ist geschlossen". A setzt intern `is_open = false`.
|
||||
2. **Kontextwechsel:** Die Zeitscheibe von A läuft ab, bevor A die Datei wirklich öffnen kann.
|
||||
3. Prozess B kommt an die Reihe, liest den Status: „Datei ist geschlossen".
|
||||
4. Prozess B öffnet die Datei, schreibt hinein und setzt den Status (physikalisch) auf „offen".
|
||||
5. **Kontextwechsel:** B wird unterbrochen.
|
||||
6. Prozess A läuft weiter. Da A intern gespeichert hat `is_open = false`, prüft es nicht erneut.
|
||||
7. **Fehler:** A versucht, die (bereits offene) Datei erneut zu öffnen → Absturz oder Datenverlust.
|
||||
### Beispiel 2: Lost Update (Datenbank)
|
||||
Zwei Threads wollen einen Zähler (`counter = 0`) erhöhen. Code: `counter = counter + 1;`
|
||||
Obwohl der Code logisch aussieht, passiert auf Maschinenebene Folgendes:
|
||||
1. Thread A lädt Wert 0 in Register.
|
||||
2. Thread B lädt Wert 0 in Register (bevor A schreibt).
|
||||
3. A erhöht auf 1 und speichert.
|
||||
4. B erhöht (seine 0) auf 1 und speichert. → Ergebnis ist 1 statt 2. Ein Update ging verloren.
|
||||
## 3. Die Lösung: Der Kritische Bereich
|
||||
**Definition:** Der Programmteil, in dem auf den gemeinsam genutzten Speicher zugegriffen wird, nennt sich **Kritischer Bereich (Critical Section)**.
|
||||
### Die 4 zentralen Bedingungen für Synchronisation
|
||||
Um Race-Conditions zu verhindern, muss das Betriebssystem folgende Regeln garantieren:
|
||||
1. **Gegenseitiger Ausschluss (Mutual Exclusion):** Es dürfen sich niemals zwei Prozesse gleichzeitig im kritischen Bereich befinden.
|
||||
2. **Keine Annahmen:** Die Lösung darf nicht von der Geschwindigkeit der Prozesse oder der Anzahl der CPUs abhängen.
|
||||
3. **Keine Blockierung von außen:** Ein Prozess, der sich außerhalb seines kritischen Bereichs befindet, darf andere Prozesse nicht blockieren.
|
||||
4. **Kein ewiges Warten (No Starvation):** Jeder Prozess muss irgendwann die Chance bekommen, in den kritischen Bereich einzutreten.
|
||||
## 4. Mechanismus 1: Atomare Operationen
|
||||
Das Grundproblem ist, dass Hochsprachen-Befehle (wie `i++`) nicht atomar (unteilbar) sind. Sie bestehen aus mehreren CPU-Befehlen (Load, Increment, Store), die unterbrochen werden können.
|
||||
**Lösung auf Hardware-Ebene:** Prozessoren bieten spezielle Assembler-Instruktionen, die garantiert nicht unterbrochen werden können. Wären alle Befehle atomar, gäbe es keine Race-Conditions.
|
||||
**Wichtige Assembler-Befehle:**
|
||||
- **TSL (Test-and-Set-Lock):** Liest ein Byte aus dem Speicher in ein Register und schreibt im selben Taktzyklus einen neuen Wert (z.B. 1) an diese Stelle.
|
||||
- **FAA (Fetch-and-Add):** Inkrementiert einen Wert im Speicher atomar.
|
||||
- **CAS (Compare-and-Swap):** Vergleicht den Speicherinhalt mit einem Erwartungswert und tauscht ihn nur bei Übereinstimmung aus (Basis für lock-free Algorithmen).
|
||||
- **XCHG (Exchange):** Tauscht die Inhalte zweier Register oder Speicheradressen atomar.
|
||||
## 5. Mechanismus 2: Mutex (Mutual Exclusion)
|
||||
**Konzept:** Ein Mutex ist eine Technik und Datenstruktur, die als "Schloss" dient.
|
||||
- Nur der Thread, der den Mutex "besitzt" (lock), darf in den kritischen Bereich.
|
||||
- Nur der Besitzer kann den Mutex wieder freigeben (unlock).
|
||||
**Ablauf (Code-Schema):**
|
||||
```c
|
||||
pthread_mutex_lock(&m); // Versuche Schloss zu holen. Wenn belegt -> Warten.
|
||||
/* KRITISCHER BEREICH (z.B. Schreiben in Datei) */
|
||||
pthread_mutex_unlock(&m); // Schloss freigeben.
|
||||
```
|
||||
### Verhalten bei belegtem Mutex
|
||||
Wenn Thread A den Mutex hat und Thread B ihn will, hat B drei Optionen:
|
||||
1. **Passives Warten:** B gibt die CPU ab und wird vom OS "schlafen gelegt" (blocked), bis der Mutex frei ist.
|
||||
2. **Aktives Warten (Spinlock):** B läuft in einer Schleife und prüft permanent („Ist frei? Ist frei?"). Sinnvoll nur bei sehr kurzen Wartezeiten auf Multi-Core-Systemen.
|
||||
3. **Timeout:** B bricht den Versuch nach einer Zeit ab.
|
||||
## 6. Mechanismus 3: Signale
|
||||
**Eigenschaften:** Signale sind ein sehr altes Konzept (Unix) und stellen eine Software-Unterbrechung (Interrupt) dar. Sie können an Prozesse oder Threads gesendet werden.
|
||||
**Funktionsweise:**
|
||||
- Wenn ein Signal empfangen wird, unterbricht der Prozess seine Arbeit sofort.
|
||||
- Entweder führt er eine ISR (Interrupt Service Routine) aus (benutzerdefiniert) oder die Standardaktion des Kernels (z.B. Prozess beenden).
|
||||
- **Signalmaske:** Jeder Thread kann definieren, welche Signale er blockieren/ignorieren möchte.
|
||||
**Wichtige POSIX-Funktionen:**
|
||||
- `pthread_kill()`: Sendet Signal an einen Thread (nicht zwingend tödlich, dient als Benachrichtigung).
|
||||
- `sigwait()`: Thread wartet passiv auf ein bestimmtes Signal.
|
||||
## 7. Mechanismus 4: Semaphore (nach Dijkstra)
|
||||
**Konzept:** Ein Semaphor ist mächtiger als ein Mutex. Er erlaubt den Zugriff einer vordefinierten Anzahl von Threads gleichzeitig.
|
||||
### Datenstruktur
|
||||
Ein Semaphor besteht aus zwei Elementen:
|
||||
1. Einem Zähler (Integer).
|
||||
2. Einer Warteschlange (Queue) für blockierte Threads.
|
||||
### Die zwei Operationen
|
||||
- **P() / wait():** Dekrementiert den Zähler.
|
||||
- Ist der Zähler danach ≥ 0: Zugriff erlaubt.
|
||||
- Ist der Zähler < 0 (bzw. war 0): Thread wird blockiert und in die Queue geschoben.
|
||||
- **V() / signal():** Inkrementiert den Zähler.
|
||||
- Wenn Threads in der Queue warten, wird einer geweckt.
|
||||
### Unterschied zu Mutex
|
||||
- Ein Mutex muss vom Besitzer freigegeben werden. Ein Semaphor kann von jedem Thread inkrementiert (V) werden (Asynchrone Signalisierung).
|
||||
- Ein Semaphor mit Zähler = 1 (Binärer Semaphor) verhält sich wie ein Mutex.
|
||||
### Beispiel: Bahnhofsüberwachung (Performance-Begrenzung)
|
||||
**Szenario:** Es gibt viele Kameras, aber aus Performance-Gründen dürfen maximal 3 Such-Threads gleichzeitig Bilder analysieren.
|
||||
- **Initialisierung:** `Semaphore s = new Semaphore(3);`
|
||||
- **Thread startet:** `s.P()` (Zähler wird 2... dann 1... dann 0).
|
||||
- Der 4. Thread muss warten (Zähler bleibt 0, Thread in Queue).
|
||||
- **Thread fertig:** `s.V()` (Zähler wird 1, Wartender darf rein).
|
||||
## 8. Mechanismus 5: Monitore
|
||||
**Konzept:** Monitore sind ein Konstrukt auf höherer Abstraktionsebene (Sprachunterstützung). Ein Monitor ist ein gekapselter kritischer Bereich (z.B. eine Klasse oder Methode).
|
||||
**Eigenschaften:**
|
||||
- **Automatische Sicherheit:** Der Compiler garantiert, dass immer nur ein einziger Prozess/Thread gleichzeitig im Monitor aktiv ist.
|
||||
- Programmierer müssen Lock/Unlock nicht manuell schreiben → weniger fehleranfällig.
|
||||
**Implementierung in Java:**
|
||||
- Schlüsselwort: `synchronized`.
|
||||
- Kann auf Methoden (`synchronized void methode()`) oder Blöcke (`synchronized(this) { ... }`) angewendet werden.
|
||||
### Bedingte Synchronisation (Warten im Monitor)
|
||||
Oft muss ein Thread im Monitor warten, bis eine Bedingung erfüllt ist (z.B. Puffer nicht mehr leer). Dazu gibt es spezielle Methoden:
|
||||
- `wait()`: Thread legt sich schlafen und gibt den Monitor frei, damit andere eintreten können.
|
||||
- `notify()`: Weckt einen wartenden Thread (zufällig).
|
||||
- `notifyAll()`: Weckt alle wartenden Threads (sicherer, um Deadlocks zu vermeiden).
|
||||
## 9. Weitere Konzepte
|
||||
### Das volatile Primitiv
|
||||
- Kennzeichnet Variablen, die sich "außerhalb des Programmflusses" ändern können (z.B. durch Hardware oder andere Threads).
|
||||
- **Effekt:** Der Compiler schaltet Optimierungen ab und erzwingt bei jedem Zugriff ein Lesen aus dem Hauptspeicher (statt aus dem schnellen CPU-Cache).
|
||||
- **Achtung:** `volatile` garantiert Sichtbarkeit (Aktualität), aber keine Atomizität. Es ersetzt keinen Mutex bei komplexen Operationen.
|
||||
### Parallele Programmiersprachen
|
||||
Sprachen wie Occam, Ada oder Erlang besitzen eingebaute Kontrollstrukturen (z.B. `PAR` für parallele Ausführung oder Kanäle für Kommunikation), um Synchronisation direkt in der Syntax abzubilden, statt externe Bibliotheken zu nutzen.
|
||||
Loading…
Add table
Add a link
Reference in a new issue