OOStuBS/MPStuBS
Aufgabe 6: Ereignisbearbeitung und Synchronisation

Lernziele

  • Synchronisation mit Hilfe von Semaphoren und anderen Kernobjekten
  • Passives Warten und Leerlauf

Aufgabenbeschreibung

StuBS soll nun um Synchronisationsobjekte für Anwendungen erweitert werden, sodass sich Threads gegenseitig über Ereignisse informieren können und auf Ereignisse von anderen Threads passiv warten können. In Aufgabe 6 sollen die folgenden Objekte implementiert werden:

  • Semaphoren (Gegenseitiger Ausschluss)
  • Bell (Threads für eine bestimmte Zeit schlafen lassen)

Hierzu müssen die Klassen Waitingroom, Semaphore, Scheduler, Bell, Bellringer, Guarded_Bell, Guarded_Semaphore, Guarded_Keyboard, Keyboard, Thread, Watch, sowie WakeUp implementiert bzw. angepasst werden. Weiter ist IdleThread zu implementieren, sodass Leerlaufthreads ausgeführt werden, wenn keine anderen Threads bereit zur Ausführung sind. Mit ihnen soll dann eine Möglichkeit geschaffen werden, den Prozessor schlafen zu lassen, wenn es keine Benutzerthreads zur Ausführung gibt.

Für die Leerlaufthreads soll je ein globales Objekt pro CPU angelegt werden, die ausgewählt werden, wenn keine anderen Threads im System sind. Werden Threads wieder aktiviert (also aus dem Schlafzustand geholt) und damit die Ready-Liste wieder gefüllt, müssen schlafende Prozessoren mit einem IPI wieder aufgeweckt werden.

Die Aufgabe 6 lässt sich in folgende Schritte einteilen:

  • Waitingroom, Semaphoren und Schutz eines Tastaturpuffers
  • Bell und Bellringer
  • Leerlaufthreads und Prozessoren schlafen legen
  • (optional) Tickless Kernel

(A) Waitingroom und Semaphoren

Zur Implementierung der Semaphoren und Bells soll ein Waitingroom verwendet werden. Die Synchronisationsobjekte zeichnen sich dadurch aus, dass in ihnen alle darauf schlafenden Threads abgespeichert werden. Wird bspw. bei Semaphore::P() der Thread schlafen gelegt, wird er in die Semaphore eingetragen. Ein Thread, der später Semaphore::V() ausführt, weckt dann einen der schlafenden Threads wieder auf.

Es müssen zunächst die Klassen Waitingroom, Semaphore, Thread, Scheduler und Guarded_Semaphore implementiert werden. Mithilfe von Semaphoren sollte sich, bei korrekter Umsetzung, verhindern lassen, dass sich die Anwendungsthreads bei der Bildschirmausgabe gegenseitig ins Gehege kommen können.

Dann kann die Keyboard::getKey() erweitert werden. Die lesenden Threads sollen nun mit einer Semaphore auf den Puffer zugreifen und warten, wenn keine Inhalte vorhanden sind. Der Tastaturinterrupt soll mithilfe der Semaphore anzeigen, wie viele Einträge im Puffer gespeichert sind. Ein Thread soll derart angepasst werden, dass er die Tastaturabfragen übernimmt und auf dem Bildschirm ausgibt.

Hier muss immer sichergestellt sein, dass genügend lauffähige Threads im System sind!

(B) Bells und Bellringer

Mithilfe vom Bellringer und den Bell-Objekten soll es Threads möglich sein, eine gewisse Zeit zu schlafen. Zunächst sollte der Bellringer programmiert werden, dann Bell und Guarded_Bell. Bereitet auch hierfür ein geeignetes Beispielprogramm vor, welches bspw. periodisch eine gewisse Zeit schläft und dann Ausgaben macht.

(C) Leerlaufthreads

Die vorigen Schritte lassen immer mehr Threads passiv warten, sodass das System einen anderen Thread einplanen muss. Der wartende Thread steht für das Scheduling nicht zur Verfügung. Wenn zu wenige Threads im System sind, um alle Prozessoren zu beschäftigen, müssen die übrigen Prozessoren trotzdem etwas machen.

Die Leerlaufthreads sollen den ausführenden Prozessor schlafen legen. Ein x86-Prozessor wacht bei einem Interrupt automatisch wieder auf (es sei denn, sie wurden mit cli ausmaskiert). Weiter ist die Kombination aus sti und hlt immer atomar, d.h. dass kein Interrupt zwischen den beiden Instruktionen ausgeführt werden kann. Wenn Threads wieder bereit werden, sollen schlafende Prozessoren durch einen IPI geweckt werden.

Zum Testen sollen mutwillig zu wenige (oder für die Einkernvariante) gar keine Threads mehr zur Verfügung gestellt werden, bzw. dafür gesorgt werden, dass dieser Fall eintritt.

(D) Tickless Kernel (optional)

Einen Prozessor bei jedem Timer-Interrupt zu wecken, nur damit er feststellen kann, dass es keine Arbeit mehr gibt, kostet Strom und ist unnütze Arbeit für unseren Prozessor. Daher kann auf den sich schlafen legenden Prozessoren der Timer-Interrupt ausgeschalten werden. Wichtig ist dabei, dass lediglich der Timer-Interrupt ausgeschaltet wird, aber andere Interrupts noch zugestellt werden. Außerdem darf auf der letzten noch aktiven CPU nur dann der Timer-Interrupt ausgeschaltet werden, wenn es keine Bell mehr im System gibt.

Klassendiagramm

dot_a6.png
Klassenübersicht für Aufgabe 6

Hilfen und Dokumentation