Folien Kapitel6 1

Werbung
Systeme 1
Kapitel 6.1
Nebenläufigkeit und wechselseitiger
Ausschluss
WS 2009/10
1
Letzte Vorlesung
• Threads
– „leichtgewichtige Prozesse“
– Gemeinsamer Adressraum
• Nebenläufigkeit (Threads)
– parallele bzw. pseudo-parallele Ausführung
– Kontrollprobleme
• Wechselseitiger Ausschluss
• Deadlocks
• Livelocks
– Anforderungen an wechselseitigen Ausschluss
WS 2009/10
2
Letzte Vorlesung
• Anforderungen an wechselseitigen Ausschluss
– Softwarelösungen
• Versuche 1 - 5
Busy
Waiting
Wechselseitiger
Ausschluss garantiert
Nicht-alternierender Zugriff auf
kritischen Abschnitt möglich
Versuch 1
Versuch 2
Versuch 3
Versuch 4
Versuch 5
WS 2009/10
3
Wechselseitiger Ausschluss in Hardware
• Zur Erinnerung Versuch 2 als Softwarelösung
/* Prozess 0 */
wiederhole
{
solange (flag[1] = true)
tue nichts;
flag[0] := true;
/* kritischer Abschnitt */
flag[0] := false;
/* nichtkrit. Abschnitt */
}
/* Prozess 1 */
wiederhole
{
solange (flag[0] = true)
tue nichts;
flag[1] := true;
/* kritischer Abschnitt */
Flag[1] := false;
/* nichtkrit. Abschnitt */
}
– Warum scheiterte dieser Versuch?
– Weil Testen und Setzen von Flags nicht in einem einzigen
Schritt durchführbar:
• Prozesswechsel zwischen Testen und Setzen ist möglich.
WS 2009/10
4
Wechselseitiger Ausschluss in Hardware
• 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”)‫‏‬.
WS 2009/10
5
Wechselseitiger Ausschluss in Hardware
• Befehl TSL RX, LOCK
mit Speicheradresse LOCK und Register RX hat folgende
Wirkung:
– RX = Speicher[LOCK]; Speicher[LOCK] := 1
– Ein Befehl, d.h. atomare Ausführung.
• Prozesse, die Zugriff auf den kritischen Abschnitt erhalten
wollen, führen folgende Befehle aus:
enter_region:
TSL, RX, LOCK
CMP RX, #0
JNE enter_region
...
// kopiere Lock-Variable und setze Lock
// War Lock-Variable = 0? (CMP = compare)
// Wenn Lock schon gesetzt war -> Schleife
(JNE = jump if not equal)‫‏‬
// Fortfahren und Betreten des krit. Abschnitts
• Prozesse, die den kritischen Abschnitt verlassen, führen
folgenden Befehl aus:
STOREI LOCK, #0
WS 2009/10
// Speicher[LOCK] := 0 (STOREI = store immediate)‫‏‬
6
Wechselseitiger Ausschluss im Betriebssystem
• Folgerung:
– Um aktives Warten zu verhindern, muss wechselseitiger Ausschluss ins
Betriebssystem integriert werden!
– Idee: Statt aktiv zu warten, blockiere 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
(sofern ein solcher Prozess vorhanden ist).‫‏‬
– Neuer Systemaufruf wakeup(lock)‫‏‬
– Parameter lock wird nur gebraucht, um Aufrufe für den gleichen
kritischen Abschnitt einander zuzuordnen.
– Eine Warteschlange pro kritischem Abschnitt
WS 2009/10
7
Mutex
• Mutex = Mutual Exclusion
• Vor dem Eintritt in einen kritischen Abschnitt wird die
Funktion mutex_lock(lock) aufgerufen.
function mutex_lock(lock)
{
solange (testset(lock) = false)
{
sleep(lock);
}
return;
}
• testset(lock) führt atomare TSL-Operation aus; liefert
true gdw. Lockvariable vorher 0 war.
WS 2009/10
8
Mutex
• Nach Verlassen des kritischen Abschnitts wird
mutex_unlock(lock) aufgerufen.
function mutex_lock(lock)
{
unset(lock);
// lock wird freigegeben
wakeup(lock);
return;
}
• Es muss eine Warteschlange für Prozesse geben, die auf lock
warten.
• Nach wakeup(lock) wird der erste Prozess in der Queue bereit
(aber nicht notwendigerweise aktiv -> Scheduler-Algorithmus!)‫‏‬.
• Die Variable lock heißt Mutexvariable bzw. kurz Mutex.
WS 2009/10
9
Das Produzenten-Konsumenten Problem
Typisches Problem bei nebenläufigen Prozessen:
–
–
–
–
–
–
–
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.
WS 2009/10
10
Das Produzenten-Konsumenten Problem
• Lösungsansatz:
– Gemeinsame Variable count für die Anzahl der
Elemente im Puffer
– Initialer Wert 0
– sleep() und wakeup()‫‏‬
– Anfangs schlafen die Konsumenten (Puffer ist
leer).‫‏‬
WS 2009/10
11
Das Produzenten-Konsumenten Problem
Prozedur producer
{
...
wiederhole
{
item = produce_item();
wenn (count = MAX_BUFFER)
sleep(producer);
insert_item(item)
count = count + 1;
wenn (count = 1)
wakeup(consumer);
//produziere nächstes Objekt
//schlafe, wenn Puffer
//voll
// Einfügen in Puffer
// wenn Puffer vorher leer:
// wecke Konsumenten
}
}
WS 2009/10
12
Das Produzenten-Konsumenten Problem
Prozedur consumer
{
...
wiederhole
{
wenn (count = 0)
Sleep(consumer);
Ist diese Lösung korrekt???
// schlafe, wenn Puffer
// leer
item = remove_item();
count = count -1;
// Entferne aus Puffer
wenn (count = MAX_BUFFER – 1)
wakeup(producer);
// wenn Puffer voll
// wecke Produzenten
consume_item(item);
// konsumiere Objekt
}
}
WS 2009/10
13
Das Produzenten-Konsumenten Problem
• Mögliche Probleme:
1)
Zwei Konsumenten: 1 Element im Puffer
• Konsument 1 entnimmt Element und wird unterbrochen bevor er
count auf 0 setzen kann.
• Konsument 2 wird aktiv aber der Puffer ist leer! (count immer noch 1)
 Fehler!
2)
Zwei Konsumenten: Puffer ist voll.
• count == MAX_BUFFER
• Konsument 1 entnimmt Element und führt count = count–1; aus und
wird dann unterbrochen (count == MAX_BUFFER-1)‫‏‬.
• Konsument 2 wird aktiv, entnimmt ein Element und reduziert count.
• Problem: Produzent wird nie mehr geweckt, da Bedingung
wenn (count = MAX_BUFFER – 1)‫‏‬
nie mehr erfüllt wird!
WS 2009/10
14
Das Produzenten-Konsumenten Problem
• Ein Produzent und ein Konsument
– Konsument gibt Prozessor ab nach
wenn(count = 0)‫‏‬
–
–
–
–
wenn Puffer leer ist
Dann fügt der Produzent ein Objekt ein, count = 1.
Aufwecken des Konsumenten geht verloren, da er noch gar
nicht schläft.
Nach nächstem Prozesswechsel: Konsument schläft für immer.
Wenn Puffer voll wird, schläft auch der Produzent für immer.
 Deadlock
– Problem:
wenn (count = 0) sleep(consumer)‫‏‬
ist keine atomare Operation!
WS 2009/10
15
Das Produzenten-Konsumenten Problem
• Die elegante Lösung des ProduzentenKonsumenten Problems ist die Nutzung eines
Semaphor.
• Dijkstra (1965)‫‏‬
• Entwickelt zur Synchronisation von Prozessen.
• Konzept:
– Integer-Variable mit drei Operationen:
• Initialisierung mit nicht-negativen Wert
• down() Operation
• up() Operation
WS 2009/10
16
Wechselseitiger Ausschluss mit Semaphoren
• Voraussetzungen:
– Es existiert ein Semaphor s.
– countS ist auf 1 initialisiert.
– n Prozesse sind gestartet, konkurrieren um
kritischen Abschnitt.
/* Prozess i */
wiederhole
{
down(s);
/* kritischer Abschnitt */
up(s);
/* nichtkritischer Abschnitt */
}
WS 2009/10
17
Wechselseitiger Ausschluss mit Semaphoren
• Beispiel:
– Semaphor wird mit 1 initialisiert.
– Prozess 1 geht in kritischen Abschnitt:
• down(s) -> Semaphor-Zähler wird 0
– Prozess 2 will in den kritischen Abschnitt:
• down(s) -> Semaphor-Zähler wird -1
– Prozess 2 wird blockiert und in die Warteschlange eingefügt.
– Prozess 3 will in den kritischen Abschnitt:
• down(s) -> Semaphor-Zähler wird -2
• Prozess 3 wird blockiert und in die Warteschlange eingefügt.
– Prozess 1 verlässt kritischen Abschnitt:
• up(s) -> Semaphor-Zähler wird -1
• Zähler <= 0, d.h. ein Prozess wird der Warteschlange entnommen
und wird bereit.
WS 2009/10
18
Wechselseitiger Ausschluss mit Semaphoren
• Auf 1 initialisierte Semaphore heißen binäre
Semaphore.
• Behandlung mehrfach nutzbarer Ressourcen (mfach) ist möglich:
– durch Initialisierung countS = m.
• 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:
WS 2009/10
• |countS| ist die Anzahl der wartenden Prozesse in queueS.
19
Das Produzenten-Konsumenten Problem mit
Semaphoren
semaphore mutex; countmutex = 1;
// mutex für kritische Abschnitte
semaphore empty; countempty = MAX_BUFFER;
// zählt freie Plätze
semaphore full; countfull = 0;
// zählt belegte Plätze
Prozedur producer
{
...
wiederhole
{
item = produce_item();
down(empty);
down(mutex);
insert_item(item);
up(mutex);
up(full);
}
}
WS 2009/10
//produziere nächstes Objekt
// Einfügen in Puffer
20
Das Produzenten-Konsumenten Problem mit
Semaphoren
Prozedur consumer
{
...
wiederhole
{
down(full);
down(mutex);
item = remove_item();
up(mutex);
up(empty);
consume_item(item);
}
}
// Entferne aus Puffer
// konsumiere Objekt
Frage:
– Funktioniert das immer noch, wenn in Prozedur
consumer
• up(mutex) und up(empty) vertauscht werden?
• down(full) und down(mutex) vertauscht werden?
WS 2009/10
21
Das Produzenten-Konsumenten Problem mit
Semaphoren
• Frage: Funktioniert das immer noch, wenn in
Prozedur consumer
und up(empty) vertauscht werden?
down(full) und down(mutex) vertauscht werden?
– up(mutex)
–
• Angenommen der Puffer ist leer:
– down(mutex), down(full)‫‏‬
• Konsument blockiert, da es keine „vollen Plätze“ gibt.
• Produzenten können aber nicht den Puffer füllen, da sie
alle bei down(mutex) hängen bleiben.
• Irgendwann schlafen alle -> Deadlock!
WS 2009/10
22
Implementierung von Semaphoren: Versuch 1
k.A.
k.A.
down(semaphore s)
up(semaphore s)‫‏‬
{
{
mutex_lock(lockS);
mutex_lock(lockS);
countS = countS - 1;
countS = countS + 1;
wenn (countS < 0)
if (countS <= 0)‫‏‬
{
{
setze diesen Prozess in
entferne einen Prozess P aus
queueS;
queueS;
blockiere den Prozess und
Schreibe Prozess P in Liste
führe unmittelbar vor
der bereiten Prozesse
Abgabe des Prozessors noch
}
mutex_unlock(lockS) aus
mutex_unlock(lockS);
}
}
sonst
mutex_unlock(lockS);
}
WS 2009/10
23
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, weil
Mutexe alleine schon reichen, um wechselseitigen
Ausschluss zu garantieren!
 Zwei Queues:
• Liste von Prozessen, die auf Freigabe des Mutex warten.
• Liste von Prozessen, die auf Erhöhung der SemaphorVariable warten.
WS 2009/10
24
Implementierung von Semaphoren: Versuch 2
k.A.
k.A.
down(semaphore s)
up(semaphore s)‫‏‬
{
{
solange (testset(lockS) = false)‫‏‬
solange (testset(lockS) = false)‫‏‬
tue nichts;
tue nichts;
countS = countS - 1;
countS = countS + 1;
wenn (countS < 0)
if (countS <= 0)‫‏‬
{
{
setze diesen Prozess in
entferne einen Prozess P aus
queueS;
queueS;
blockiere den Prozess und
Schreibe Prozess P in Liste
führe unmittelbar vor
der bereiten Prozesse
Abgabe des Prozessors noch
}
lockS = 0 aus
lockS = 0;
}
}
sonst
lockS = 0
}
WS 2009/10
25
Implementierung von Semaphoren: Versuch 2
• Analyse:
– aktives Warten (solange(...) tue nichts;)!
• 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.
WS 2009/10
26
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.
WS 2009/10
27
Herunterladen