Betriebssysteme Tafelübung 3. Deadlock Spinczyk - TU Dortmund
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
Betriebssysteme Tafelübung 3. Deadlock https://ess.cs.tu-dortmund.de/DE/Teaching/SS2018/BS/ Olaf Spinczyk olaf.spinczyk@tu-dortmund.de http://ess.cs.tu-dortmund.de/~os AG Eingebettete Systemsoftware Informatik 12, TU Dortmund
Agenda ● Besprechung Aufgabe 2: Threadsynchronisation ● Aufgabe 3: Deadlock – Semaphore – Wiederverwendbare/Konsumierbare Betriebsmittel – Problemvorstellung – Voraussetzungen für Verklemmungen – Verklemmungsbehandlung – Makefiles ● Alte Klausuraufgabe zu Semaphoren 2
Besprechung Aufgabe 2 ● → Foliensatz Besprechung 3
Wiederholung Mutex ● Mutex dienen zum Schutz von kritischen Abschnitten – Nebenläufige Prozesse können hierdurch auf eine gemeinsame Datenstruktur zugreifen, ohne dass der Zustand der Datenstruktur inkonsisten wird. /* Schlossvariable (Initialwert 0) */ pthread_mutex_t rangmutex; … int main(void){ … Kritische Abschnitte pthread_mutex_init(&rangmutex, NULL); können auch durch … Semaphore geschützt werden. } Dazu müssen wir aber zunächst wissen, was void* run(void* arg) { Semaphore sind und wie … wir sie benutzen können. pthread_mutex_lock(&rangmutex); rangliste[rang++] = team; pthread_mutex_unlock(&rangmutex); … } 4
Mutexe vs. Semaphoren ● Mit beiden können kritische Abschnitte geschützt werden. ● Beim Mutex kann immer nur ein Thread den kritischen Abschnitt betreten. ● Mit Semaphoren können n Threads den kritischen Abschnitt betreten. ● Nützlich für Betriebssystemressourcen, bei denen eine bestimmte Anzahl zur Verfügung steht. ● Für n=1 verhält sich ein Semaphor ähnlich wie ein Mutex. Semaphoren können aber auch von unterschiedlichen Prozessen freigegeben werden. 5
Semaphoren ● Ein Sempahor ist eine Betriebssystemabstraktion zum Austausch von Synchronisierungssignalen zwischen nebenläufig arbeitenden Prozessen. ● Steht für „Signalgeber“ ● E. Dijkstra: Eine „nicht-negative ganze Zahl“, für die zwei unteilbare Operationen definiert sind: P (holländisch prolaag, „erniedrige“; auch down oder wait) – hat der Semaphor den Wert 0, wird der laufende Prozess blockiert – ansonsten wird der Semaphor um 1 dekrementiert V (holländisch verhoog, „erhöhe“; auch up oder signal) – auf den Semaphor ggf. blockierter Prozess wird deblockiert – ansonsten wird der Semaphor um 1 inkrementiert 6
Eselsbrücken zu Semaphoren ● Mit P wartet man auf eine Ressource und belegt diese dann. Eselsbrücke: „p(b)elegen, ggfs. vorher warten” Es sind danach weniger Ressourcen verfügbar, also wird runtergezählt. ● Mit V gibt man eine Ressource wieder frei, ggfs. wird der nächste wartende Thread benachrichtigt. Eselsbrücke: „v(f)reigeben, ggfs. benachrichtigen” Es sind danach wieder mehr Ressourcen verfügbar, also wird hochgezählt. 7
Semaphor – Beispiel ● Gemeinsam genutzte FIFO Queue mit maximal 100 Elementen void enqueue(element *item){ /* gem. Speicher */ if (item != NULL){ Semaphore lock; p(&freiePlaetze); Semaphore freiePlaetze; p(&lock); struct List * queue; queue->tail = item; ... /* Initialisierung */ v(&lock); lock = 1; } freiePlaetze = 100; } queue->head = NULL; element * dequeue(){ queue->tail = NULL; p(&lock); element *item = queue->head; ... Mehrere Threads können if (item != NULL) v(&freiePlaetze); Elemente in die Queue schreiben, v(&lock); andere Threads können dann return item; Elemente wieder herausnehmen. } 8
Semaphor – Beispiel ● Zusätzlich noch einseitige Synchronisation /* gem. Speicher */ void producer(){ Semaphore verfuegbar; while(1){ element *e = produce(); enqueue(e); /* Initialisierung */ v(verfuegbar); verfuegbar = 0; } } void consumer(){ Die Consumer lesen nur, wenn while(1){ etwas in der Queue steht. p(verfuegbar); element *e = dequeue(); consume(e); } } 9
POSIX Semaphoren int sem_init(sem_t *sem, int pshared, unsigned int value); ● Anlegen einer Semaphore – Parameter #include ● sem: Adresse des Semaphor-Objekts ● pshared: 0, falls nur zwischen Threads eines Prozesses verwendet ● value: Initalwert der Semaphore, entspricht dem n – Rückgabewert: ● 0, wenn erfolgreich ● -1 im Fehlerfall – Bsp.: sem_t semaphor; if (sem_init(&semaphor, 0, 1) == -1) { /* Fehlerbehandlung */ } 10
POSIX Semaphoren ● Belegen einer Semaphore (P), ggf. müssen wir vorher warten: int sem_wait(sem_t *sem); ● Freigeben einer Semaphore (V), ggf. wird der nächste Thread int sem_post(sem_t *sem); benachrichtigt: ● Entfernen einer Semaphore: int sem_destroy(sem_t *sem); – Parameter ● sem: Adresse des Semaphor-Objekts – Rückgabewert: ● 0, wenn erfolgreich ● -1 im Fehlerfall 11
POSIX Semaphore ● Beispiel: Gegenseitiger Ausschluss mit Semaphore #include /* Konsumierbare Rangsemaphore */ sem_t rangsemaphore; globale Deklaration, ... damit auch Threads int main(void){ Zugriff haben sem_init(&rangsemaphore, 0, 1); ... sem_destroy(&rangsemaphore); return 0; } void* run(void* arg) { ... sem_wait(&rangsemaphore); rangliste[rang++] = team; Fehlerbehandlung! sem_post(&rangsemaphore); ... } 12
Betriebsmittel ... werden vom Betriebssystem verwaltet und den Prozessen zugänglich gemacht. Man unterscheidet zwei Arten: ● Wiederverwendbare Betriebsmittel – Werden von Prozessen für eine bestimmte Zeit belegt und anschließend wieder freigegeben. – Beispiele: CPU, Haupt- und Hintergrundspeicher, E/A-Geräte, Systemdatenstrukturen wie Dateien, Prozesstabelleneinträge, … – Typische Zugriffssynchronisation: Gegenseitiger Ausschluss ● Konsumierbare Betriebsmittel – Werden im laufenden System erzeugt (produziert) und zerstört (konsumiert) – Beispiele: Unterbrechungsanforderungen, Signale, Nachrichten, Daten von Eingabegeräten – Typische Zugriffssynchronisation: Einseitige Synchronisation Vorlesungsfolie – Vorlesung 6 S. 15 13
Problemvorstellung Das Szenario: Chemiker in einem Chemielabor führen Experimente durch und benötigen dazu Chemikalienflaschen von einem zentralen Vorrat. Jeder Chemiker holt sich die erste für sein Experiment notwendige Chemikalie, verarbeitet diese, holt dann die zweite, verarbeitet diese und so weiter. Die Chemikalienflaschen werden erst zurückgebracht, nachdem das Experiment abgeschlossen ist. Da die Anzahl an Chemikalienflaschen begrenzt ist, müssen Chemiker ggf. warten, bis ein anderer Chemiker eine passende Flasche zurückbringt. 14
Deadlock-Voraussetzungen Die notwendigen Bedingungen für eine Verklemmung: 1. „mutual exclusion“ – die umstrittenen Betriebsmittel sind nur unteilbar nutzbar 15
Deadlock-Voraussetzungen Die notwendigen Bedingungen für eine Verklemmung: 1. „mutual exclusion“ – die umstrittenen Betriebsmittel sind nur unteilbar nutzbar 2. „hold and wait“ – die umstrittenen Betriebsmittel sind nur schrittweise belegbar 16
Deadlock-Voraussetzungen Die notwendigen Bedingungen für eine Verklemmung: 1. „mutual exclusion“ – die umstrittenen Betriebsmittel sind nur unteilbar nutzbar 2. „hold and wait“ – die umstrittenen Betriebsmittel sind nur schrittweise belegbar 3. „no preemption“ – die umstrittenen Betriebsmittel sind nicht rückforderbar 17
Deadlock-Voraussetzungen Die notwendigen Bedingungen für eine Verklemmung: 1. „mutual exclusion“ – die umstrittenen Betriebsmittel sind nur unteilbar nutzbar 2. „hold and wait“ – die umstrittenen Betriebsmittel sind nur schrittweise belegbar 3. „no preemption“ – die umstrittenen Betriebsmittel sind nicht rückforderbar Erst wenn zur Laufzeit eine weitere Bedingung eintritt, liegt tatsächlich eine Verklemmung vor: 4. “circular wait” – eine geschlossene Kette wechselseitig wartender Prozesse 18
Deadlock-Voraussetzungen 4. „circular wait“ – eine geschlossene Kette wechselseitig wartender Prozesse ist belegt von A wird benötigt von B Ressource 1 Thread: A Thread: B Ressource 2 wird benötigt von A ist belegt von B 19
Verklemmungsauflösung ● Die „einfachste“ Variante: Prozesse abbrechen und so Betriebsmittel frei bekommen – Verklemmte Prozesse schrittweise abbrechen (großer Aufwand) – Mit dem „effektivsten Opfer“ (?) beginnen – Oder: alle verklemmten Prozesse terminieren (großer Schaden) → Diese Lösung wollen wir verwenden 20
Pthreads abbrechen int pthread_cancel(pthread_t thread); ● Sendet eine Anfrage zum Abbrechen des Pthreads – Parameter ● thread: Thread-Objekt – Rückgabewert: ● 0, wenn erfolgreich ● ≠0 im Fehlerfall ● Wann der Thread auf den Abbruch reagiert hängt vom – gesetzten Abbruchzustand (enabled oder disabled) – und dem gesetzten Abbruchtyp ab (asynchronous oder deferred). 21
Makefiles ● Bauen von Projekten mit mehreren Dateien ● Makefile → Informationen wie eine Projektdatei beim Bauen des Projektes zu behandeln ist # -= Variablen =- # Name=Wert oder auch # Name+=Wert für Konkatenation CC=gcc CFLAGS=-Wall -ansi -pedantic -D_XOPEN_SOURCE -D_POSIX_SOURCE # -= Targets =- # Name: # Kommando # Kommando ... (ohne beschwert sich make!) all: program1 program2 # erstes Target = Default-Target program1: prog1.c prog1.h $(CC) $(CFLAGS) -o program1 prog1.c program2: prog2.c prog2.h program1 # Abhängigkeit: benötigt program1! $(CC) $(CFLAGS) -o program2 prog2.c 22
Targets & Abhängigkeiten program1 program2 prog1.c prog2.c prog1.h prog2.h ## target target program1 program1 program1: program1: prog1.c prog1.c prog1.h prog1.h ... ... ## target target program2 program2 program2: program2: prog2.c prog2.c prog2.h prog2.h program1 program1 ... ... ● Vergleich von Änderungsdatum der Quell- und Zieldateien – Quelle jünger? → Neu übersetzen! ● make durchläuft Abhängigkeitsgraph ● Java-Pendant: Apache Ant 23
Client-/Server-Beispiel # -= Variablen =- # Name=Wert oder auch # Name+=Wert für Konkatenation CC=gcc CFLAGS=-Wall -ansi -pedantic -D_XOPEN_SOURCE -D_POSIX_SOURCE # -= Targets =- # Name: # Kommando # Kommando ... (ohne beschwert sich make!) all: chatclient chatserver # erstes Target = Default-Target chatclient: chatclient.c chatshm.c chatshm.h sync.c sync.h $(CC) $(CFLAGS) -o chatclient chatclient.c chatshm.c sync.c chatserver: chatserver.c chatshm.c chatshm.h sync.c sync.h $(CC) $(CFLAGS) -o chatserver chatserver.c chatshm.c sync.c clean: rm -f chatclient chatserver *.o .PHONY: all clean # “unechte” (phony) Targets 24
Makefiles und make(1) hsc@ios:~/A2/vorgaben$ ls chatclient chatclient.c chatserver chatserver.c chatshm.c chatshm.h Makefile sync.c sync.h hsc@ios:~/A2/vorgaben$ make clean rm -f chatclient chatserver *.o hsc@ios:~/A2/vorgaben$ make gcc -Wall (...) -o chatclient chatclient.c chatshm.c sync.c gcc -Wall (...) -o chatserver chatserver.c chatshm.c sync.c hsc@ios:~/A2/vorgaben$ touch chatserver.c hsc@ios:~/A2/vorgaben$ make gcc -Wall (...) -o chatserver chatserver.c chatshm.c sync.c hsc@ios:~/A2/vorgaben$ make make: Nothing to be done for `all'. hsc@ios:~/A2/vorgaben$ touch sync.h hsc@ios:~/A2/vorgaben$ make gcc -Wall (...) -o chatclient chatclient.c chatshm.c sync.c gcc -Wall (...) -o chatserver chatserver.c chatshm.c sync.c hsc@ios:~/A2/vorgaben$ 25
Makefile ● Makefiles ausführen mit make – bei fehlendem wird das Default-Target (das 1.) ausgeführt – Optionen ● -f: Makefile angeben; make -f ● -j: Anzahl der gleichzeitig gestarteten Jobs, z.B. make -j 3 26
Klausuraufgabe: Synchronisierung Initialwerte der Semaphore: S1 = S2 = S3 = chicken1() { chicken2() { printf("to"); printf("get"); } printf("to"); chicken3() { printf("the"); printf("other"); printf("side"); } } 27
Klausuraufgabe: Synchronisierung Initialwerte der Semaphore: S1 = 0 S2 = 0 S3 = 0 chicken2() { chicken1() { P(S2); printf("to"); printf("get"); V(S2); V(S1); P(S1); } printf("to"); chicken3() { V(S3); P(S3); P(S1); printf("the"); printf("other"); V(S1); V(S3); P(S3); } printf("side"); } 28
Klausuraufgabe: Synchronisierung 29
Sie können auch lesen