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

Die umfangreiche Vorgabe enthÀlt einen Zufallszahlengenerator, ein Dateisystem sowie einen Grafikmodus.

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, besser 32 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 ĂŒblicher Ausweg bietet sich libguestfs an, welches intern mit einer virtuellen Maschine arbeitet und dadurch eine Bearbeitung ermöglicht.

Die Vorgabe enthĂ€lt allerdings in fs/tool eine Anwendung, welches die selbe Implementierung des Dateisystems wie das Betriebssystem nutzt um direkt auf ein Abbild zuzugreifen – komplett ohne besondere Privilegien können mit FTP-artigen Befehlen die Dateien und Ordner kopiert und modifziert werden.

Zur Vereinfachung sind Übersetzung und Aufruf des FSTool bereits in eurem Makefile 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 der Ramdisk an QEMU/KVM bzw. dem Bootloader auf dem Testrechner.

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

#include "fs/vfs.h"
#include "fs/ramdisk.h"
#include "boot/multiboot/data.h"
// ...
void main() {
// ...
// Ramdisk einhÀngen (= das erste Modul)
Multiboot::Module * initrd = Multiboot::getModule(0);
if (initrd == NULL) {
kout << "No initial ramdisk - abort!" << endl;
}
Ramdisk ramdisk(initrd->getStartAddress(), initrd->getSize());
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 Graphics 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 CGA_Window benötigt ihr ein globales Objekt der Klasse Guarded_Graphics (sinnigerweise in der main.cc) und ruft im Rahmen der Systeminitalisierung die Methode Guarded_Graphics::init() auf. Ein passender Grafikmodus wird bereits vom Multiboot-kompatiblen Bootloader gewÀhlt, und zwar in der Datei boot/multiboot/config.inc durch setzen von MULTIBOOT_VIDEO_MODE in den MULTIBOOT_HEADER_FLAGS sowie dem Anpassen von MULTIBOOT_VIDEO_WIDTH, MULTIBOOT_VIDEO_HEIGHT und MULTIBOOT_VIDEO_BITDEPTH.

Achtung
Es ist möglich, dass der gewĂŒnschte Modus so nicht verfĂŒgbar ist, dann wird ein Ă€hnlicher Modus gewĂ€hlt. Ihr könnt euch also nicht komplett auf die Auflösung verlassen
Zu beachten
QEMU/KVM bieten mit dem in der Makefile verwendeten -kernel-Parameter keinen echten Multiboot-kompatiblen Bootloader an (Fehlermeldung multiboot knows VBE. we don't auf der Kommandozeilte). Deshalb muss ein Image gebaut werden, welches einen solchen Bootloader – z.B. Grub – verwendet. Das könnt ihr mit den -iso-Targets machen, wie make kvm-iso. Unser Netboot ist jedoch kompatibel und bedarf keiner Besonderen Behandlung.
// Maximal darstellbarer Bildschirm
const unsigned fb_width = 1280;
const unsigned fb_height = 1024;
const unsigned fb_bpp = 4;
const unsigned fb_size = fb_width * fb_height * fb_bpp;
char buffer_1[fb_size] __attribute__((aligned));
char buffer_2[fb_size] __attribute__((aligned));
Guarded_Graphics graphics(fb_size, buffer_1, buffer_2);
// ...
void main() {
// ...
// Grafik initialisieren
if (!graphics.init(true)) {
kout << "Not able to initialize Framebuffer - abort!" << endl;
}
// ...
}

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 Graphics::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 (GraphicsExample ) 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). PC zeigt eine Framebufferkonsole mit einer einfachen Variante die Bildwiederholungsrate zu messen an.

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.

Achtung
Die Bibliothek unterstĂŒtzt nur RGB und Graustufenbilder (mit und ohne Alphakanal) – jedoch keine Bilder mit angepasster Farbpalette.

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 besten Lösungen fĂŒr Aufgabe 7 aus den Vorjahren findet ihr auf dem Testrechner im CIP-Pool im NetzwerkbootmenĂŒ unter Ruhmeshalle.

Zu beachten
Leider sind aufgrund des Umstiegs auf die neuen Testhardware (Intel Core i-Serie, welche etwas empfindlicher auf den Standard reagiert als die vorherige Intel Core 2-Serie) einige der alten Beispiele auf den Testrechnern nicht mehr lauffÀhig. Diese können jedoch (zum Teil) noch mit QEMU/KVM gestartet werden: kvm -kernel /proj/i4bs/halloffame/13_schach.elf