Saalübung - Betriebssysteme

Werbung
Saalübung Informatik II
SS 2006
Threads, Monitore und
Semaphore
Virtuelle
Speicherverwaltung
Prozesse und Threads
z
Wünschenswert, Parallelität innerhalb eines einzigen Programms zur
Verfügung zu haben und nicht nur innerhalb des Betriebssystems
z
Java ist eine Sprache, die nebenläufige Programmierung direkt unterstützt
z
Konzept beruht auf so genannten Threads
z
Parallel ablaufende Aktivitäten, die sehr schnell in der Umschaltung sind
z
Innerhalb eines Prozesses kann es mehrere Threads geben
–
–
Threads laufen alle zusammen in dem selben Adressraum ab
Threads eines Prozesses können daher untereinander auf ihre öffentlichen Daten
zugreifen → mitunter problematisch!
Prozesse und Threads
z
Moderne Betriebssysteme - Illusion, dass verschiedene Programme
gleichzeitig ausgeführt werden
z
Quasiparallelität ~ Nebenläufigkeit
z
Nebenläufigkeit der Programme wird durch das Betriebssystem
gewährleistet
z
die Prozesse werden alle paar Millisekunden umgeschaltet - verzahnte
Bearbeitung der Prozesse
z
Scheduler - Teil des Betriebssystems, der die Prozessumschaltung
übernimmt
Threads erzeugen
z
Für nebenläufige Programme in Java ist die Klasse Thread
zuständig
z
Die gewünschte, parallel auszuführende Aktivität wird in der
Objektmethode run() programmiert
z
Java bietet zur Programmierung von Threads zwei
Möglichkeiten an
(1)
Threads über die Schnittstelle Runnable implementieren
(2)
Die Klasse Thread erweitern
Threads erzeugen
Vorteile
Threads erzeugen
z
Ableiten von Thread
Wir bilden eine Unterklasse von Thread
Für ein Programmstück, das nebenläufig ablaufen
soll, überschreiben wir die run()-Methode
Damit der Thread und damit das Programm startet,
rufen wir die Objektmethode start() auf
Nachteile
Ableiten von Thread
(extends Thread)
Programmcode in run()
kann die Methoden der
Klasse Thread nutzen
Da es in Java keine
Mehrfachvererbung gibt,
kann die Klasse nur
Thread erweitern
1)
Implementieren von
Runnable (implements
Runnable)
Die Klasse kann von einer
anderen,
problemspezifischen
Klasse erben
Thread-Methoden können
nur über Umwege genutzt
werden
3)
Threads erzeugen
z
Ableiten von Thread
class DateThread extends Thread
{
public void run()
{
for ( int i = 0; i < 20; i++ )
System.out.println( new Date() );
}
}
Thread t = new DateThread(); t.start();
oder
new DateThread().start();
2)
Zustände eines Threads
z
Bei einem Thread-Exemplar können wir
einige Zustände feststellen:
–
–
–
–
–
noch nicht erzeugt,
laufend (vom Scheduler berücksichtigt),
nicht laufend (vom Scheduler nicht berücksichtigt),
wartend,
beendet
Ende eines Threads
z
Allgemein ist ein Thread beendet, wenn eine der folgenden
Bedingungen zutrifft:
–
–
–
die run()-Methode wurde ohne Fehler beendet
in der run()-Methode tritt eine Exception auf, die die Methode
beendet
der Thread wurde von außen abgebrochen. Dazu kann die
prinzipbedingt problematische Methode stop() verwendet werden –
oder aber ein Flag gesetzt werden, dass die „Endlosschleife“
abbricht
Notwendigkeit: Synchronisation
z
Problem: Mehrere Programme versuchen
gleichzeitig auf eine Ressource zuzugreifen
(z.B. Programmsegment, Drucker, …)
z
Lösung: Zugriff auf Ressource wird
synchronisiert → gegenseitiger Ausschluss
public void run { ... while (Flag) { ... } ... }
–
die virtuelle Maschine wird beendet – Folge: alle Threads werden
ebenfalls beendet
Sicherheitseigenschaft
z
z
Wenn mehr Threads oder Prozesse den
selben kritischen Bereich betreten, als in
diesem Bereich zugelassen sind, ist die
Sicherheitseigenschaft verletzt
Die Erfüllung der Sicherheitseigenschaft
bezeichnet man auch als partielle
Korrektheit
Synchronisation über kritische
Abschnitte
class T extends Thread
{
static int result;
public void run() { ... }
result ist somit in jeder
Instanz von T sichtbar
}
Kritische Abschnitte
Zusammenhängende Programmblöcke, die nicht unterbrochen werden dürfen
und besonders geschützt werden müssen, nennen sich kritische Abschnitte.
Wenn lediglich ein Thread den Programmteil abarbeitet, dann nennen wir dies
gegenseitigen Ausschluss oder atomar.
Kritische Abschnitte
z
Punkte parallel initialisieren
z
Nehmen wir an, ein Thread T1 belegt das vorher mit (0,0) belegte
Point-Objekt p mit den Werten (1,2) und ein Thread T2 gleichzeitig mit
den Werten (2,1).
Das heißt, T1 führt die Anweisungen
p.x = 1; p.y = 2;
durch und T2 die Anweisungen
p.x = 2; p.y = 1;
z
Führt im Einzelfall zu den folgenden möglichen Ergebnissen:
{ (1,2),(1,1,),(2,1),(2,2) }
Abschnitte mit synchronized schützen
z
z
Soll ein Programmabschnitt oder eine Objekt- oder
Klassenmethode atomar ablaufen, so existiert dafür
in Java ein Sprachkonstrukt. Wir setzen vor die
Methode das Schlüsselwort synchronized
Das oben genannte Problem mit foo() lässt sich mit
synchronized einfach lösen:
synchronized void foo() { i++; }
Kritische Abschnitte
public class IPlusPlus
{
static int i;
static void foo()
{
i++;
}
}
0 getstatic #19 <Field int i>
3 iconst_1
4 iadd
5 putstatic #19 <Field int i> 8
return
Die Objektmethode foo() erhöht die
statische Variable i.
Um zu erkennen, dass i++ ein kritischer
Abschnitt ist, sehen wir uns den dazu
generierten Bytecode für die Methode
foo() an
Monitore
z
Laufzeitumgebung lässt nur einen Thread in einen Block
z
Wie macht die Java-Maschine dies?
z
Jedes Java-Objekt definiert einen so genannten Monitor
Der Begriff geht auf C. A. R. Hoare zurück, der im Aufsatz »Communicating Sequential Processes« von 1978 erstmals dieses
Konzept veröffentlichte
z
Mit dem Monitor – wir nennen ihn auch Lock (leicht zu merken durch einen
Schlüssel, der die Tür abschließt) – ist der serielle Zugriff gewährleistet
z
Wir setzen dann vor die Methode das Schlüsselwort synchronized
z
Ein betretender Thread setzt dann diesen binären Monitor des aufrufenden
Objekts, und andere ankommende Threads müssen warten, wenn dieser
gesetzt ist
Deadlocks
z
z
Deadlocks können in
Java-Programmen nicht
erkannt und verhindert
werden
Dem Programmierer
fällt also die Aufgabe
zu, diesen ungünstigen
Zustand gar nicht erst
herbeizuführen
Deadlocks
z
Ein Deadlock kann nur dann vorliegen, wenn alle 4 folgenden
Bedingungen gleichzeitig erfüllt sind:
–
Zyklische Wartebedingung (circular-wait)
z
–
Wechselseitiger Ausschluss (exclusive-use)
z
–
jedes Betriebsmittel eines Betriebssystems ist zu einem Zeitpunkt entweder nur
von genau einem einzigen Prozess exklusiv belegt oder aber frei
Belegungs- und Wartebedingung (hold-and-wait)
z
–
ein Prozess wartet auf eine Ressource, die der nächste Prozess einer
Prozesskette bereits zugewiesen bekommen hat (siehe Abb. der vorherigen
Folie)
alle am Deadlock beteiligten Prozesse belegen bereits Betriebsmittel und fordern
noch weitere Betriebsmittel an, die momentan nicht frei sind
Nicht-Unterbrechbarkeit (no-preemption)
z
ein Prozess kann einem anderen Prozess nicht einfach ein Betriebsmittel
entziehen, das dieser zur Zeit belegt
http://olli.informatik.uni-oldenburg.de/Deadlock/Html/Deadlock_Verfahren.html
Synchronisation über Warten und
Benachrichtigen
z
Die Synchronisation von Methoden oder Blöcken ist eine
einfache Möglichkeit, konkurrierende Zugriffe von der virtuellen
Maschine auflösen zu lassen
z
Obwohl die Umsetzung mit den Locks die Programmierung
einfach macht, reicht dies für viele Aufgabenstellungen nicht
aus
z
Zwar können Daten ausgetauscht werden, doch gewünscht ist
dieser Austausch in einer synchronisierten Abfolge
¾
Threads sollen sich ggf. gegenseitig über den aktuellen
Zustand informieren
Synchronisation über Warten und
Benachrichtigen
z
In Java wird dies durch die speziellen Objektmethoden wait() und notify()
realisiert
z
Wenn wir den Lock am Objekt o festmachen und warten, dann schreiben wir:
synchronized( o )
{
try {
o.wait();
} catch ( InterruptedException e ) {}
}
z
Ein wait() kann mit einer InterruptedException vorzeitig abbrechen, wenn der
wartende Thread per interrupt()-Methode unterbrochen wird
Synchronisation über Warten und
Benachrichtigen
z
z
Wenn der aktuelle Thread das Lock eines Objekts besitzt, kann
er Threads aufwecken, die auf dieses Objekt warten:
Synchronisation über Warten und
Benachrichtigen
import java.util.LinkedList;
import java.util.Queue;
class Erzeuger extends Thread
{
private final static int MAXQUEUE = 13;
private Queue<String> nachrichten = new LinkedList<String>();
synchronized void benachrichtige()
{
notifyAll();
}
public void run()
{
try {
while ( true )
{
erzeuge();
sleep( (int)(Math.random()*1000) );
}
} catch ( InterruptedException e ) { }
}
Um notify() muss es keinen eine Exception auffangenden Block
geben
public synchronized void erzeuge() throws InterruptedException
{
while ( nachrichten.size() == MAXQUEUE )
wait();
nachrichten.add( new java.util.Date().toString() );
notifyAll(); // oder notify();
}
// vom Verbraucher aufgerufen
public synchronized String verbrauche() throws InterruptedException
{
while ( nachrichten.size() == 0 )
wait();
notifyAll();
return nachrichten.poll();
}
}
Semaphore
z
Synchronisationsprobleme lassen sich durch kritische Abschnitte und
Wartesituationen mit wait() und notify() lösen
z
Dennoch ist der eingebaute Mechanismus auch mit Nachteilen
verbunden
–
eine große Schwierigkeit ist es, synchronisierende Programme zu
entwickeln und zu warten
–
Synchronisationsvariablen verstreuen sich mitunter über große
Programmteile und machen die Wartung schwierig
–
Teile, die bewusst atomar ausgeführt werden müssen, benötigen zwingend
einen Programmblock und eine Synchronisationsvariable
class Verbraucher extends Thread
{
Erzeuger erzeuger;
String name;
Verbraucher( String name, Erzeuger erzeuger )
{
this.erzeuger = erzeuger;
this.name = name;
}
public void run()
{
try {
while ( true ) {
String info = erzeuger.verbrauche();
System.out.println( name +" holt Nachricht: "+ info );
Thread.sleep( (int)(Math.random()*1000) );
}
} catch ( InterruptedException e ) { }
}
}
public class ErzeugerVerbraucherDemo
{
public static void main( String args[] )
{
Erzeuger erzeuger = new Erzeuger();
erzeuger.start();
new Verbraucher( "Eins", erzeuger ).start();
new Verbraucher( "Zwei", erzeuger ).start();
new Verbraucher( "Drei", erzeuger ).start();
}
}
Semaphore
z
Eine Semaphore kann als eine Datenstruktur aufgefasst
werden, die eine nicht-negative Integervariable und eine
Referenz auf eine Warteschlange enthält
z
Auf einer Semaphoren s werden zwei Operationen P(s) und
V(s) definiert
z
Anhand von s wird die Zuordnung einer Semaphore zu einer
Warteschlange vorgenommen
z
Eine Semaphore wird als fair bezeichnet, wenn sie sicherstellt,
dass kein wartender Prozess/Thread verhungern kann
(Starvation)
Semaphore
z
z
Semaphore in Java (>=1.5)
Die P()-Operation verringert den Wert einer
Semaphore um 1, wenn ihr Wert größer null
ist. Wenn nicht, wird der aufrufende Prozess
suspendiert
Die V()-Operation weckt, falls vorhanden,
einen suspendierten Prozess auf. Ansonsten
erhöht sie den Wert der Semaphore um 1
Semaphore in Java (>=1.5)
public void run()
{
for(;;)
{
try
{
System.out.print(t.getName());
System.out.println(" trying to acquire
permit");
s.acquire();
System.out.print(t.getName());
System.out.println(" acquired permit");
// kritische Arbeit
for(int i=0; i<=10000000; i++);
s.release();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
import java.util.concurrent.Semaphore;
public class SemaphoreThread implements Runnable
{
Thread t;
Semaphore s;
public SemaphoreThread(Semaphore s)
{
this.s = s;
t = new Thread(this);
t.start();
}
public static void main(String[] args)
{
// 1 permit, fairness: false
Semaphore s = new Semaphore(1, false);
SemaphoreThread st1 = new SemaphoreThread(s);
SemaphoreThread st2 = new SemaphoreThread(s);
}
}
java.util.concurrent.Semaphore
aquire()
statt P()
statt V()
release()
Konstruktor: Semaphore(int permits, boolean fair);
erster Parameter gibt Anzahl erlaubter paralleler Zugriffe an
zweiter Parameter gibt an, ob die Semaphore fair ist
wechselseitiger Ausschluss durch binäre Semaphore
möglich
Quellen- und Literaturhinweise
z
http://www.galileocomputing.de/openbook/javainsel4
z
http://java.sun.com/docs/books/tutorial/essential/threads/index.html
Die Speicherhierarchie
schneller
kleiner
Virtueller Speicher
z
Trennung von logischem und physikalischem Speicher
–
Registers
–
Internal
Cache
z
Secondary
Cache
Ein Prozess adressiert logische (=virtuelle) Speicheradressen
Diese werden vom Betriebssystem in physikalische Adressen
umgesetzt
Vorteile:
–
ein einzelner Prozess kann einen größeren Speicher adressieren
als physikalisch vorhanden ist
–
alle Prozesse erhalten den identischen (virtuellen), flachen
Speicherbereich
Seitentausch
Zentralspeicher
Festplatte
langsamer
größer
Backup-Medien (CD-ROM,
Bandlaufwerke)
Virtuelle Speicherverwaltung
z
z
z
Prozess arbeitet mit logischen Adressen, werden
vom BS auf physikalische umgesetzt
Virtueller Speicher wird in Seiten (pages) unterteilt
Hardware-Unterstützung durch MMU
–
–
–
–
z
Nebeneffekte des Paging
z
z
z
Umsetzung von virtueller auf physikalische Adresse
Seitenfehler (Page Fault) bei Zugriff auf ausgelagerte Seite
R-Bit (Reference Bit): wird gesetzt, wenn Seite benutzt
wurde
M-Bit (Modified Bit): wird gesetzt, wenn Seite geändert
wurde
Seiten werden auf Anforderung vom BS
bereitgestellt (Demand Paging)
Paging kann zu Verzögerungen des Programms führen →
ungeeignet für Echtzeitsysteme
Paging ist transparent für den Benutzer. Aber: Nebeneffekte
sind möglich!
Beispiel: Seitengröße 4kByte; Programm benötigt Feld mit
1024 mal 1024 Integer-Zahlen
int i, j, a[1024][1024];
// schnelle Schleife:
for(i=0; i<1024; i++)
for(j=0; j<1024; j++)
a[i][j] = 0;
z
// langsame Schleife:
for(i=0; i<1024; i++)
for(j=0; j<1024; j++)
a[j][i] = 0;
Links 1024 Seitenanforderungen, rechts 1024*1024 !
Seitentauschstrategien
z
Problem: wenn kein freier Seitenrahmen vorhanden
ist, wie kann eine neue Seite eingelagert werden?
–
Es muss zunächst eine andere entfernt werden!
Optimale Strategie
z
z
Welche Seite kann zuerst
entfernt werden?
z
Ziel: möglichst wenig Seitenfehler
–
–
Verschiedene Seitentauschstrategien
Implementierung muss mit vorgegebener Hardware möglich
sein
Seitentauschstrategien - Benchmark
z
z
z
z
First-In First-Out (FIFO)
Ziel: möglichst wenig Seitenfehler
Implementierung muss mit vorgegebener Hardware
möglich sein
Test einer Seitentauschstrategie, indem eine
spezielle Seitenanforderungsfolge (reference string)
durchlaufen und die Anzahl der Seitenfehler
gemessen wird
z
Beispiel: 0, 1, 2, 4, 0, 1, 3, 0, 1, 2, 4, 3
Seitenanforderungsfolgen können zufällig mit
vorgegebenen Verteilungen erzeugt werden, oder von
echten Programmläufen gewonnen werden
z
–
–
Optimale Strategie: Suche die Seite, die am
spätesten wieder benutzt wird
Im Allgemeinen unmöglich zu
implementieren, da keine Vorhersage in die
Zukunft möglich ist
Ist jedoch wichtig als Vergleichsmöglichkeit
für andere Seitentauschstrategien
z
z
z
Die Seite wird entfernt, die als erste geladen wurde
Einfach zu implementieren
Nur minimale Hardware-Unterstützung nötig
Anfällig für Belady‘s Anomalie: in seltenen Fällen
kommt es bei mehr Seitenrahmen zu einer Erhöhung
der Seitenfehler (Belady et.al., 1969)
Wurde in den ersten VAX/VMS Rechnern verwendet
Belady‘s Anomalie
Belady‘s Anomalie
Belady‘s Anomalie
Least-Recently Used (LRU)
z
Seitenanforderungsfolge:0,1,2,3,0,1,4,0,1,2,3,4
z
z
0
3 4
0
4 3
1
0 2
1
0 4
2
1 3
2
1
3
2
9 Seitenfehler
(Optimal: 7)
z
10 Seitenfehler
(Optimal: 6)
(zweiter Fall: 012401301243)
z
Suche die Seite, die am längsten unbenutzt war
Kommt der optimalen Strategie nahe, ist jedoch
sehr aufwändig zu implementieren
LRU Simulationen in Software:
–
–
z
Verwendung von Zählern
Stack Algorithmen
Alle LRU-Algorithmen sind immun gegen
Belady‘s Anomalie
LRU Näherung:
Zählerbasierte Tauschstrategien
z
LFU (Least Frequently Used): Für jede Seite wird ein
Zähler mitgeführt; wurde die Seite benutzt (d.h. R-Bit
ist 1), so wird der Zähler erhöht.
–
–
z
Die Seite mit dem niedrigsten Zählerstand wird entfernt
Problem: LFU vergisst nicht: unbenutzte Seiten, die vor
langer Zeit mal stark genutzt wurden, verbleiben im
Speicher
Altern (Aging): bei unbenutzten Seiten wird der
Zähler vermindert:
–
Exponentielles Altern:
z = z >> 1;
if( r_bit > 0 ) z += 128;
r_bit = 0;
–
Lineares Altern:
if( r_bit > 0 ) z += 3;
else if( z>0 ) z--;
r_bit = 0;
Quellen- und Literaturhinweise
z
„Modern Operating Systems“, 2nd Edition, Andrew Tanenbaum,
Prentice Hall, 2001 (deutsch: „Moderne Betriebssysteme“ im Verlag
Pearson Studium)
z
„Operating System Concepts“, 6th Edition, A. Silberschatz et.al,
Wiley, 2003
z
Informationen zu Linux VM unter www.linux-mm.org, insbesondere
„Too little, too slow“ von Rik van Riel
Zusammenfassung
Seitentauschstrategien
z
z
z
z
z
Optimale Strategie nicht umsetzbar
FIFO: einfach, nur minimale HardwareAnforderungen, aber Belady Anomalie
LRU: gut, schwer zu implementieren
LRU Näherungen: Second Chance, LFU, Aging, u.a.
Seitentauschstrategien sind wichtige Teile der
komplexen virtuellen Speicherverwaltung
Herunterladen