Betriebssystembau 1. Übung Michael Engel Arbeitsgruppe Eingebettete Systemsoftware Lehrstuhl für Informatik 12 TU Dortmund [email protected] http://ess.cs.uni-dortmund.de/~me/ 1 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 Nils) ● dient zum Bearbeiten der Rechneraufgaben ● ist zum Fragen stellen da ● ...und ist der Ort zum Abgeben der Aufgaben! Es gibt keine theoretischen Aufgaben! Betriebssystembau: 1. Übung © Olaf Spinczyk 2 Betriebssystembau: Übungen ● 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) Betriebssystembau: 1. Übung © Olaf Spinczyk 3 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... Betriebssystembau: 1. Übung © Olaf Spinczyk 4 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 5 C++ ● Wie so üblich: „Hello, World“ in C++ #include <iostream> int main() { cout << "Hello, world" << endl; return 0; } ● Java-Version: import whatever.u.like.*; class Test { public static void main(String[] argv){ System.out.println(„Hello, world“); } } Betriebssystembau: 1. Übung © Olaf Spinczyk 6 C++ - Übersetzungsprozess Betriebssystembau: 1. Übung © Olaf Spinczyk 7 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 <iostream> für System-Headerfiles - #include „device.h“ für eigene Headerfiles Betriebssystembau: 1. Übung © Olaf Spinczyk 8 Sourcecode - Präprozessor ● Weitere Präprozessorfunktionen: 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 9 Sourcecode - Präprozessor ● 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 10 Sourcecode - Compiler ● Erzeugt aus vom Präprozessor vorverarbeitetem Source Code eine Objektdatei (.o) ● ● ● 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... Betriebssystembau: 1. Übung © Olaf Spinczyk 11 Sourcecode - Linker ● ● ● 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? Betriebssystembau: 1. Übung © Olaf Spinczyk 12 Klassen in C++ ● Eine Klasse in C++ besteht aus ● Deklaration in Headerdatei (z.B. keyctrl.h) class Keyboard_Controller { ... }; ● und Implementierungsdatei (keyctrl.cc) #include “machine/keyctrl.h“ ... ● Name der .h/.cc-Files und Name der Klasse müssen nicht übereinstimmen! Betriebssystembau: 1. Übung © Olaf Spinczyk 13 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); ... }; Betriebssystembau: 1. Übung © Olaf Spinczyk 14 Aufbau der Headerdatei ● ● ● ● ● ● 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! Betriebssystembau: 1. Übung © Olaf Spinczyk 15 Aufbau der Implementierungsdatei ● ● 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 () { ... } Betriebssystembau: 1. Übung © Olaf Spinczyk 16 C++-Konzepte ● Kontrollstrukturen und Variablentypen in C++ ● Komplexe Datentypen (structs) ● Zeiger (Pointer) und Referenzen ● Vererbung und Mehrfachvererbung ● Virtuelle Funktionen ● Überladen von Operatoren Betriebssystembau: 1. Übung © Olaf Spinczyk 17 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 AssemblerFunktionen 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 :-) ● Betriebssystembau: 1. Übung © Olaf Spinczyk 18 Kontrollstrukturen und Variablentypen ● 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 19 Typwandlung (type casting) ● 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; ● // 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 20 Wertebereiche ● ● ● ● 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; Betriebssystembau: 1. Übung © Olaf Spinczyk 21 Komplexe Datentypen ● 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: struct rechteck r; r.xp = 100; r.yp = 200; r.width = 20; r.height = 40; Betriebssystembau: 1. Übung © Olaf Spinczyk 22 Pointer (Zeiger) ● ● ● ● ● 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; Betriebssystembau: 1. Übung © Olaf Spinczyk 23 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 !!! Betriebssystembau: 1. Übung © Olaf Spinczyk 24 Pointer (Zeiger) ● ● 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; Betriebssystembau: 1. Übung © Olaf Spinczyk 25 Pointer (Zeiger): Beispiel ● Code: char *CGA_START = (char *)0xb8000; char *pos; int x=20, y=20; pos = 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 26 Pointer (Zeiger): Beispiel char *CGA_START = (char *)0xb8000; char *pos; int x=20, y=20; pos = CGA_START + 2*(x + y*80); *pos = 'Q'; Betriebssystembau: 1. Übung © Olaf Spinczyk 27 Referenzen als Parameter ● Neben den normalen Referenzen der Art int *a; int b; a = &b; 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! Betriebssystembau: 1. Übung © Olaf Spinczyk 28 Einfache Vererbung ● ● Klasse keyboard_interrupt erbt von Klasse interrupt Vererbungsoperator „:“ (entspricht extends in Java) interrupt.h: keyboard_interrupt.h: class interrupt { ... } #include „interrupt.h“ class keyboard_interrupt : public interrupt { public: keyboard_interrupt(); ~keyboard_interrupt(); } Betriebssystembau: 1. Übung © Olaf Spinczyk 29 Mehrfachvererbung ● Klasse keyboard_interrupt erbt von Klassen interrupt und keys: keyboard_interrupt.h: #include „interrupt.h“ class keyboard_interrupt : public interrupt, public keys { public: keyboard_interrupt(); ~keyboard_interrupt(); } Betriebssystembau: 1. Übung © Olaf Spinczyk 30 Virtuelle Funktionen ● ● Virtuelle Funktionen sind Funktionen einer Basisklasse. Eine abgeleitete Klasse kann sie überschreiben und übernimmt damit die Ausführung der Funktion für ihre Klassenmitglieder. ● ● ● das funktioniert auch mit nicht virtuellen Klassen... 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) Betriebssystembau: 1. Übung © Olaf Spinczyk 31 Virtuelle Funktionen ● Ausgabe: „Derived“ ● ohne das virtual vor void display(): „Base“ #include <iostream> class base { public: virtual void display() { cout<<”\nBase”; } }; class derived : public base { public: void display() { cout<<”\nDerived”; } }; void main() { base *ptr = new derived(); ptr->display(); } Betriebssystembau: 1. Übung © Olaf Spinczyk Virtuelle Destruktoren ● ● 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 empfiehlt. Betriebssystembau: 1. Übung © Olaf Spinczyk 33 Ü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 ● ● In OOStuBS: Operator „<<“ 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 Betriebssystembau: 1. Übung © Olaf Spinczyk 34 Überladen von Operatoren ● ● 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: + - * & ~ ! ++ -- -> ->* ● binäre Operatoren: + - * / % ^ & | << >> += -= *= /= %= ^= &= |= <<= >>= < <= > >= == != && || , [] () new new[] delete delete[] Betriebssystembau: 1. Übung © Olaf Spinczyk 35 Überladen von Operatoren: Beispiel ● Addition von ganzen Zahlen zu einem Datum: class tDatum { public: // .... tDatum operator+(int Tage); }; tDatum tDatum::operator+(int Tage) { // Berechnung des Datums return *this; } 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: tdatum heute; heute = heute + 14; Betriebssystembau: 1. Übung © Olaf Spinczyk 36 Systemprogrammierung in C++ ● Keine Laufzeitumgebung vorhanden! ● ● man muss alles selber von Hand bauen... Damit sind auch keine Objekte dynamisch instanziierbar! kein „new“ und „delete“ möglich... ● ...woher soll auch die passende Speicherverwaltung dazu kommen? ● ● Für Spezialisten... das geht auch nicht: 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 ● Betriebssystembau: 1. Übung © Olaf Spinczyk 37