Upload
leonore-zahniser
View
105
Download
2
Embed Size (px)
Citation preview
RW-Systemarchitektur Kap. 101
10.2 Wechselseitiger Ausschluss in Hardware• Zur Erinnerung: 2. Versuch in Software
/* Prozess 0 */repeat { while (ready[1] = true) tue nichts; ready[0] := true; /* kritischer Abschnitt */ ready[0] := false; /* nichtkrit. Abschnitt */ }
/* Prozess 1 */repeat { while (ready[0] = true) tue nichts; ready[1] := true; /* kritischer Abschnitt */ ready[1] := false; /* nichtkrit. Abschnitt */ }
• Warum scheiterte dieser Versuch?
• Weil Testen und Setzen von ready[] nicht in einem einzigen, unteilbaren Schritt durchführbar: ) Prozesswechsel zwischen Testen und Setzen ist möglich
RW-Systemarchitektur Kap. 102
Wechselseitiger Ausschluss in Hardware (2)
• Neues Konzept: – Einführung atomarer Operationen.– Hardware garantiert atomare Ausführung.
• Testen und Setzen zusammen bilden eine atomare Operation:– Definiere neuen Befehl TSL: Test and Set Lock– Da TSL ein einziger Befehl ist, kann ein Prozesswechsel nicht
zwischen Testen und Setzen erfolgen (nicht “mitten im Befehl”)
• Für jeden kritischen Bereich eine Sperrvariable mit Wert = 0 für ungesperrt und Wert ungleich 0 für gesperrt.
RW-Systemarchitektur Kap. 103
Wechselseitiger Ausschluss in Hardware (3)• Befehl TSL RX, LOCK
mit Speicheradresse LOCK und Register RX hat folgende Wirkung:– RX = Speicher[LOCK]; Speicher[LOCK] := 1– Ein Befehl ) atomare Ausführung
• Prozesse, die Zugriff auf den kritischen Abschnitt erhalten wollen, führen folgende Befehle aus:enter_region:
TSL RX, LOCK // kopiere Lock-Variable und setze LockCMP RX, #0 // War Lock-Variable = 0? Ungesperrt?JNE enter_region // Wenn Lock schon gesetzt war ) Schleife... // Fortfahren und Betreten des krit.
Abschnitts
• Prozesse, die den kritischen Abschnitt verlassen, führen folgenden Befehl aus:
STOREI LOCK, #0 // Speicher[LOCK] := 0
RW-Systemarchitektur Kap. 104
Wechselseitiger Ausschluss in Hardware - Analyse
• Vorteil:– Nicht-alternierender Zugriff auf den kritischen Abschnitt– Wechselseitiger Ausschluss garantiert– Kein Deadlock
• Nachteil: Aktives Warten genau wie bei Softwarelösung!
• Weiterer Nachteil sowohl für Petersons Algorithmus als auch für Hardware-Lösung: Bei festen Prioritäten von Prozessen und Präemption können diese Lösungen trotzdem zu Livelocks führen.– Szenario:
• Prozess 0 hat höhere Priorität, ist aber (z.B. wegen I/O-Operation) blockiert. • Prozess 1 betritt kritischen Abschnitt.• Prozess 0 wird wieder rechenbereit, erhält Prozessor wegen höherer Priorität• Wenn Prozess 0 in den kritischen Abschnitt will, betritt er die Schleife für aktives
Warten ) Livelock!!! Dies heißt Prioritätsinversion– Problem dahinter: Kombination von präemptivem Scheduling mit nichtpräemtiver
Bedingung (kritischer Bereich, Ressource etc.)
RW-Systemarchitektur Kap. 105
10.3 Wechselseitiger Ausschluss, ins Betriebssystem integriert
• Folgerung:Um aktives Warten zu verhindern, muss wechselseitiger Ausschluss ins Betriebssystem integriert werden!
• Idee: Statt aktiv zu warten, blockieren Prozesse einfach!) neuer Systemaufruf sleep(lock)
• Nach Verlassen des kritischen Abschnitts weckt der verlassende Prozess einen anderen Prozess auf, der auf Erlaubnis wartet, den kritischen Abschnitt zu betreten (wenn ein solcher Prozess vorhanden ist)) neuer Systemaufruf wakeup(lock)
• Variable lock wird eindeutig einem kritischen Abschnitt zugeordnet. lock = 0 heißt nicht gesperrt. Alles andere heißt gesperrt.
RW-Systemarchitektur Kap. 106
Mutexe
• Vor Eintritt in kritischen Abschnitt wird Funktion mutex_lock(lock) aufgerufen:
• testset(lock) führt TSL-Befehl aus und liefert true gdw. Lock-Variable vorher 0, d.h. ungesperrt war.
function mutex_lock(int lock) { if (testset(lock) = false) sleep(lock); return; }
RW-Systemarchitektur Kap. 107
Mutexe
• Nach Verlassen des kritischen Abschnitts wird mutex_unlock(lock) aufgerufen:
• Es muss eine Warteschlange für Prozesse geben, die auf lock warten.• Nach wakeup(lock) wird der erste Prozess in der Warteschlange
bereit (aber nicht notwendigerweise aktiv).
• Die Variable lock heißt Mutexvariable bzw. kurz Mutex.• Sie wird üblicherweise vom Betriebssystem verwaltet (im Adressraum des
Betriebssystems).
function mutex_unlock(int lock) { lock = 0; wakeup(lock); return; }
RW-Systemarchitektur Kap. 108
Das Produzenten/Konsumenten-Problem mit mutex und Zählvariable (1)
Typisches Problem bei nebenläufigen Prozesse:– Gemeinsamer Puffer– Einige Prozesse schreiben in den Puffer (“Produzenten”) – Einige Prozesse lesen aus dem Puffer (“Konsumenten”)– Prozedur insert_item schreibt Objekt in Puffer.– Prozedur remove_item entfernt Objekt aus Puffer.– Puffergröße ist beschränkt und Puffer kann leer sein.– Wenn Puffer voll ist, dann sollten Produzenten nicht einfügen.
Aus Effizienzgründen: Blockieren der Produzenten, die einfügen wollen.
– Wenn Puffer leer ist, sollten Konsumenten nichts entfernen. Aus Effizienzgründen: Blockieren der Konsumenten, die entfernen wollen.
RW-Systemarchitektur Kap. 109
Weiteres Beispiel für Interaktion verschiedener Prozesse:
Das Produzenten/Konsumenten-Problem (2)
• Lösung mit – gemeinsamer Variable count für Anzahl der Elemente im
Puffer (initialisiert durch 0)– sleep und wakeup auf zwei Mutexen p und c– Prozesse, die auf p warten, wollen einfügen,– Prozesse, die auf c warten, wollen konsumieren.
• Anfangs schläft Konsument
...
RW-Systemarchitektur Kap. 1010
Weiteres Beispiel für Interaktion verschiedener Prozesse:
Das Produzenten/Konsumenten-Problem (2)
Prozedur producer { … repeat { item = produce_item(); // produziere nächstes
// Objekt if (count = MAX_BUFFER) // schlafe, wenn Puffer voll sleep(p);
insert_item(item); // Einfügen in Puffer count = count + 1; if (count = 1) // wenn Puffer vorher leer: wakeup(c); // wecke Konsumenten } }
RW-Systemarchitektur Kap. 1012
Weiteres Beispiel für Interaktion verschiedener Prozesse:
Das Produzenten/Konsumenten-Problem (3)
Prozedur consumer { … repeat { if (count = 0) // schlafe, wenn Puffer sleep(consumer); // leer item = remove_item(); // Entfernen aus Puffer count = count - 1; if (count = MAX_BUFFER - 1) // wenn Puffer vorher
// voll wakeup(producer); // wecke Konsumenten consume_item(item); // konsumiere Objekt } }
Ist diese Lösung korrekt???
RW-Systemarchitektur Kap. 1013
Weiteres Beispiel für Interaktion verschiedener Prozesse:
Das Produzenten/Konsumenten-Problem (4)
• Die “Lösung” ist falsch!
• Problem:– “if (count = 0) sleep(c)“ ist keine atomare Operation.– Fehlersituation:
• concumer gibt Prozessor ab nach “if (count = 0)”, wenn Puffer leer ist
• Dann fügt producer Objekt ein und erhöht count auf 1• Aufwecken von consumer geht verloren, da er noch gar nicht
schläft.
• Nach nächstem Prozesswechsel: consumer schläft für immer.
• Wenn Puffer voll wird, schläft auch producer für immer.
) Deadlock!
• Erneut: Subtile Fehler, Beweise nötig!
RW-Systemarchitektur Kap. 1014
Elegante Lösung des Produzenten/Konsumenten-Problems
• Unsere Lösung benutzt zählende Semaphore, Dijkstra 1965• Entwickelt zur Synchronisation von Prozessen.
• Konzept:– Integer-Variable mit 3 Operationen:
• Initialisierung mit nicht-negativem Wert– down-Operation
• Verringere Wert um 1• Wenn Wert < 0, blockiere aufrufenden Prozess.
– up-Operation • Erhöhe Wert um 1• Wenn Wert >= 0, wecke einen der blockierten Prozesse auf (wird bereit).
• Wichtig: up und down müssen atomare Operationen sein.
• Garantiert durch Betriebssystem und Hardware.
RW-Systemarchitektur Kap. 1015
Binäre Semaphore
• Semaphore, die exklusiven Zutritt zu einem kritischen Abschnitt gewähren, heißen binäre Semaphoren.– Zustand 0 heißt gesperrt,– Zustand 1 heißt nicht gesperrt.
RW-Systemarchitektur Kap. 1016
Wechselseitiger Ausschluss mit Semaphoren (1)
• Erster Prozess betritt kritischen Abschnitt.
• Andere Prozesse müssen warten.
• Bei Verlassen des kritischen Abschnitts wird Semaphor-Variable erhöht.
/* Prozess i */repeat { down(s); /* kritischer Abschnitt */; up(s); /* nichtkrit. Abschnitt */; }
• Voraussetzungen:– Ein binäres Semaphor s, d.h. counts ist auf 1 initialisiert– n Prozesse sind gestartet, konkurrieren um kritischen Abschnitt.
RW-Systemarchitektur Kap. 1017
Semaphore
• Behandlung mehrfach nutzbarer Ressourcen (m-fach nutzbar mit m 2 N) ist möglich (zählendes Semaphor):– durch Initialisierung counts = m 2 N.
• Interpretation von counts:
– Falls counts ¸ 0:
• counts gibt die Anzahl der Prozesse an, die down(s) ausführen können ohne zu blockieren (wenn nicht zwischenzeitlich up(s) ausgeführt wird).
– Falls counts < 0:
• |counts| ist die Anzahl der wartenden Prozesse in queues.
• Ein zählendes Semaphor s hat 3 Komponenten:– Integer-Variable counts
– Warteschlange queues
– Lock-Variable locks (für Implementierung, siehe später)
RW-Systemarchitektur Kap. 1018
Produzenten/Konsumenten-Problem mit Semaphoren
Prozedur producer { … repeat { item = produce_item(); // produziere nächstes Objekt down(non_full); down(mutex); insert_item(item); // Einfügen in Puffer up(mutex); up(non_empty); } }
semaphore mutex; countmutex = 1; // mutex für kritische Abschnittesemaphore non_full; countnon_full = MAX_BUFFER; // zählt freie Plätzesemaphore non_empty; countnon_empty = 0; // zählt belegte Plätze
RW-Systemarchitektur Kap. 1019
Produzenten/Konsumenten-Problem mit Semaphoren
Prozedur consumer { … repeat { down(non_empty); down(mutex); item = remove_item(); // Entfernen aus Puffer up(mutex); up(non_full); consume_item(item); // konsumiere Objekt } }
Frage:Funktioniert das immer noch, wenn in Prozedur consumer• up(mutex) und up(non_full) vertauscht werden?• down(non_empty) und down(mutex) vertauscht werden?
RW-Systemarchitektur Kap. 1020
Implementierung von Semaphoren mit mutexen: Versuch 1
down(semaphore s) { mutex_lock(locks); counts = counts - 1; if (counts < 0) { setze diesen Prozess in queues; blockiere den Prozess und führe unmittelbar vor Abgabe des Prozessors noch mutex_unlock(locks) aus } else mutex_unlock(locks); }
up(semaphore s) { mutex_lock(locks); counts = counts + 1; if (counts > 0) { entferne einen Prozess P aus queues; Schreibe Prozess P in Liste der bereiten Prozesse } mutex_unlock(locks); }
• Systemaufrufe down und up
RW-Systemarchitektur Kap. 1021
Implementierung von Semaphoren: Versuch 1
• Analyse:– down und up sind nicht wirklich atomar, aber trotzdem stören sich
verschiedene Aufrufe von down und up nicht aufgrund des mutex.
– Zumindest für binäre Semaphore ist Verwendung von mutexen in Semaphoraufrufen etwas aufwändig!
– Zwei Queues:• Liste von Prozessen, die auf Freigabe des mutex warten
• Liste von Prozessen, die auf Erhöhung der Semaphor-Variable warten
RW-Systemarchitektur Kap. 1022
Implementierung von Semaphoren: Versuch 2
down(semaphore s) { while (testset(locks) = false) tue nichts; counts = counts - 1; if (counts < 0) { setze diesen Prozess in queues; blockiere den Prozess und führe unmittelbar vor Abgabe des Prozessors noch locks = 0 aus } else locks = 0 }
up(semaphore s) { while (testset(locks) = false) tue nichts; counts = counts + 1; if (counts <= 0) { entferne einen Prozess P aus queues; Schreibe Prozess P in Liste der bereiten Prozesse } locks = 0; }
• Systemaufrufe down und up
RW-Systemarchitektur Kap. 1023
Implementierung von Semaphoren: Versuch 2
• Analyse der Implementierung:– aktives Warten!
• nicht so gravierend:– beschränkt auf up und down– up und down sind relativ kurz
– down und up sind nicht wirklich atomar, aber trotzdem stören sich verschiedene Aufrufe von down und up nicht aufgrund des busy waitings.
– Semaphoren blockieren Prozesse ) keine CPU-Zeit für wartende Prozesse
– Unterbinden von Unterbrechungen nicht nötig.
– Implementierung auch für Multiprozessoren geeignet.
• Für Benutzer sind nur abstrakte Semaphoren sichtbar, keine Implementierungsdetails
RW-Systemarchitektur Kap. 1024
Zusammenfassung
• CPU (einzelne CPU oder Multiprozessor) wird von mehreren Prozessen geteilt.
• Verwaltung gemeinsamer Ressourcen bei Multiprogramming ist nicht trivial.
• Subtile Fehler möglich, formale Beweise nötig
• Konzepte für wechselseitigen Ausschluss, Produzenten/Konsumenten-Problem