StuBS
|
Ziel dieser Aufgabe ist es, den isolierten Prozessen effiziente Primitiven zum Nachrichtenaustausch (message passing) anzubieten, sowie einen Systemaufruf zur Prozesserzeugung, ähnlich dem Unix-fork()
, bereitzustellen.
Folgende Systemaufrufe sollen implementiert werden:
sbuffer
mit der Länge ssize
synchron und blockierend an den Prozess mit der Nummer id
. Die Operation blockiert solange bis der Empfänger die Nachricht mit recv()
entgegennimmt und mit Hilfe von reply()
eine Antwort schickt. Diese Antwort steht dann in rbuffer
mit der Maximallänge rsize
zur Verfügung.buffer
mit der Maximallänge size
abgelegt wird. Der Rückgabewert gibt die Identifikationsnummer des Senders an.id
identifizierten Prozess. Diese Funktion soll nicht blockieren und nur erfolgreich sein falls der Zielprozess tatsächlich ein send()
zu diesem Prozess ausführt bzw. auf dessen Fertigstellung wartet.fork()
-Aufruf zurückkehren. Das einzige Unterscheidungsmerkmal ist die Prozess-Identifikationsnummer. Der Rückgabewert der Funktion ist die Identifikationsnummer des jeweils anderen Prozesses.Die Puffer für send()
, recv()
und reply()
, sollten am besten an Seitengrenzen liegen. Eine Möglichkeit, dies statisch zu erreichen, bietet der GCC mit __attribute__ ((aligned (x)))
und einer Konstanten für x
an. Anfangs sollten diese Systemaufrufe durch explizites Kopieren der Inhalte der betroffenen Seiten implementiert werden.
In einem zweiten Schritt sollen die Seiten nur in den jeweils anderen Adressraum eingeblendet werden und erst im Fall eines Schreibzugriffs physikalisch kopiert werden.
Um Schreibzugriffe zu erkennen und anschließend Seiten kopieren zu können, müssen folgende Mechanismen implementiert werden:
Bei send
/recv
bzw. reply
/send
sollen die Inhalte der Puffer nur dann mit Hilfe von COW kopiert werden, wenn die Nachrichtengröße mindestens der Seitengröße entspricht (Verschnitt regulär kopieren).
Genaue Informationen zum Paging und Pagefaults finden sich im Intel-Handbuch in Kapitel 4. fork
soll natürlich nicht den Kernelbereich mitkopieren. Bei COW kann es vorkommen, dass die betroffenen Seiten bereits durch vorherige Übertragungen/fork()
s COW-abgebildet sind, und die Referenzzähler entsprechend angepasst werden müssen. Der TLB wird beim Schreiben von cr3
implizit geleert. Einzelne Einträge können mit dem Assembler-Befehl invlpg
invalidiert werden. Dieser Befehl funktioniert nur richtig mit indirekter Adressierung über ein Register: asm volatile(`"invlpg (%0)`" : : `"r`"(address) : `"memory`");
Sicher zu stellen, dass eine Copy-on-Write Implementierung das richtige tut, ist schwierig. Wir haben uns daher eine Anwendung ausgedacht, die alle Funktionalitäten aus der dritten Aufgabe auf Randfälle testet.
Diese Anwendung spaltet sich mit fork()
in 4 Paare von Fäden auf. Innerhalb jedes Paares wird ein Faden auf eine Nachricht warten (parent) und der andere Faden (child) schickt seinem Partner eine Nachricht. Der empfangende Faden errechnet aus der Nachricht einen Wert und sendet den Buffer zurück. Am Ende beenden sich alle acht Fäden mit exit()
.
Als Ausgabe wird das Programm etwas ähnliches ausgeben wie:
REPLY: DD REPLY: GG REPLY: EE REPLY: FF
Die Zeilen können in beliebiger Reihenfolge auftreten. Die beiden Buchstaben nach dem REPLY
hängen dabei von eurer Implementierung der Fadennummer ab. Allerdings müssen die beiden Buchstaben, die folgen, in allen Fällen gleich sein!
Um eure Implementierung mit unserer Musterlösung zu vergleichen, könnt ihr eine Messung der freien Seiten durchführen. Um vergleichbare Aussagen zu machen, müsst ihr die Anzahl der freien Seiten messen, die euer Allokator verwaltet, ausgeben. Diese Zahlen müsst ihr an zwei Stellen ausgeben:
scheduler.schedule()
in der main()
IdleThread::action()
Dabei sollt ihr die Anzahl der verfügbaren Kernelpages und die Anzahl der verfügbaren Userpages ausgeben. Die Anzahl der freien Seiten darf dabei steigen, aber auf keinen Fall fallen! Und ihr dürft keine Seiten ausgeben, die in den Multibootinformationen reserved sind.
Der QEMU soll mit 128 MiB (default) RAM ausgestattet sein.
Die Musterlösung ignoriert alle Seiten unterhalb von 0x100000
. Unser Speichererkennungsmechanismus erkennt 32478 freie Seiten. Die Regionen, die dafür bei uns verwendet werden, sind folgende:
module 0x0x118000-0x0x11f000 user/build/initrd.img kernel 0x100000-0x116010 (available) 0x100000 0x7ffdfff \-> use! 0x100000-0xfffff 0 pages \-> use! 0x117000-0x117fff 1 pages \-> use! 0x121000-0x7ffdfff 32477 pages (reserved) 0x7ffe000 0x7ffffff (reserved) 0xfffc0000 0xffffffff
Am ersten Messpunkt benötigt unser System 49 Seiten:
allocator: 49 pages used allocator: 28669 free userpages; 111 MByte allocator: 3760 free kernelpages; 14 MByte
Am zweiten Messpunkt benötigt unser System 42 Seiten:
allocator: 42 pages used allocator: 28670 free userpages; 111 MByte allocator: 3766 free kernelpages; 14 MByte
Diese Abnahme im Seitenverbrauch liegt daran, dass im ersten Messpunkt sowohl ein Benutzerfaden als auch der Idle-Faden existieren. Am zweiten Messpunkt existiert nur noch der Idle-Faden. Unsere Initial Ramdisk verbraucht 7 Seiten.