Blog
Kernelzugriff auf mehrfachreferenzierte Seiten
2022-07-21
Bei Copy-On-Write wird eine Seite “kopiert”, in dem wir die gleiche Kachel (Page Frame) einfach in mehreren Adressräumen einblenden – und durch Entfernen der Schreibberechtigung bei einem Schreibzugriff einen Pagefault auslösen.
So weit so einfach.
Wichtig dabei: Das gilt nur im Userspace (Ring 3)!
Außer ihr habt das 16. Bit im cr0 Register gesetzt, welches dazu führt, dass auch in Ring 0 dieses Bit berücksichtigt wird – würde ich jedoch nicht empfehlen!
Nun werden aber einige Operationen im Kernel (Ring 0) durchgeführt, welche einen schreibenden Zugriff auf Puffer aus dem Userspace haben.
Beispiel
Nehmen wir an, unsere Anwendung hat einen Puffer (z.B. char puffer[42];
). Ein fork()
führt dazu, dass der Page Frame, in welchem der Puffer liegt, sowohl im Eltern- als auch Kindprozess eingeblendet wird (aber nur lesbar).
Nun kann es natürlich leicht vorkommen, dass in der Anwendung keine Schreibzugriffe auf andere Daten, die in diesem Page Frame liegen, durchgeführt werden (weder im Eltern- noch im Kindprozess), bevor der Puffer einem Systemaufruf übergeben wird – welcher diesen dann wiederum mit Daten befüllen soll (z.B. recv(puffer, 42);
im Elternprozess).
Dann muss in der Kernelimplementierung des Systemaufrufs (Skeleton) irgendwo beim Kopieren ein Schreibzugriff auf den Puffer ausgeführt werden… wenn wir das ohne weitere Überprüfung machen, dann schreiben wir die Inhalte in die eigentlich wegen Copy-On-Write (im Userspace) schreibgeschützte Seite – und sowohl Eltern- als auch Kindprozess sehen die geänderten Daten im Puffer, obwohl diese nur beim Elternprozess sein sollte!
Lösung
Auch wenn wir eine Kopieroperation haben, bei welcher nur der Teil eines Page Frames verändert wird, wir also wirklich die Inhalte kopieren müssen, dann müssen wir dennoch den Referenzzähler prüfen: Wenn der größer als 1
ist, so muss zuerst der Page Frame dupliziert werden (neuer Frame vom Allocator und Inhalte kopieren) – also das, was eigentlich der Pagefaulthandler bei Schreibzugriffen auf CoW-Seiten im Userspace macht – bevor mit dem eigentlichen Kopiervorgang für den Pufferinhalt beginnen können. (Vergesst dabei den TLB nicht!)
Wenn bei euch in der Testanwendung (sporadisch) ein Reply X=Y bad!
kommt, und irgendwo davor ein Reply X=X good
steht, nun, dann ist das sehr wahrscheinlich dieser Fehler: der Zugriff auf rbuf[8192]
kann diesen Fehler verursachen (wenn auch der Scheduler “richtig” einlastet). Sollte der Fehler eher selten kommen, so kann man die Wahrscheinlichkeit durch ein paar zusätzliche fork
am Beginn der Testanwendung erhöhen.