Übungsaufgabe #3: Nachrichtenaustausch und erweitertes Paging
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.
Systemaufrufe
Folgende Systemaufrufe sollen implementiert werden:
int getpid()
- liefert die Prozess-Identifikationsnummer des aufrufenden Prozesses. Diese Nummer soll im laufenden System eindeutig sein.
void send(int id, void *sbuffer, size_t ssize, void *rbuffer, size_t rsize)
- sendet die Nachricht in
sbuffer
mit der Längessize
synchron und blockierend an den Prozess mit der Nummerid
. Die Operation blockiert solange bis der Empfänger die Nachricht mitrecv()
entgegennimmt und mit Hilfe vonreply()
eine Antwort schickt. Diese Antwort steht dann inrbuffer
mit der Maximallängersize
zur Verfügung. int recv(void *buffer, size_t size)
- wartet auf eine Nachricht die in
buffer
mit der Maximallängesize
abgelegt wird. Der Rückgabewert gibt die Identifikationsnummer des Senders an. void reply(int id, void *buffer, size_t size)
- schickt eine Antwortnachricht an den mit
id
identifizierten Prozess. Diese Funktion soll nicht blockieren und nur erfolgreich sein falls der Zielprozess tatsächlich einsend()
zu diesem Prozess ausführt bzw. auf dessen Fertigstellung wartet. int fork()
- erzeugt einen neuen Prozess der eine Kopie des aktuellen Prozesses
ist. Auch der neue Prozess wird bei Aktivierung aus dem
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 bestem 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.
Kopie bei Schreibzugriff (copy-on-write)
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:
-
Einblenden einer Seite in einen anderen Adressraum.
-
Betroffene Seiten als nur lesbar (read only) in den beteiligten Adressräumen markieren.
-
Ein spezieller pagefault handler (IRQ 14), der dann die Art des Fehlers feststellt, bei einer nur lesbaren Seite eine physikalische Kopie erzeugt und die Adressraumabbildung entsprechend anpasst.
-
Eine Schattentabelle für physikalische Seitenadressen soll die Anzahl der Referenzen auf COW-Seiten zählen. Wenn nur noch eine Referenz übrig ist, muss die Seite bei Schreibzugriffen nicht kopiert 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`");