This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
L.L. NebenläufigkeitNebenläufigkeit
L.1. Einordnung
Betriebssystemkonzepte:Nebenläufige Ausführung von Programmteilen,Nondeterminismen & Synchronisierung,Transaktionen, Threads und Prozesse.
Direkte Programmierung: - Geräteregister, E/A-Adressraum, Hauptplatine
LC
E
G
KI
H
FD
L.2. Nebenläufigkeit
2 Abläufe (Threads) sind nebenläufig (concurrent), wenn die Verschränkung der Instruktionsausführung (Schreiben/Lesen) nicht deterministisch erfolgt.Der Frontside-Bus garantiert die Atomarität des Speicherzugriffs.
Th1 Th2 Physische Nebenläufigkeit, mehrere CPUs vorhanden, keine Kontextumschaltung.X
Th1 Th2
Logische Nebenläufigkeit, nur eine CPU vorhanden, Kontextumschaltung.
X
L.3. Synchronisierung nebenläufiger Abläufe
Mehrprogrammbetrieb: Programme greifen auf die gemeinsame Hardware & Software zu,viele Programme konkurrieren "gleichzeitig" um Ressourcen,z.B. Hauptspeicher, CPU(s), Disk, Devices, Interrupts ...
Race condition: zwischen nebenläufigen Prgrammen (Threads),Lesen & /Schreiben in unsicherer Reihenfolge,Resultat der Rechnung ist nicht deterministisch,"mal ist der eine, mal der andere zuerst da",könnte die Zuweisung von "1" entfallen?
Synchronisierung:erzwingt eine deterministische Ausführung,unterschiedl. Synchronisierungstechniken,z.B. mit Signal & Wait.
Programmabschnitte, die auf gemeinsame Variablen zugreifen und des-halb einer Synchronisierung bedürfen:
wechselseitiger Ausschluss gewünscht max. 1 Thread im kritischen Abschnitt.keine Annahmen bezüglich CPU-Geschwindigkeit oder Anzahl der CPUs, ...Fairness: Wartezeit für Eintritt in kritischen Abschnitt muss begrenzt sein.Fortschrittsgarantie: keine Verklemmungen,in Java mit Schlüsselwort "synchronized".
Unterschiedliche Programmabschnitte nutzen dieselben Variablen. => Nicht Programmabschnitt, sondern Variablenobjekt wird geschützt.Metapher einer Strassenkreuzung:
Transaktion "SyncDemo" wird 109 mal aus der Plurix-Hauptschleife heraus aufgerufen,Timer-Interruptroutine wird 18,2 mal pro Sekunde aufgerufen,13 Inkrementierungen aus 109 gehen verloren.
Nebenläufige Programmteile inkrementieren 2 gemeinsame Variablen:Variable "syncCount" wird unter wechselseitigem Ausschluss inkrementiert,Variable "asyncCount" wird ohne weitere Synchronisierung inkrementiert,Inkrementierungen von "asyncCount" gehen verloren.
L.5.2. Ausgabe des Programmes (verschönert)Eine Ausgabezeile jeweils nach 1000'000 Transaktionen:
durch Interruptsperre geschützte, synchrone Inkrementierungen,ungeschützte, asynchrone Inkrementierungen (asyn++),verlorengegangene asynchrone Inkrementierungen,Anzahl Aufrufe der Timer ISR-Routine,Anzahl der ausgeführten Transaktionen,Summe der Timer und TA-Aufrufe.
L.5.3. Rainbow-Transaktionen und HauptschleifeIm Rainbow-OS sind Transaktionen rücksetzbar (hier noch nicht):
im verteilten Rainbow OS können alle Anderungen falls nötig rückgängig gemacht werden.läuft eine Transaktion jedoch zu Ende, so werden deren Resultate "committed",ähnlich dem ACID-Konzept für Datenbank-Transaktionen.
Transaktionen werden aus der Hauptschleife heraus aufgerufen:TAs sind im Transaktionsvektor der jeweiligen Station eingetragen,Hauptschleife erschient als Methode "loop" in der Klasse Station,Die Transaktionen beginnen und enden mit leerem Stack,teilen den Stack aber mit der Interruptroutine.
private void Loop(){ int taIndex; // indiziert Transaktionsvektor
Transaction currentTA; // in Ausführung begriffene TAwhile (true) // Endlosschleife (fast endlos){ taIndex=(taIndex+1)% freeTASlot; // nächste TA auswählen
currentTA = TAVector[ taIndex ]; // if (currentTA.scheduleTime <= STATION.ticks) // nur wenn fällig
L.5.4. Transaktionsklasse "SyncDemo"Alle User-Transaktionen müssen von Transaction abgeleitet werden:
User-Transaktionen erben die finalen Methoden Install() und Remove(),werden im Transaktionsvektor der Hauptschleife installiert,können Felder und Methoden hinzufügen,überschreiben die Methode run().
import transactions.Transaction; import kernel.Interrupts; import plurix.Viewer;public class SyncDemo extends Transaction // only transactions can be installed{ public static int syncCount, asyncCount, timerCount, taCount; // includes shared variables static Viewer out; // needed for printing
public void run(){ taCount++; // count our invocations, no conflicts
L.5.5. Timer-Routine:Während der Ausführung der ISR sind die Interrupts gesperrt:
weitere externe Interrupts sind hier nicht möglich (interne schon),kann nicht gestört werden, stört aber die reguläre Transaktion,die ISR-Methode läuft "atomar" ab.
package devices; // timer must go into package devicespublic class Timer extends Device // Handle interval timer - through interrupt #0{ final int blinker = 0xb8000 + 3998; // last Screenposition, ... + 25 * 80 * 2 -2
public int ISR() { // second level handler onlySyncDemo.syncCount++; // static context accessibleSyncDemo.asyncCount++;SyncDemo.timerCount++; // Timer invocationsSTATION.ticks=STATION.ticks+1; // actual timer workif((STATION.ticks % 18) == 0 )
MAGIC.Mem32[blinker]=MAGIC.Mem32[blinker] ^ 0x7700; // alivereturn 1; // interrupt was handled
Kontextcontainer ( Kontext-Abstraktion ?):als Konstrukte in verschiedenen Programmiersprachen zu finden.
hat eigenen => Scope Code PC concurrent Stack Adressraum Resourcen Objektinstanz ja nein nein nein nein nein neinModul/Klasse ja ja nein nein nein nein neinProzedur ja ja ja nein nein nein neinTransaktion ja ja ja ja nein nein neinThread ja ja ja ja ja nein neinProzess ja ja ja ja ja ja ja
Der getrennte Adressraum erschwert die Interprozesskommunikation.Resourcen sind zum Beispiel Zugriffsrechte, Priorität, Dateien, Heap ...Wichtig bei Transaktionen ist oft die isolierende Zugriffssemantik.
die einzelnen Tasks (Teilaufgaben) laufen solange, bis sie freiwillig die Kontrolle abgeben,keine "Preemption" von Tasks niedrigerer Priorität durch solche mit höherer Priorität,minimale Kontextumschaltung, entsprechend einem Prozeduraufruf, nur ein Stack ist vorhanden, der von allen genutzt wird,Interrupts als "accidental" Prozeduraufruf,ein gemeinsamer Adressraum,z.B.Plurix-Transaktionen.
Multitasking mit Koroutinen:explizite Umschaltung des Laufzeitkellers (Stack),enterCoroutine(), exitCoroutine() ...
Multithreading:Umschaltung des Laufzeitkellers durch das Betriebssystem,die verschiedenen Threads teilen sich den Adressraum.
Prozessumschaltung (!=Multiprocessing):zusätzlich zum Multithreading wird auch der Adressraum umgeschaltet,umfassende Isolation der einzelnen nebenläufigen Prozesse,wechselseitiger Zugriffschutz zwischen Prozessen,Interprozesskommunikation aufwendig,zeitaufwendige Prozessumschaltung.
L.7. SynchronisierungsmechanismenL.7.1. Übersicht über SynchronisierungstechnikenTest & Set: Besondere Maschineninstruktionen, auch für MP-Szenarien.Interruptsperre: verhindert Umschaltung, aber nur auf einer CPUSignal und Wait: Spezialfall einer BarrierenoperationMutex (Windows): Windowsvariante einer Semaphor-VariablenMonitore (P. Hansen): High-level Konstrukt z.B. für Concurrent Pascal.Semaphore (E. Dijkstra): Synchronisierungsvariable mit atomaren Op.Rendez-vous (ADA-Sprache): Gleichzeitiges Warten auf Eventgruppe.Synchronized Anweisung (Java-Sprache): ~ kritische Region.CSP - Communicating Sequential Processes (C. Hoare).Software-based mutual exclusion (Peterson's Algorithmus).
L.7.2. Atomare Maschinenoperationen Viele CPUs haben eine atomare Test-&-Set-Instruktion:Test: Speicherwort lesen & prüfen, Set: Speicherwort auf "true" setzen.
Instruktionsablauf mit atomarer / unteilbarer Bustransaktion:kein Zugriff durch DMA oder Bus-Master.keine Interrupts an der eigenen CPU,Cache-Konsistenz gewährleistet,kein Zugriff durch andere CPUs,reservieren des Speicherbusses.
L.7.3. Sperren von Interrupts als „Thread-Umschaltsperre“Werden Threads nur zufällig umgeschaltet, so entstehen Nichtdeterminismen.
Durch Sperren der Interrupts wird das Umschalten vermieden:CLI, STI bedienen das Interrupt Enable Flag der CPU und sperren Interrupts,sind privilegierte x86-Instruktionen - nur für Treiber & OS zulässig,unbrauchbar für Multiprozessorszenarien.
Vorsicht beim Sperren der Interrupts:Nur für sehr kurze Abschnitte in Kern und Treibern empfohlen,sonst Scheduler, Effizienz und Fairness beeinträchtigt,rekursive Interrupt-Sperre ist heikel.
...CLI... // kritischer Abschnitt 2if ... then STI // nicht immer
L.7.4. Semaphor-VariablenSemaphor: (Wortbedeutung aus dem Griechischen)
optischer Telegraph; Mast mit Armen, durch deren Verstellung Zeichen zur Nachrichtenübermittlung weitergeleitet werden; schon im Altertum bekannt; Im Eisenbahnwesen auch Bezeichnung für ein Hauptsignal; in der Schiffahrt zur Anzeige von Windrichtung und Windstärke von der Küste aus.
Variable zur Synchronisierung mit folgenden atomaren Operationen:
semVar:=semVar+ 1;if { Prozess wartet auf semVar }
then anstossen( Prozess )
if semVar > 0 then semVar:=semVar-1else { warten auf V( semVar ) }
L.7.5. FunktionsmodellSemaphore werden z.B. mit Test&Set-Instruktionen implementiert:
Entweder direkt durch den Compiler oder durch die Laufzeitunterstützung ...
Semaphor-Variablen haben Queue zur Verwaltung wartender Threads.Beim Starten wartender Threads evt. Prioritäten berücksichtigen.Einträge verweisen auf Task-/Thread-/Process-Kontrollblöcke,kein Busy-Waiting, keine leeren CPU-Zyklen,alle wartenden Threads sind „blocked“.
Monitor-Prozeduren garantieren:kein externer Zugriff auf Monitorvariablen,jeweils nur ein Thread im Monitor,automatische Freigabe des Monitors beim Rücksprung.
Im Vergleich dazu sind Semaphore etc. low-level Sprachelemente:Grösseres Risiko von logischen Programmierfehlern,ohne V() tritt eventuell eine Verklemmung auf,ohne P() kein gegenseitiger Ausschluß.
L.7.8. Incrementer-Monitor (Beispiel in "Concurrent Pascal"):Die Laufzeitumgebung sorgt mit Unterstützung des Compilers dafür, daß sich nur ein Thread im Monitor befindet.Realisiert eine ge-schützte kritische Region oder meh-rere verbundene.Stützt sich in der Regel auf Sema-phore des Betriebs-systems ab.Im Prinzip bietet auch Java Monitore. An Stelle von Monitoren werden aber Instanzen und Klassen mit dem Attribut "synchronized" verwendet.
monitor Incrementer; { Concurrent Pascal } var zaehler: integer;
procedure PlusEins; { P(IncSem) implizit } begin
zaehler:= zaehler+1 end { PlusEins }; { V(IncSem) implizit }
procedure PlusZwei … end;
begin { initialize }zaehler := 0
end { Incrementer }.
L.7.9. Dining PhilosophersSzenarium:
Problemstellung nach Edsger W. Djikstra,N Philosophen sitzen um einen Tisch,Sie denken & wollen Spaghetti essen,Zum Essen jeweils 2 Gabeln nötig,hier angepasst auf Steak & Messer.
Wissenheim-Animation:Modellierung Björn Mantelars,Betreuung Markus Fakler,
Java Lösungsansatz: Zustand eines Philosophen: THINKING, HUNGRY, EATING, STARVED.Boole'scher Array forks[]: Besetzte Gabeln dürfen nicht genommen werden,Essen nur möglich, wenn kein Nachbar gerade isst.Links = i; Rechts = (i+1) mod N;Konstruktor für Philosophen.
public class Philosopher extends Thread {static final String blanks= ": ";static final int MXPH=7, // How many philosophers ?
delayTime=10111222; // delay instead of try & sleep static boolean[] fork= new boolean[MXPH]; // one fork for each philosopherString indent, state="thinking "; // initial state is thinkingint starvation, lnx, rxz; // how hungry? Which forks?
Philosopher(int idParm){ // construct a philosopherthis.lnx=idParm; rxz=(lnx+1)% MXPH; // left & right forksindent=blanks.substring(0,3+10*lnx); // indent output
}static void delay(long count){ // rather than try ... sleep ... catch
Kritische Region synchronisieren:Methoden: Gabeln nehmen, Gabeln ablegen, Drucken,nur Synchronisierung auf ein Objekt oder auf eine Klasse möglich,hier also hinsichtlich der Philosophenklasse (forks kein Objekt!),nicht auf Instanz, sondern auf Klasse!
synchronized static boolean takeForks(int lnx,int rxz){ // synchronize on class !If( fork[lnx] | fork[rxz] ) return false; // forks not availablefork[rxz]=true; fork[lnx]=true; // beware of access collisionreturn true; // forks taken
}synchronized static void dropForks(int lnx, int rxz){