Betriebssystembau: Übungen ● ● ● ● Betriebssystembau: Übungen 2 SWS „Tafel“übung (hier :-)) und 2 SWS Rechnerübung Die Tafelübung vermittelt... ● Grundlagen der PC-Hardware-Architektur ● Grundwissen zur Bearbeitung der Rechnerübungen ● C++-Programmierkenntnisse Die Rechnerübung... (Horst und Jochen) ● dient zum Bearbeiten der Rechneraufgaben ● ist zum Fragen stellen da ● ...und ist der Ort zum Abgeben der Aufgaben! Es gibt keine theoretischen Aufgaben! Michael Engel <[email protected]> ● 1 Michael Engel <[email protected]> Betriebssystembau: Einführung C++ ● Grundlage für die Rechnerübungen ● Voraussetzungen: ● Programmierkenntnisse in einer objektorientierten Sprache (z.B. Java) ● Wir konzentrieren uns auf die Unterschiede zwischen Java und C++ ● ...und die kleinen Eigenheiten, auf die man achten muss, wenn man C++ für Systemprogrammierung einsetzt... Rechnerübung ● Aufgaben alle 2-3 Wochen, insgesamt 6 Aufgaben ● optional: 7. Aufgabe — eine OOStuBS-Anwendung ● Bearbeitung in Dreiergruppen ● Anwesenheit zu den Abgabeterminen erforderlich! ● Abgabe innerhalb der Gruppe soll reihum erfolgen ­ Jeder Gruppenteilnehmer sollte also die Lösung zu jeweils 2 der 6 Aufgaben demonstrieren und erläutern ­ Demonstration auf echter PC-Hardware, nicht im Emulator ● Bearbeitung der Aufgaben auch zu Hause möglich ­ auf Linux oder mittels vmware-Image (debian) 2 Literatur ● ● ● Es gibt jede Menge Bücher und Tutorials zu C++... Eine gute Einführung (€ 17,95) ist ● Marko Meyer „C++ programmieren im Klartext“ Pearson Verlag, ISBN 3-8273-7093-0 Kostenlos und gut – Folien und Skript von Prof. Ring, Uni Siegen: http://www.math.uni-siegen.de/ring/cpp.html ● ...und außerdem der Kurs „Von Java nach C++“ von Prof. Müller und Frank Weichert, die Basis für diese Folien http://ls12-www.cs.uni-dortmund.de/~marwedel/eda/Java2C.pdf Michael Engel <[email protected]> 3 Michael Engel <[email protected]> 4 C++ ● C++ - Übersetzungsprozess Wie so üblich: „Hello, World“ in C++ #include <iostream> int main() { cout << "Hello, world" << endl; return 0; } ● Java-Version: import java.awt.*; class Test { public static void main(String[] argv){ System.out.println(„Hello, world“); } } Michael Engel <[email protected]> 5 Michael Engel <[email protected]> Sourcecode - Präprozessor ● ● ● Sourcecode - Präprozessor Zwei Dateiendungen: ● .cc — C++ Source Code ● .h — „Header Files“ mit Definitionen von Datentypen, Konstanten, Präprozessor-Makros etc. Die Endungen sind Konvention, aber nicht zwingend ● oft z.B. auch .cpp, .hpp o.ä. Header-Files werden mit Hilfe des Präprozessors textuell in .cc-Files integriert ● #include – Anweisung: ­ #include <iostreams> für System-Headerfiles ­ #include „device.h“ für eigene Headerfiles Michael Engel <[email protected]> 6 ● Weitere Präprozessorfunktionen: ● ● ● 7 Makrodefinition, z.B. für Konstanten: ­ #define pi 3.1415926 ­ #define VGA_BASE 0xb8000 ­ ohne Semikolon am Ende! Bedingte Compilierung: ­ #ifdef DEBUG .... #endif ­ #ifndef VGA_BASE #define VGA_BASE 0xb8000 #endif Der Präprozessor expandiert Makros im Source Code, fügt Header-Files ein und erzeugt eine neue Textdatei, die der Compiler vorgesetzt bekommt Michael Engel <[email protected]> 8 Sourcecode - Präprozessor ● Sourcecode - Compiler Wichtige Anwendung für #define und #ifndef: ● Verhindern von mehrfacher Inklusion von HeaderDateien ­ Header-Dateien dürfen wiederum Header-Dateien inkludieren -> Ringschluss... ● ● ● #ifndef __cgastr_include__ #define __cgastr_include__ #include "object/o_stream.h" #include "machine/cgascr.h" ● class CGA_Stream /* Hier muesst ihr selbst Code vervollstaendigen */ { /* Hier muesst ihr selbst Code vervollstaendigen */ }; #endif Michael Engel <[email protected]> ● ● Michael Engel <[email protected]> 10 Klassen in C++ Der Linker faßt eine Menge an Objektdateien (.o) sowie bei Bedarf Libraries (.a, .so) zu einem ausführbaren Programm zusammen: ● Auflösung von Referenzen ● Sortierung der einzelnen Teile der Objektdateien im Speicherabbild der ausführbaren Datei „Normalerweise“ gibt es zwei Link-Modi: ● dynamisch – Libraries werden erst zur Zeit der Ausführung des Programms zum Objektcode geladen und Referenzen darin aufgelöst ● statisch – Libraries werden zur Link-Zeit in ein komplett ausführbares Programm integriert Vor-/Nachteile beider Ansätze? Welcher davon ist für unsere Systementwicklung geeignet? Michael Engel <[email protected]> Diese ist i.a. nicht direkt ausführbar, da noch Referenzen auf Funktionen oder Variablen in anderen Objektdateien enthalten sein können Der Compiler überprüft den Source Code auf Syntaxfehler und erzeugt ggf. ● Fehlermeldungen (errors) ● Warnungen (warnings) Eine Objektdatei wird nur bei fehlerfreier Compilierung erzeugt ● Warnungen führen nicht zum Abbruch des Übersetzungsvorgangs! Sie sollten aber beachtet werden... 9 Sourcecode - Linker ● Erzeugt aus vom Präprozessor vorverarbeitetem Source Code eine Objektdatei (.o) ● Eine Klasse in C++ besteht aus ● Deklaration in Headerdatei (z.B. keyctrl.h) class Keyboard_Controller { ... }; ● und Implementierungsdatei (keyctrl.cc) #include „machine/keyctrl.h“ ... ● 11 Name der .h/.cc-Files und Name der Klasse müssen nicht übereinstimmen! Michael Engel <[email protected]> 12 Aufbau der Headerdatei ● Aufbau der Headerdatei Ausschnitt aus keyctrl.h: class Keyboard_Controller { private: unsigned char code; unsigned char prefix; ... public: Keyboard_Controller (); ~Keyboard_Controller (); ● ● ● // Attribute ● // Konstruktor // Destruktor ● Key key_hit (); // Methoden void reboot (); void set_repeat_rate (int speed, int delay); ... ● Beginn der Klassendefinition mit Schlüsselwort „class“ Klassen sind immer public Attribute ● (Instanz-)Variablen dürfen bei der Deklaration nicht initialisiert werden Konstruktoren und Destrukturen ● Konstruktoren: Instanziierung von Objekten ● Destruktoren: Löschen instanziierter Objekte Deklaration von Methoden Klassendefinition wird mit Semikolon beendet! }; Michael Engel <[email protected]> 13 Michael Engel <[email protected]> Aufbau der Implementierungsdatei ● ● C++-Konzepte Einbinden der Header-Datei mit #include Durch den Klassennamen und den Bereichsoperator „::“ wird die Zugehörigkeit zur Klasse gekennzeichnet: #include „keyctrl.h“ Keyboard_Controller::Keyboard_Controller () { ... } Keyboard_Controller::~Keyboard_Controller () {} void Keyboard_Controller::reboot () { ... } Michael Engel <[email protected]> 14 15 ● Kontrollstrukturen und Variablentypen in C++ ● Komplexe Datentypen (structs) ● Zeiger (Pointer) und Referenzen ● Vererbung und Mehrfachvererbung ● Virtuelle Funktionen ● Überladen von Operatoren Michael Engel <[email protected]> 16 Kontrollstrukturen und Variablentypen ● ● bedingte Anweisungen, Schleifen, Verbundanweisungen (Blöcke) ● sind identisch in C++ und Java! In C++ sind „globale“ Funktionen möglich, in Java dagegen müssen Methoden immer innerhalb einer Klasse stehen ● insbesondere lassen sich in C++ auch „normale“ C- und Assembler-Funktionen aufrufen ● ...und man kann C++-Funktionen als von C und Assembler aufrufbar deklarieren mittels extern „C“ (wird für OOStuBS aber nicht benötigt) ● eine wichtige globale Funktion ist die „main“-Funktion :-) Kontrollstrukturen und Variablentypen ● ● ● ● Michael Engel <[email protected]> 17 Michael Engel <[email protected]> Typwandlung (type casting) ● ● ● ● // b==1.5 Eine weitere Möglichkeit, die nur in C++ verfügbar ist: ● Typ(Ausdruck) ● Beispiel: ● int a=3; double b=double(a)/2; // b==1.5 ● Michael Engel <[email protected]> 18 Wertebereiche In C++ können Typen – wie in Java – explizit gewandelt werden: ● (Typ) Ausdruck // in C++ und Java ● Beispiel: int a=3; double b=(double)a/2; Arrays (Felder) werden in C++ wie folgt definiert: ● int a[4]; // oder ● int a[] = {1,2,3}; // mit Initialisierung In C++ findet dabei keine Überprüfung der Array-Grenzen zur Laufzeit statt! ● Folge: die berüchtigten „Buffer Overflows“, bei denen z.B. über die Grenzen von Arrays hinaus Werte (andere Variableninhalte, Rücksprungadressen auf dem Stack etc.) überschrieben werden ­ potentiell großes Sicherheitsproblem! Variablen haben keine Default-Werte, müssen also immer explizit initialisiert werden. Erfolgt das nicht, generiert der Compiler eine warning (aber keinen error!) Die Speicherverwaltung muss durch den Programmierer erfolgen. Ein Garbage Collector wie in Java ist nicht vorhanden 19 In C++ existieren vorzeichenbehaftete und nicht vorzeichenbehaftete Typen (char, short, int, long), z.B.: ● int von -2^31 bis 2^31-1 ● unsigned int von 0 bis 2^32-1 Bei arithmetischen Operationen erfolgt keine Überprüfung auf Overflow bzw. Underflow! => Sicherheitsproblem! ● Beispiel: unsigned int i = 0; i = i - 1; // i == 4294967295 Die Wertebereiche, die einzelne Variablentypen einnehmen können, sind maschinenabhängig! ● z.B. kann ein int 32 oder 64 Bit „lang“ sein Mittels „typedef“ lassen sich neue Namen für Datentypen definieren: ● typedef int Index; Index a=3; Michael Engel <[email protected]> 20 Komplexe Datentypen ● Pointer (Zeiger) enums: Aufzählungstypen ● enum { caps_lock = 4, num_lock = 2, scroll_lock = 1 }; ● Oft Alternative zu #defines structs: Benutzerdefinierte Datentypen ● ● struct rechteck { int xp, yp; int width, height; int color; ... }; ● ● ● Verwendung: Ein Pointer ist eine Variable, deren Wert auf die Speicheradresse einer Variablen, einer Struktur oder eines Objekts zeigt Der Pointer ist ein eigener Datentyp Er ist typisiert (bezogen auf den Datentyp, auf den gezeigt wird) Durch Symbol „*“ gekennzeichnet Beispiel: ● kein Pointer: int a; ● Pointer auf eine Integer-Variable: int *a; struct rechteck r; r.xp = 100; r.yp = 200; r.width = 20; r.height = 40; Michael Engel <[email protected]> 21 Michael Engel <[email protected]> Pointer (Zeiger) ● ● ● ● Pointer (Zeiger) Eine Adresse ist die Speicherstelle, die einer Variablen oder einem Objekt zugeordnet ist Bei der Betriebssystemprogrammierung kann dies auch die Speicherstelle sein, an der ein bestimmtes Gerät Speicher oder Kontrollregister einblendet — z.B. der Bildschirmspeicher der Grafikkarte Der Inhalt ist dann der Wert, der an einer Speicherstelle gespeichert ist Die Größe des Inhalts (in Bytes) ist vom jeweiligen zugeordneten Datentyp abhängig ● z.B. 1 Byte für char, 2 Byte für short usw. ● Diese Größen sind in C/C++ architektur- und compilerabhängig, also nicht portabel !!! Michael Engel <[email protected]> 22 23 ● ● Ein Pointer ist also eine Variable, in der eine Adresse gespeichert wird. Diese Variable hat den identischen Typ zu der Variablen, deren Adresse in ihr gespeichert ist Es existieren zwei Operatoren zu Pointern: ● Dereferenzierungsoperator „*“ ­ Gibt den Wert zurück, der an der Adresse gespeichert ist, auf die die Pointervariable zeigt ● Referenzoperator „&“ ­ Liefert die zu einer Variablen gehörende Speicheradresse int a; int *adresse_von_a; ­ // Entweder... adresse_von_a = &a; *adresse_von_a = 42; ­ // oder einfach — mit selbem Ergebnis a = 42; Michael Engel <[email protected]> 24 Pointer (Zeiger): Beispiel ● Pointer (Zeiger): Beispiel Code: #define CGA_START 0xb8000 char *pos; int x=20, y=20; pos = (char *)CGA_START + 2*(x + y*80); *pos = 'Q'; ● char *pos; definiert einen Pointer pos, der auf eine Variable vom Typ char zeigt ● pos = (char *)CGA_START + 2*(x + y*80); initialisiert den Pointer mit dem Wert, der dem Zeichen an Position (x,y) im Bildschirmspeicher (Basisadresse CGA_START) entspricht ● *pos = 'Q'; schreibt das ASCII-Zeichen (char) 'Q' in die Speicherzeille, auf die der Pointer pos verweist Michael Engel <[email protected]> 25 Michael Engel <[email protected]> Referenzen als Parameter ● 26 Einfache Vererbung Neben den normalen Referenzen der Art ● ● int *a; int b; a = &b; Klasse keyboard_interrupt erbt von Klasse interrupt Vererbungsoperator „:“ (entspricht extends in Java) können Referenzen auch als Funktionsparameter auftauchen: int& max(int& a, int& b) { if (a>b) return a else return b; } ● Dies entspricht einem „call by reference“, d.h., es wird eine Referenz auf die entsprechende Variable übergeben und auch zurückgegeben. Der Aufruf erfolgt dann so: int a=5, b=7; max(a,b)++; // erhöht b um 1! Michael Engel <[email protected]> 27 interrupt.h: keyboard_interrupt.h: class interrupt { ... } #include „interrupt.h“ class keyboard_interrupt : public interrupt { public: keyboard_interrupt(); ~keyboard_interrupt(); } Michael Engel <[email protected]> 28 Mehrfachvererbung ● Virtuelle Funktionen Klasse keyboard_interrupt erbt von Klassen interrupt und keys: ● ● Virtuelle Funktionen sind Funktionen einer Basisklasse. Eine abgeleitete Klasse kann sie überschreiben und übernimmt damit die Ausführung der Funktion für ihre Klassenmitglieder. ● ● keyboard_interrupt.h: ● #include „interrupt.h“ class keyboard_interrupt : public interrupt, public keys { public: keyboard_interrupt(); ~keyboard_interrupt(); } Michael Engel <[email protected]> 29 Ausgabe: „Derived“ ● ohne das virtual vor void display(): „Base“ Das Besondere an virtuellen Funktionen ist, dass das Objekt selbst weiss, zu welcher abgeleiteten Klasse es gehört und seine zugehörige Klassenfunktion ruft. Nicht jede Funktion ist standardmäßig virtuell, es muss explizit das Schlüsselwort „virtual“ verwendet werden! (im Gegensatz zu Java) Michael Engel <[email protected]> Virtuelle Funktionen ● das funktioniert auch mit nicht virtuellen Klassen... 30 Virtuelle Destruktoren #include <iostream> class base { public: virtual void display() { cout<<”\nBase”; } }; class derived : public base { public: void display() { cout<<”\nDerived”; } }; ● ● Es gibt eine Faustregel, die besagt, dass jede Klasse mit virtuellen Funktionen auch einen virtuellen Destruktor haben soll. Da ein nicht virtueller Destruktor nicht gewährleistet, dass abgeleitete Klassen ordnungsgemäß abgebaut werden, kann ein nicht virtueller Destruktor sogar so interpretiert werden, dass der Autor ein Ableiten seiner Klasse nicht vorgesehen hat und wohl auch nicht empfielt. void main() { base *ptr = new derived(); ptr->display(); } Michael Engel <[email protected]> 31 Michael Engel <[email protected]> 32 Überladen von Operatoren ● ● ● Wurde in Java nicht realisiert Operatoren funktionieren abhängig vom Datentyp, auf dem sie operieren Beispiel: Operator „+“ ● ● ● ● ● ● + - * & ~ ! ++ -- -> ->* für int, float, double-Variablen wird die jeweilige Additionsfunktion für den entsprechenden Zahlentyp aufgerufen für String-Objekte kann der „+“-Operator so überladen (=umdefiniert) werden, dass er die beiden String-Operanden konkateniert ● binäre Operatoren: + - * / % ^ & | << >> += -= *= /= %= ^= &= |= <<= >>= < <= > >= == != && || , [] () new new[] delete delete[] für int-Werte bewirkt der „<<“-Operator, dass der in der Variablen enthaltene Zahlenwert um n Bits (2. Operand) nach links geschoben wird — z.B. ist 2 << 3 == 16 für Ausgabestreams ist der Operator ein Verkettungsoperator, siehe „Hello World“: cout << "Hello, world" << endl; Operator „>>“ entsprechend für Eingabestreams Michael Engel <[email protected]> 33 Michael Engel <[email protected]> Überladen von Operatoren: Beispiel ● Es können nur von der Sprache definierte Operatoren überladen, die Definition neuer Operatoren ist nicht möglich Unterstützt werden dabei ● unäre Operatoren: In OOStuBS: Operator „<<“ ● ● Überladen von Operatoren 34 Systemprogrammierung in C++ Addition von ganzen Zahlen zu einem Datum: ● Keine Laufzeitumgebung vorhanden! ● class tDatum { public: // .... tDatum operator+(int Tage); }; ● Damit sind auch keine Objekte dynamisch instanziierbar! ● ● ● tDatum tDatum::operator+(int Tage) { // Berechnung des Datums return *this; } ● ● ● kein „new“ und „delete“ möglich... ...woher soll auch die passende Speicherverwaltung dazu kommen? Für Spezialisten... das geht auch nicht: ● Die Addition liefert ein neues Datum zurück und als rechter Operand ist eine ganze Zahl zulässig. So kann das Datum durch einfache Addition um 14 Tage weitergeschoben werden: man muss alles selber von Hand bauen... Exceptions Assertions runtime type information Ein falscher Pointer kann das Ende sein... ● ● der Rechner hängt und das war's keine „segmentation violation“, keine core dumps tdatum heute; heute = heute + 14; Michael Engel <[email protected]> 35 Michael Engel <[email protected]> 36