Betriebssystemtechnik (OSE) - SS 2006
Aufgabe 1: OSE-I/O-Library
In dieser Aufgabe soll eine im Speicherplatzverbrauch skalierbare
Bibliothek zur formatierten Ausgabe von Zeichen, Zeichenketten, Ganzzahlen und
Zeigern erstellt werden.
| Ausgabetermin |
Vorgabe |
(spätester) Abgabetermin |
| 27.04. |
OSE_Vorgabe1.tgz |
10.05. 16:00 |
Lernziele
Ziel dieser Übung ist es, ein "Gefühl für Größe" zu bekommen. Wie gestaltet man eine Bibliothek so, dass sie in ihrer Codegröße mit der verwendeten Funktionalität skaliert? Was macht der Compiler aus meinem Code und wo kommen die vielen KBs eigentlich her?
Bearbeitung und Abgabe
Die Bearbeitung der Aufgabe soll wie angekündigt in einer
Dreiergruppe erfolgen. Spätestens jetzt solltet ihr euch also eine
Gruppe suchen. Die Abgabe der Aufgabe erfolgt bei Olaf im
Büro durch ein Mitglied der Gruppe. Wir werden uns dann
per ssh im CIP Pool anmelden, wo eurer
funktionstüchtiges Implement abgabereif liegen sollte.
Dann müsst ihr mir auch die Gruppenzusammensetzung mitteilen.
Bitte beachtet die unten genannten Anforderungen an den
Speicherplatzverbrauch!
Vorgabe
Die Vorgabe umfasst einen Verzeichnisbaum, in dem ihr eure Entwicklung
durchführen könnt. Die Makefiles sind für die Anwendung
auf den Debian/Linux PCs im CIP Pool gedacht. Wer zu Hause arbeiten
will, muss sie ggfs anpassen.
Des weiteren umfasst die Vorgabe drei Testprogramme
test1.cc bis test3.cc mit deren Hilfe ihr am
Ende die Skalierbarkeit der Bibliothek nachweisen sollt.
Die einzige vorgegebene C++ Klasse ist OStreamDock. Sie
hat eine Methode void out(char), die mit Hilfe des
Systemaufrufs write() implementiert wird. Alle
Aufgabefunktionen der zu erstellenden Bibliothek sollen darauf
aufsetzen.
Des weiteren vorgegeben ist der Startup-Code, der u.a. für die Ausführung der Konstruktoren und Destruktoren aller globalen Objekte sorgt. Außerdem misst er den Stackverbrauch und gibt diesen als exitcode des Programms zurück.
Die Datei config.h wird in den g++-Optionen per forced include in jede Übersetzungseinheit eingebunden. Sie ist damit der ideale Ort für Konfigurierungs-Makros und ähnliches.
Funktionsumfang
Bei der "I/O-Library" sind lediglich Funktionen zur Ausgabe
bereitzustellen. Eingabefunktionen sind nicht
nötig. Außerdem werden auch Fließkommazahlen nicht
unterstützt.
Wie man den drei Testprogrammen ansieht, sollen Anwendungsprogramme
die Bibliothek mit Hilfe des Datentyps OutputStream aus
"OutputStream.h" nutzen. Der Typ stellt die von
cout her bekannten Ausgabeoperatoren operator <<
für Zeichen, Zeichenketten, Ganzzahlen und Zeiger
zur Verfügung. Ob die Funktionen dieses Typs vielleicht
aus Basisklassen geerbt werden oder mit Hilfe von Hilfsklassen
implementiert werden, bleibt euch überlassen.
Neben den Ausgabeoperatoren, sollen folgende Methoden auf einem
OutputStream-Objekt anwendbar sein:
void width (int) |
Setzt die Feldbreite für die nächste Ausgabe. |
void fill (char) |
Setzt das Füllzeichen, das ausgegeben wird, wenn das
Ausgabefeld aufgefüllt werden muss (Default: Leerzeichen) |
void left () |
Ausgaben sollen ab sofort im Feld linksbündig erfolgen |
void right () |
Ausgaben sollen ab sofort im Feld rechtsbündig erfolgen |
void showbase (bool) |
Stellt ein, ob bei Integer-Zahlen die Basis ausgegeben wird ("0x"
bei Basis 16, "0" bei Basis 8, "%" bei Basis 2) |
Die Funktionen orientieren sich an der C++ Standardbibliothek. Neben
den Ausgabeoperatoren und den hier genannten Funktionen sind die
bekannten Manipulatoren hex, bin,
dec oct und endl zu implementieren.
Speicherplatzverbrauch
Die folgende Tabelle zeigt den Speicherplatzverbrauch der drei
Testprogramme in meiner Implementierung und
den von eurer Implementierung nicht zu überschreitenden Wert
(Angaben in Bytes). Die statische Größe wird dabei mit dem Befehl size ermittelt. Der Stackverbrauch wird als exitcode zurückgegeben und kann somit nach Ausführung von der Shell ausgegben werden (./test1 ; echo $?).
| Testprogramm |
text |
data |
bss |
stack |
Summe |
Eure Obergrenze |
| test1 |
549 |
0 |
12 |
92 |
653 |
710 |
| test2 |
776 |
4 |
20 |
108 |
937 |
1020 |
| test3 |
1519 |
4 |
20 |
148 |
1692 |
1850 |
Diese Werte gelten für g++-3.3, der im CIP-Pool der Standardcompiler ist.
Tipps
Um diese harten Anforderungen bzgl. des Speicherplatzverbrauchs zu
erfüllen, sind folgende Hinweise vielleicht hilfreich:
- Virtuelle Funktionen sind zu vermeiden
- Die Aufrufbeziehungen so ordnen, dass möglichst viel
unbenutzte Funktionalität durch das "function-level linking" rausfällt.
- Wo das "function-level linking" nicht reicht, kann man auch
anwendungsspezifisch konfigurieren.
- Mit konfigurierbaren Klassen (typedefs, die eine bestimmte
Variante einer Klassenimplementierung wählen)
- Mit #ifdefs innerhalb des Codes
Außerdem sollte man sich immer mal wieder ansehen, was Compiler und Linker eigentlich aus dem Programmcode machen. Dazu eignet sich der Befehl objdump. Mit objdump -D --demangle test1 erhält man z.B. ein relativ gut lesbares Diassembler-Listing seiner Applikation.