Übungen Betriebssystembau: Einführung C++ Literatur

Werbung
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
Herunterladen