Friedrich-Alexander-Universität Erlangen-Nürnberg  /   Technische Fakultät  /   Department Informatik
Aufgabe 7: Eine Anwendung fĂŒr OO/MPStuBS (freiwillig)

Lernziel

  • Anwendung von ProgrammfĂ€den und Semaphoren

Aufgabenbeschreibung

Im Rahmen dieser Aufgabe sollt ihr eine OO/MPStuBS-Anwendung eurer Wahl implementieren. Dabei sollten möglichst mehrere FĂ€den zum Einsatz kommen, die ĂŒber Semaphore synchronisiert werden.

Vorgabe

Zufallszahlengenerator

Der Pseudozufallszahlengenerator in Random muss zu Beginn mit einem Anfangswert initialisiert werden, und liefert danach mit jedem Aufruf von Random::number() eine zufĂ€llige Zahl zurĂŒck

Dateisystem

Zur Vereinfachung der Datenverwaltung beinhaltet die Vorgabe ein Minix (3) Dateisystem.

Dieses kann entweder ĂŒber den ATA Treiber die (QEMU-) Festplatte oder einen temporĂ€ren DatentrĂ€ger im Arbeitsspeicher (z.B. die vom Bootloader ĂŒbermittelte initrd) eingebunden werden. Bei der zweiten Variante können Änderungen nicht persistent ĂŒber Rechnerneustarts gespeichert werden, dennoch sollte diese Variante fĂŒr den Testrechner verwendet werden.

Das Dateisystem benötigt eine dynamische Speicherverwaltung mit den malloc() und free() , welche – sofern nicht bereits geschehen – implementiert werden muss. Eine einfache Implementierung wie die Halde aus der Grundlagenvorlesung Systemprogrammierung ist natĂŒrlich ausreichend, allerdings sollte man darauf achten, dass der dafĂŒr statisch allokierte Speicherblock ausreichend groß ist (min. 16 MB).

Ein Abbild mit einem Megabyte speicher kann einfach mit den typischen Linux Boardmittel erstellt werden:

dd if=/dev/zero of=~/file.img bs=1MiB count=1
mkfs.minix -3 /dev/loop0  # optional --inodes <number>

Um Daten aufzuspielen (oder abzurufen) muss das Abbild eingebunden werden (z.B. mit mount ~/file.img /mnt/tmp/), was jedoch im CIP aufgrund Sicherheitsbedenken (und mangels funktionierendem FUSE-Dateisystem) nicht ohne weiteres möglich ist. Als Alternative bietet sich libguestfs an, welches intern mit einer virtuellen Maschine arbeitet und dadurch eine Bearbeitung ermöglicht.

Die gewöhnungsbedĂŒrfte Benutzung ist in eurem Makefile bereits wegabstrahiert: Alle Dateien im Ordner ./initrd/ werden automatisch in eine neue initiale Ramdisk (unter build/initrd.img) gepackt, zusammen mit etwa einen Megabyte freien Speicher – das Verzeichnis kann mittels INITRD_DIR und der freie Speicher in Bytes mit INITRD_FREE geĂ€ndert werden.

Ebenfalls kĂŒmmert sich das Makefile um die korrekte Übergabe an QEMU/KVM bzw. grub auf dem Testrechner.

Wenn ihr nun das Dateisystem in eurem Betriebssystem verwenden wollt, mĂŒsst ihr zuerst die Ramdisk (mit der Speicheraddresse aus den Multiboot-Informationen) als blockorientiertes GerĂ€te initialisieren, bevor ihr das eigentliche Dateisystem einhĂ€ngen könnt:

#include "fs/ramdisk.h"
#include "fs/vfs.h"
Ramdisk ramdisk;
// ...
void main() {
// ...
int error = VFS::mount("minix", &ramdisk, "");
if (error != 0) {
DBG << "Error mounting ramdisk (minix): " << -error << endl;
return 1;
}
// ...
}

Danach könnt ihr mit den POSIX-artigen Schnittstellen ( VFS::open / VFS::read usw ) auf die Dateien zugreifen.

Grafikmodus

Mit VESAGraphics habt ihr eine einfache Schnittstelle zu den VESA BIOS Extensions, einem grundlegenden Grafikmodus. Wenn ihr diesen verwenden wollt, mĂŒsst ihr dazu jedoch noch ein wenig Hand anlegen:

Analog zu CGAScreen benötigt ihr ein globales Objekt der Klasse Guarded_VESAGraphics (sinnigerweise in der main.cc) und ruft im Rahmen der Systeminitalisierung die Methode Guarded_VESAGraphics::init() auf. Mit Guarded_VESAGraphics::find_mode() könnt ihr nun nach den Kriterien Auflösung und Farbtiefe einen Grafikmodus aussuchen und falls ein valider Modus gefunden wird diesen mit Guarded_VESAGraphics::set_mode() setzen.

char buffer1[1280*1024*4];
char buffer2[1280*1024*4];
Guarded_VESAGraphics vesa(buffer1, buffer2);
// ...
void main() {
// ...
vesa.init();
VBEModeData_t* mode = vesa.find_mode(1024, 768, 24);
if (mode != 0) {
vesa.set_mode(mode);
}
// ...
}

Jetzt mĂŒsst ihr noch dafĂŒr sorgen, dass der Inhalt der Puffer auch in den Speicher der Grafikkarte geschrieben wird. Dazu dient die Methode VESAGraphics::scanout_frontbuffer . Diese ruft ihr entweder als Teil des Zeichenloops auf, immer dann wenn ein neuer Frame fertiggezeichnet wurde, oder fĂŒhrt sie im Watch-Epilog (dort allerdings die Variante ohne Guard) aus, was dafĂŒr sorgt, dass der Framebuffer der Grafikkarte mit einer festen Frequenz aktualisiert wird. Wenn ihr die mitgelieferte Beispielapplikation (Example ) ohne Modifikationen ausfĂŒhren wollt, dann mĂŒsst ihr zweitere Variante implementieren.

Damit die Systemlast durch den Scanout nicht zu hoch wird, empfielt es sich, den Scanout nicht bei jedem Timerinterrupt durchzufĂŒhren, ca. 20ms (entspricht maximal 50 Bilder pro Sekunde) sind ein tragbarer Kompromiss zwischen der Bildschirmaktualisierungsfrequenz und dem Overhead durch die vielen KopiervorgĂ€nge. Dabei könnt ihr die Timerfrequenz im Bereich von 1ms belassen, den Scanout dann aber nur noch entsprechend jedes zwanzigste Mal durchfĂŒhren (alternativ den Timer erhöhen). FPS zeigt eine einfache Möglichkeit die Bildwiederholungsrate zu messen.

Die bereitgestellten Grafikprimitiven ermöglichen Zeichnen von Linien und Rechtecken auf dem Bildschirm, die Ausgabe von Text (wie Title ) mit verschiedenen Schriftarten – und auch eine direkte Pixelmanipulation (Beispiel Fire).

Einbinden von Bitmaps

Wenn man analog zum Beispielprogramm Pong eigene Bitmaps einbinden möchte, so kann man diese mit GIMP einfach als C-Sourcecode (*.c) exportieren. Dabei sollte man keine Glib Typen verwenden. In der .c Datei findet man dann die entsprechenden BinÀrdaten in einem struct (identisch mit GIMP ), welches die Grafikbibliothek als darstellen kann.

Achtung
Durch die Verwendung von Bitmapgraphiken direkt im Sourcecode kann das erzeugte Systemimage jedoch sehr groß werden.

Einbinden von PNG

Das Problem mit der den großen BinĂ€rdateien lĂ€sst sich durch die Verwendung von verlustfrei komprimierten PNG-Datein lösen. Allerdings benötigt die auf uPNG basierende Bibliothek eine dynamische Speicherverwaltung, welche (falls nicht bereits zuvor fĂŒr die Festplatte geschehen) noch implementiert werden muss.

Die Datei kann entweder mittels xxd -i bild.png > bild.c als statisches Datenarray im C-Sourcecode gespeichert und eingebunden werden, oder man verwendet einfach das Dateisystem indem man das Bild nach ./initrd/bild.png legt. Danach kann man es in dem Betriebssystem mit /bild.png als Pfadangabe fĂŒr ein neues PNG Objekt.

Achtung
Da das Entpacken von komprimierten Bildern ressourcenintensiv ist, muss fĂŒr ausreichend Speicher – sowohl Stack als auch Heap – gesorgt werden!

Die von der Grafikbibliothek bereitgestellten Methoden erlauben auch eine einfache Verwendung von Spritesheets, wodurch einfach Animationen dargestellt werden können (siehe Cat )

Lösungen frĂŒherer Jahre

Die Lösungen fĂŒr Aufgabe 7 aus den Vorjahren findet ihr auf dem Testrechner im CIP-Pool im NetzwerkbootmenĂŒ unter 'Aufgabe 7 Beispiele'