Commit 9a54c2d2 authored by Bernhard Heinloth's avatar Bernhard Heinloth
Browse files

Vorgabe oostubs Aufgabe 3

parent 756b5f87
// vim: set et ts=4 sw=4:
#include "guard/guard.h"
// vim: set et ts=4 sw=4:
/*! \file
* \brief Diese Datei enthält die Klasse Guard.
*/
#pragma once
#include "guard/gate.h"
#include "object/queue.h"
/*! \brief Synchronisation des BS-Kerns mit Unterbrechungen.
* \ingroup interrupts
*
*
* Die Klasse Guard dient der Synchronisation zwischen "normalen"
* Kernaktivitäten (zur Zeit Ausgaben, später Systemaufrufe) und
* Unterbrechungsbehandlungsroutinen. Dazu besitzt Guard eine Warteschlange
* (Queue-Objekt) je CPU, in die Gate-Objekte eingereiht werden können. Das ist
* immer dann erforderlich, wenn zum Zeitpunkt des Auftretens einer
* Unterbrechung der kritische Abschnitt gerade besetzt ist, die epilogue()
* Methode also nicht sofort bearbeitet werden darf. Die angesammelten
* Epiloge werden behandelt, sobald der kritische Abschnitt wieder
* freigegeben wird.
*
* \b Hinweise:
* <ul>
* <li>Die Epilogqueue stellt eine zentrale Datenstruktur dar, deren
* Konsistenz geeignet gesichert werden muß. Die von uns durch die Klasse
* Queue bereitgestellte Implementierung ist nicht
* unterbrechungstransparent! Entweder ihr implementiert also selbst eine
* unterbrechungstransparente Queue, oder ihr synchronisiert die
* bereitgestellte Queue entsprechend hart.</li>
*
* <li>In MPStuBS benötigt man für jeden Prozessor eine eigene
* Epilogqueue, über die jeder Prozessor "seine" Epiloge serialisiert.
* Allerdings könnten dann Epiloge auf unterschiedlichen Kernen parallel
* laufen, da der kritische Abschnitt prozessorweise getrennt
* verwaltet wird. Dies muss verhindert werden, indem man beim Betreten
* des kritischen Abschnittes diesen mit einem Spinlock vor paralleler
* Abarbeitung auf anderen Prozessoren schützt. Dieses Spinlock ist nicht
* zu verwechseln mit der Sperrvariable, die das Betreten der Epilogebene
* markiert!</li>
*
* <li>Da Gate Objekte nur einen einzigen Verkettungszeiger besitzen,
* dürfen sie zu einem Zeitpunkt nur ein einziges Mal in der Epilogliste
* aufgeführt sein. Wenn also zwei gleichartige Interrupts so schnell
* aufeinanderfolgen, dass der zugehörige Epilog noch gar nicht behandelt
* wurde, darf nicht versucht werden, dasselbe Gate Objekt zweimal in die
* Epilogliste einzutragen. Die Klasse Gate bietet Methoden, dies zu
* vermerken bzw. zu prüfen.</li>
*
* <li>Ein Betriebssystem sollte Unterbrechungen immer nur so kurz wie
* möglich sperren. Daher sieht das Pro-/Epilog-Modell vor, dass Epiloge
* durch Prologe unterbrochen werden können. Für OOStuBS bedeutet das,
* dass bereits vor der Ausführung des Epilogs einer
* Unterbrechungsbehandlung Interrupts wieder zugelassen werden
* sollen.</li>
* </ul>
*/
class Guard {
// Verhindere Kopien und Zuweisungen
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
public:
/*! \brief Konstruktor
*
*/
Guard () {}
public:
/*! \brief Betreten des kritischen Abschnitts.
*
* Das Betreten des kritischen Abschnittes ist je nach Art des Systems
* unterschiedlich zu handhaben. Bei einem Uniprozessorsystem genügt es,
* das Betreten durch einfaches Setzen einer Sperrvariable zu markieren,
* da nur ein einziger Kontrollfluss gleichzeitig den kritischen Bereich
* betreten kann. Sobald jedoch mehrere CPUs vorhanden sind, ist dies
* nicht mehr der Fall. Will nun eine CPU den kritischen Bereich betreten,
* obwohl sich dort schon eine andere CPU befindet, so soll hier aktiv
* gewartet werden, bis der kritische Bereich wieder freigegeben wurde.
*
* \todo Methode implementieren
*/
void enter();
/*! \brief Verlassen des kritischen Abschnitts.
*
* Mit dieser Methode wird der kritische Abschnitt verlassen und die
* angestauten Epiloge werden abgearbeitet.
*
* \todo Methode implementieren
*/
void leave();
/*! \brief Ein Prolog möchte, dass seine Epilog-Aktivität ausgeführt wird.
*
* Diese Methode wird von guardian () aufgerufen, falls der zuvor
* ausgeführte Prolog durch einen Rückgabewert true angezeigt hat, dass
* sein Epilog ausgeführt werden soll. Ob der Epilog sofort behandelt oder
* zunächst nur in die Epilogliste eingehängt wird, hängt davon ab, ob der
* kritische Abschnitt frei oder besetzt ist.
*
* \todo Methode implementieren
*/
void relay(Gate *item);
};
// vim: set et ts=4 sw=4:
/*! \file
* \brief Diese Datei enthält die Klasse Secure.
*/
#pragma once
/*! \brief Die Klasse Secure dient dem bequemen Schutz kritischer Abschnitte.
* \ingroup interrupts
*
*
* Dabei wird die Tatsache ausgenutzt, dass der C++ Compiler für jedes Objekt
* automatisch Konstruktor- und Destruktoraufrufe in den Code einbaut und dass
* ein Objekt seine Gültigkeit verliert, sobald der Bereich (Scope), in dem es
* deklariert wurde, verlassen wird.
*
* Wenn im Konstruktor von Secure also ein kritischer Abschnitt betreten und
* im Destruktor wieder verlassen wird, kann die Markierung kritischer
* Codebereiche ganz einfach folgendermaßen erfolgen:
*
* \verbatim
// unkritisch
...
{ Secure section;
// hier kommen die kritischen Anweisungen
...
}
// Ende des kritischen Abschnitts
\endverbatim
*/
class Secure
{
// Verhindere Kopien und Zuweisungen
Secure(const Secure&) = delete;
Secure& operator=(const Secure&) = delete;
public:
//TODO: Konstruktor und Destruktor müssen noch von euch implementiert
// werden.
};
// vim: set et ts=4 sw=4:
/*! \file
* \brief Enthält die Klasse BBuffer
*/
#pragma once
#include "types.h"
/*! \brief Die Klasse BBuffer implementiert einen "Bounded Buffer",
* also einen Puffer mit beschränkter Größe.
*
* \tparam T gibt an welcher Typ gespeichert werden soll
* \tparam CAP gibt die Kapazität des Puffers an.
*/
template <typename T, unsigned CAP>
class BBuffer
{
// Verhindere Kopien und Zuweisungen
BBuffer(const BBuffer&) = delete;
BBuffer& operator=(const BBuffer&) = delete;
private:
T data[CAP];
volatile unsigned in;
volatile unsigned out;
public:
/*! \brief Der Konstruktor initialisiert den Puffer als leer.
*
*/
BBuffer() : in(0), out(0) {}
/*! \brief In den Puffer einfügen.
* \param val Das Element das eingefügt werden soll.
* \return \b false wenn der Puffer voll ist und keine weiteres Element
* mehr eingefügt werden kann, \b true sonst.
*/
bool produce(T val) {
unsigned nextin = (in + 1) % CAP;
if(nextin != out) {
data[in] = val;
in = nextin;
return true;
}
return false;
}
/*! \brief Aus dem Puffer herausnehmen.
* \param val Falls es noch Elemente im Puffer gibt wird das Nächste in
* \b val gespeichert, ansonsten wird \b val nicht geschrieben.
* \return \b false wenn der Puffer leer ist, \b true sonst.
*/
bool consume(T &val) {
if(in != out) {
val = data[out];
out = (out + 1) % CAP;
return true;
}
return false;
}
};
// vim: set et ts=4 sw=4:
/*! \file
* \brief Enthält die Klasse Queue
*/
#pragma once
#include "object/queuelink.h"
/*! \brief Die Klasse Queue realisiert eine einfach verkettete Liste von
* Objekten mit next Zeiger.
*
* Objekte, die verkettet werden sollen, enthalten ein QueueLink<T>-Member,
* welches diese Liste identifiziert.
* Beispiel:
\verbatim
class Foo {
QueueLink<Foo> next_foo;
}
\endverbatim
*
* Das next_foo Feld enthaelt dabei den Verkettungszeiger. Es lassen
* sich nun Listen mit dem next_foo Verkettungszeiger generieren und
* verwenden. Insbesondere können diese Queues in C++11 Range
* Ausdrücken verwendet werden.
*
\verbatim
Queue<Foo, &Foo::next_foo> list;
Foo a, b, c;
list.enqueue(&a);
list.enqueue(&b);
list.enqueue(&c);
for(Foo * elem : list) {
// use elem
}
\endverbatim
* Wird eine Queue ohne Angabe eines expliziten Verkettungszeigers
* instanziiert, so muss die zu verkettende Klasse ein QueueLink<T>-Member mit
* dem Namen "queue_link" enthalten:
\verbatim
class Bar {
QueueLink<Bar> queue_link;
}
Queue<Bar> list;
\endverbatim
*
* Zur Implementierung: tail verweist nicht, wie oft üblich, auf das letzte
* Element der Liste, sondern
* auf den next Zeiger des letzten Elements, bzw., solange die Liste noch leer
* ist, auf den head Zeiger der Liste. Dadurch muss beim Einfügen eines
* Elements an die Liste nicht überprüft werden, ob bereits Elemente in ihr
* enthalten sind. Beim Entfernen von Elementen kann auf die Fallunterscheidung
* allerdings nicht verzichtet werden.
*/
template<typename T, QueueLink<T> T::* next_field = nullptr>
class Queue
{
/* Wir erlauben es nicht Warteschlangen zuzuweisen, da ansonsten
* der "tail Zeiger auf das "head" Feld einer anderen
* Warteschlange zeigt.*/
Queue(const Queue<T, next_field>&) = delete;
Queue& operator=(const Queue<T, next_field>&) = delete;
private:
T *head;
T **tail;
public:
/*! \brief Der Konstruktor initialisiert die Liste als leere Liste
*/
Queue() : head(0), tail(&head) { }
/*! \brief Gibt einen Zeiger auf das QueueLink-Element
* innerhalb des Argument-Objektes zurück.
*
* Wir verwenden hier die "ungewöhnliche" Pointer-to-Member
* Funktionalität von C++. Ein PtM kann mit dem "->*" Operator auf
* ein Objekt der passenden Klasse angewendet werden, um Zugriff
* auf ein bestimmtes Feld zu bekommen.
*/
static QueueLink<T> *get_node(T *o) {
return queue_link_get<T, next_field>::call(o);
}
/*! \brief Das Listenelement item wird an das Ende der Liste angefügt.
* \param item Listenelement, welches angefügt werden soll.
*/
void enqueue(T *item) {
QueueLink<T> *node = get_node(item); // Verkettungszeiger für das Objekt holen.
node->next = 0; // Das neue Element besitzt noch keinen Nachfolger.
*tail = item; // Das Element an das Ende der Liste anfuegen
tail = &(node->next); // und den tail Zeiger aktualisieren.
}
/*! \brief Liefert das erste Element der Liste und entfernt es gleichzeitig
* aus dieser.
* \return entferntes Kopfelement oder 0 wenn die Liste leer ist.
*/
T* dequeue() {
T *out;
if((out = head)) { // Es gibt ein Element
// Es ist das letzte Element der Liste
if (!(head = get_node(out)->next))
tail = &head;
// Markiere das Element als Entfernt
get_node(out)->next = nullptr;
}
return out;
}
/*! \brief Ein Queue Iterator
* Wir implementieren eine Iterator-Wrapper-Klasse, die den
* aktuellen Zustand einfängt, wenn wir über eine Queue iterieren.
* Dies geschieht automatisch, wenn C++11-Range-Loops verwendet
* werden. Queue-Iteratoren sind als Vorwärts-Iteratoren
* implementiert. Mehr Details zu Vorwärts-Iteratoren gibt es <a
* href="http://en.cppreference.com/w/cpp/concept/ForwardIterator">hier</a>.
*/
class Iterator {
T * first;
public:
Iterator() : first(nullptr) {};
Iterator(Queue<T, next_field> * queue) : first(queue->head) {};
bool operator!=(const Iterator & other) {
return first != other.first;
}
T * operator*() { return first; }
Iterator & operator++() {
first = get_node(first)->next;
return *this;
}
};
/*! Liefert einen Iterator auf den Beginn der Queue
*/
Iterator begin() {
return Iterator(this);
}
/*! Liefert einen End Iterator
*/
Iterator end() {
return Iterator();
}
/*! \brief Mit dieser Methode kann das angegebene Element item aus der
* Liste entfernt werden, unabhängig von seiner Position dort.
* Verglichen werden die Pointer-Adressen, optional kann eine
* Vergleichsfunktion angegeben werden. Die
* Standardvergleichsfunktion ist ein C++11 Lambda Ausdruck, der
* auf Pointer-Gleichheit prüft.
*
* \param item Element, welches entfernt werden soll.
* \param cmp Vergleichsfunktion.
* \return Gibt das entfernte Element zurück, \b 0 wenn keins entfernt
* wurde.
*/
T* remove(T *item, bool (*cmp)(T*,T*) = [] (T* a, T* b) {return a == b;}) {
T **prev = &head;
for (T * current : *this) {
if(cmp(item, current)) {
*prev = get_node(current)->next;
if(&get_node(current)->next == tail) tail = prev;
return current;
}
prev = &get_node(current)->next;
}
return 0;
}
/*! \brief Fügt das \b item am Anfang der Liste ein.
* \param item Einzufügendes Element.
*/
void insert_first(T *item) {
get_node(item)->next = head;
head = item;
if(tail == &head) tail = &get_node(item)->next;
}
/*! \brief Fügt das Element new_item hinter dem Element old_item in die
* Liste ein.
* \param old_item Element, nach dem eingefügt werden soll.
* \param new_item Einzufügendes Element.
*/
void insert_after(T *old_item, T *new_item) {
get_node(new_item)->next = get_node(old_item)->next;
get_node(old_item)->next = new_item;
if((*tail) == new_item)
tail = &(get_node(new_item)->next);
}
/*! \brief Liefert das erste Element der Liste, ohne es zu entfernen.
* \return Erstes Element der Liste.
*/
T* first() {
T *out;
out = head;
return out;
}
/*! \brief Liefert das nächste Element der Liste für ein gegebenes Element.
*/
T* next(T* o) {
return get_node(o)->next;
}
};
// vim: set et ts=4 sw=4:
/*! \file
* \brief Enthält die Klasse QueueLink
*/
#pragma once
/* Forward declaration: */
template<typename T> class QueueLink;
template<typename T, QueueLink<T> T::*> class Queue;
/*! \brief Verkettungszeiger zum Einfügen eines Objektes in eine einfach
* verkettete Liste.
*
* Die Klasse QueueLink stellt einen Verkettungszeiger auf ein Element mit dem
* Typ T zur Verfügung und ist damit Basis aller Klassen, deren Instanzen in
* Listen (Queue Objekten) verwaltet werden sollen.
*/
template <typename T>
class QueueLink
{
/* Erlaube Klasse Queue Zugriff auf den privaten Member next. */
template<typename X, QueueLink<X> X::* x> friend class Queue;
private:
/*! \brief next Gibt das nächste Element der Liste an.
*/
T *next;
public:
QueueLink<T>() : next(nullptr) {}
};
/* Here we do some template magic to get either the pointer-to-member
* field (if != nullptr) or a default QueueLink field with the name
* queue_link. As a BS student you're not supposed to understand this
* without hard thinking. */
// enable_if is copied from the standard C++ libr
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
template<typename X, QueueLink<X> X::* next_field, class Enable = void>
struct queue_link_get {
static QueueLink<X> * call(X* o) {
return &(o->queue_link);
}
};
template<typename X, QueueLink<X> X::* next_field>
struct queue_link_get<X, next_field, typename enable_if<next_field != nullptr>::type> {
static QueueLink<X> * call(X* o) {
return &(o->*next_field);
}
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment