Virtuálna pamäť umožňuje implementovať určitý spôsob presmerovania prístupu k dátam. Ak jadro označí PTE ako neplatný alebo iba na čítanie, prístup k danej stránke spôsobí výpadok stránky a jadro môže rozhodnúť, čo tento prístup znamená.
V počítačových systémoch existuje porekadlo, ktoré hovorí, že akýkoľvek systémový problém je možné vyriešiť presmerovaním. V tomto cvičení sa pozrieme na ďalší príklad – copy-on write fork.
Pred začiatkom práce prepnite repozitár do vetvy cow:
1 2 3 |
$ git fetch $ git switch cow # alebo git checkout cow $ make clean |
Systémové volanie fork()
v xv6 kopíruje celý adresný priestor rodiča do priestoru potomka. Ak je adresný priestor rodiča veľký, kopírovanie môže trvať dlho (relatívne k rýchlosti procesora). Naviac sa týmto tiež zbytočne plytvá pamäťou – niektoré stránky nemodifikuje ani rodič, ani potomok. To znamená, že by mohli byť spoločné pre oba procesy. Najlepším príkladom neefektívnosti takéhoto kopírovania je volanie exec()
v detskom procese, ktoré obvykle nasleduje hneď po forku. exec()
uvoľní všetky ťažko nakopírované stránky zvyčajne bez toho, aby nejakú použil. Kopírovať stránku by sme v ideálnom prípade mali až vtedy, ak ju zdieľajú rodič aj dieťa a aspoň jeden chce do nej zapisovať.
Cieľom vašej implementácie copy-on-write (COW) forku je odložiť alokáciu a kopírovanie fyzickej pamäte pre detský proces, až kým kópie nie sú skutočne potrebné (t. j. buď rodič a/alebo dieťa sa pokúsi o zápis do spoločnej stránky).
COW fork()
vytvorí iba tabuľku stránok pre detský proces. Záznamy v tabuľke (PTE) budú ukazovať na fyzické stránky (údaje v RAM) rodičovského procesu. Zároveň záznamy v tabuľke stránok oboch procesov budú označené iba na čítanie. Ak sa jeden z procesov pokúsi zapísať niečo do COW stránky, CPU vynúti výpadok stránky. Obsluha výpadkov stránky v jadre tento výpadok zachytí, alokuje stránku fyzickej pamäte pre proces, ktorý spôsobil výpadok, skopíruje obsah pôvodnej (spoločnej) stránky do novej stránky a upraví súvisiace záznamy v tabuľke stránok, aby ukazovali na novú stránku s príznakom PTE_W, ktorý umožní zapisovanie. Po návrate z obsluhy výpadku bude môcť užívateľský proces pokračovať v zápise do vlastnej kópie stránky.
Uvoľňovanie fyzických stránok procesov bude pri COW forku o niečo zložitejšie. Jedna fyzická stránka môže byť namapovaná vo viacerých procesoch. Uvoľnená by mala byť až po zániku poslednej referencie, teda keď ju odmapuje posledný proces, ktorý ju používa. V jednoduchom jadre, ako xv6, nie je zložité udržať informáciu o procesoch, v ktorých je namapovaná nejaká stránka. V produkčných jadrách to už môže byť náročnejšia úloha. Prečítajte si článok o tom, ako to robia v Linuxe: Patching until the COWs come home.
Na otestovanie vašej implementácie môžete použiť program cowtest (jeho zdrojový súbor nájdete v user/cowtest.c
). cowtest obsahuje niekoľko testov, na neupravenom kóde xv6 zlyhá hneď prvý test. Takto to vyzerá:
1 2 3 |
$ cowtest simple: fork() failed $ |
Test simple alokuje viac ako polovicu dostupnej fyzickej pamäte a potom zavolá fork()
. Fork zlyhá, pretože nie je miesto na skopírovanie celého adresného priestoru rodiča.
Implementácia je korektná, ak úspešne prebehnú programy cowtest a usertests -q. Takto by to malo vyzerať:
1 2 3 4 5 6 7 8 9 10 11 12 |
$ cowtest simple: ok simple: ok three: ok three: ok three: ok file: ok ALL COW TESTS PASSED $ usertests -q ... ALL TESTS PASSED $ |
Postupovať by ste mali takto:
uvmcopy()
tak, aby namapovala fyzické stránky rodiča do dieťaťa namiesto alokácie nových stránok. Aj v rodičovi, aj v potomkovi vynulujte príznak PTE_W
stránkam, ktoré ho majú nastavený.usertrap()
tak, aby rozoznala výpadky stránok. Ak pri zapisovaní nastane výpadok stránky na COW stránke, ktorá bola pôvodne zapisovateľná, alokujte novú stránku pomocou kalloc()
, skopírujte obsah starej stránky do novej stránky a namapujte novú stránku do adresného priestoru procesu, ktorý výpadok spôsobil s nastaveným príznakom PTE_W
. Stránky, ktoré boli pôvodne mapované len na čítanie (nemali nastavený príznak PTE_W
, napríklad stránky zo segmentu text) by mali byť zdieľané medzi rodičom a potomkom len na čítanie. Ak sa rodič alebo potomok pokúsi zapisovať do takejto stránky, musí byť ukončený.kalloc.c
a pouvažujte, či by sa tam nehodilo implementovať počítanie referencií.copyout()
tak, aby používal vyššie popísanú schému pri výpadku stránky COW.Pomôcky:
cowtest
netestuje. Nezabudnite preto skontrolovať výsledok oboch testov.Spustite príkaz make grade, aby ste overili správnosť svojho riešenia. Aby ste prešli posledným testom time, musíte vytvoriť nový súbor time.txt, v ktorom uvediete počet hodín, ktorý ste strávili nad zadaním ako celé číslo.
Týmto je cvičenie ukončené. Svoje zmeny môžete commitnúť do repozitára.