Friedrich-Alexander-Universität Erlangen-Nürnberg  /   Technische Fakultät  /   Department Informatik

Aufgabe 2: Paging in StuBSmI

Ziel dieser Aufgabe ist es, StuBSmI um grundlegende Paging-Funktionalität zu erweitern, Anwendungsprozesse voneinander zu isolieren und Kernel- und Anwendercode voneinander zu trennen.

Um die Initialisierung des Paging einfach zu halten, soll StuBSmI einen “lower-half” Kernel darstellen, d.h. die virtuellen Adressen von 0x0 bis 32 MiB gehören dem Kernel und entsprechen den darunterliegenden physikalischen Adressen. Anwendungen könnten also virtuell direkt dort anfangen wo der Kernelbereich aufhört.

Physikalischer Speicher

Um im weiteren Verlauf die Datenstrukturen für das Paging anlegen zu können, muss zunächst der verfügbare physikalische Speicher ermittelt werden.

Multiboot-konforme Bootloader (wie GNU GRUB, sowie auch QEMU oder PXE für Netzwerkboot) stellen dem Betriebssystem hierfür eine Liste aller vorhandenen, nicht durch Geräte oder Busse belegten Speicherbereiche in einem dokumentierten Format1 zur Verfügung. Hier ist darauf zu achten, dass bereits vom Kernel (und später auch der initrd) belegte Speicherbereiche nicht automatisch von dieser Liste ausgenommen werden, sondern explizit herausgefiltert werden müssen. Achtung: die angegebenen Bereiche können sich überlappen und widersprüchlich sein. Im Zweifelsfall hat nicht-frei (reserved) Präzedenz.

Die Multiboot-Referenz beschreibt einige Datenstrukturen sehr merkwürdig, daher benutzt man am besten gleich die angebotene Headerdatei und wirft einen Blick auf den angebotenen Beispiel-Code.

Paging-Datenstrukturen

In diesem Schritt bietet sich eine vollständige identische Abbildung des Arbeitsspeichers (virtuelle Adressen entsprechen den physikalischen Adressen) mit uneingeschränkter Zugriffsberechtigungen an.

Datenstrukturen, die folgende Informationen speichern, könnten sich als nützlich erweisen:

  • Freie physikalische Seiten oberhalb der Kernelgrenze

  • Freie physikalische Seiten unterhalb der Kernelgrenze

  • Prozessekontrollblöcke und Kernelstacks (dynamisch oder statisch mit fester Obergrenze ist egal)

Damit können für alle Anwendungen die page directories und page tables aufgebaut werden. Soll nun im Dispatcher auf einen anderen Prozess gewechselt werden (Dispatcher::go, Dispatcher::dispatch) muss auch das Mapping des nächsten Prozesses aktiv werden.

Es ist sinnvoll, die erste Page (ab Adresse 0x0) dauerhaft nicht zu mappen und den restlichen Bereich bis 1 MiB nicht zu benutzen (dort sind viele Geräte eingeblendet).

Genaue Informationen zum Paging finden sich im Intel-Handbuch in Kapitel 4.

Trennung von Kernel- und Usercode

Lokalität

Um einfacher zwischen Kern und Anwendungen isolieren zu können, soll StuBSmI nun getrennt von den Anwendungen kompiliert werden. Das Buildsystem muss entsprechend angepasst werden. Zusätzlich soll eine Bibliothek “libsys” erstellt werden, in der sich die Syscall-Stubs für die Anwendungen befinden. Mit Hilfe dieser Bibliothek soll nun jede Anwendung für sich kompiliert werden, ohne direkt gegen den Kernel zu linken oder Teile davon zu #include-en. Damit weiterhin die Konstruktoren von globalen Objekten im Anwendungscode ausgeführt werden, benötigt jede Anwendung die Datei init.cc. Zusätzlich ist ein Linkerskript erforderlich, das den endgültigen Aufbau der ausführbaren Datei beschreibt. Dieses Skript sollte unter anderem eine sinnvolle Startadresse definieren, ansonsten ähnelt es aber im Wesentlichen dem Linkerskript des Kernels.

Mithilfe des Programms objcopy lassen sich dann aus den einzelnen, im ELF-Format vorliegenden Anwendungs-Binaries sogenannte “flat” Binaries generieren, also komplette Speicherabbilder der Programme, die direkt (ohne ELF-Loader zur Laufzeit) zur Ausführung gebracht werden können. Das BSS-Segment sollte dabei nicht vergessen werden (-–set-section-flags .bss=alloc,load,contents).

Multibootfähige Bootloader unterstützen das Laden sogenannter initial RAM disk (initrd) zusätzlich zum Kernelimage. Die Anwendungen sollen alle in eine initrd gepackt werden, die mit einem Header versehen wird, in welchem die Informationen zu Anzahl und Größe der Anwendungsbinaries vorliegen. Das mitgelieferte Werkzeug imgbuilder.cc übernimmt diese Aufgabe; das Auslesen zur Laufzeit muss allerdings selbst implementiert werden. Die Speicherposition und Größe der geladenen initrd wird vom Bootloader zur Verfügung gestellt.

Es empfiehlt sich, zuerst versuchsweise nur eine Anwendung zu laden, die an die entsprechende Adresse, die im Linkerskript definiert wurde, kopiert wurde. Anschließend können statt dem kopieren direkt die Anwendungen eingeblendet werden – sie sind bereits auf Seitenbeginn ausgerichtet.

Befugnisse

Damit Prozesse nun weder auf die Speicherbereiche von anderen Prozessen noch auf den immer eingeblendeten Kernelbereich zugreifen können, müssen die Zugriffsrechte der eingeblendeten Seiten angepasst werden. Der Kernel soll dabei problemlos auf den Speicher des aktuell unterbrochenen Prozesses zugreifen können.

Bitte beachten:

  • Prozesse benötigen einen les- und schreibbaren Stack

  • IOAPIC und LAPIC greifen auf Adressen zu die u.U. außerhalb des Kernelbereichs liegen.

Es ist in dieser Aufgabe nicht nötig, eine Behandlung für Pagefaults zu implementieren – für das Entkäfern kann es jedoch sehr nützlich sein, die fehlgeschlagene Adresse (in cr2) angezeigt zu bekommen.

Erweiterte Speicherverwaltung (für 7,5 ECTS)

Als erweiterte Übung sollen folgende Systemaufrufe implementiert werden:

  • void* map(size_t size, void* addr)

  • void exit()

Der map-Systemaufruf soll Seiten der Größe size in den aktuellen Adressraum an der Adresse addr einblenden. Sofern die Adresse NULL ist muss das Betriebssystem eine geeignete Lücke im Benutzeradressraum finden und dort die freie Seiten einfügen. Der Speicher sollte initial ausgenullt sein. Falls die Allokation fehlschlägt (weil z.B. die Adressen bereits verwendet wurden), soll map einen sinnvollen Fehlercode zurückgeben.

Der exit-Systemaufruf beendet den aktuellen Prozess und gibt alle damit verbundenen Resourcen frei. Dieser Systemaufruf darf auf keinen Fall zur Anwendung zurückkehren. Zur Validierung der Speicherfreigabe soll der Zustand der Freispeicherverwaltung ausgegeben werden. Nach dem Beenden aller Anwendungen soll genau so viel freier Speicher zur Verfügung stehen wie vor dem Start der ersten Anwendung.

Optional TAR mit ELF Dateien

Mit map ist es nun relativ einfach möglich, statt den “flat” Binaries auch direkt (statische) ELF Dateien zu unterstützen (das Linkerskript wird dennoch weiterhin benötigt). Die ELF Dateien können z.B. als TAR gepackt über die initrd geladen werden.


  1. http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format