10. cvičenie – mmap

mmap (ťažká)

Systémové volania mmap a munmap umožňujú UNIX programom získať väčšiu kontrolu nad ich adresným priestorom. Môžu byť použité na zdeľanie pamäte medzi procesmi, mapovanie súborov do adresného priestoru procesu a rôzne schémy na obsluhu výpadkov stránok z užívateľského prostredia. Na tomto cvičení implementujete tieto systémové volania na mapovanie súborov do pamäte.

Stiahnite si kód pre cvičenie do repozitára a prepnite sa na vetvu mmap:

Manuálová stránka Linuxu pre mmap (man 2 mmap) uvádza túto deklaráciu funkcie mmap:

Ako môžete vidieť z manuálovej stránky, mmap môže byť volaná mnohými spôsobmi. Na tomto cvičení nám ale bude stačiť funkcionalita pre mapovanie súboru do pamäte. Môžete predpokladať, že argument addr bude vždy nulový, čiže jadro rozhodne na ktorej virtuálnej adrese namapuje súbor. Funkcia mmap vráti túto adresu, alebo číslo 0xffffffffffffffff, ak sa súbor nepodarilo namapovať. Argument length určuje počet bajtov, ktoré sa majú namapovať; nemusí byť rovnaký ako veľkosť súboru. Argument prot určuje, či má byť pamäť mapovaná na čítanie, zápis a/alebo vykonanie. Môžete predpokladať, že bude nadobúdať hodnoty PROT_READ, PROT_WRITE, alebo kombináciu oboch. Argument flags bude buď MAP_SHARED, čo znamená, že úpravy v namapovanej pamäti majú byť zapísané späť do súboru, alebo MAP_PRIVATE, kedy úpravy nebudú do súboru spätne zapísané. Podporu iných bitov vo flags nie je potrebné implementovať. Argument fd je otvorený súborový deskriptor so súborom, ktorý sa má namapovať. Môžete predpokladať, že offset je nulový (určuje začiatočný bod v súbore, od ktorého sa bude súbor mapovať)

Je v poriadku, ak procesy, ktoré zdieľajú rovnaký MAP_SHARED súbor nezdieľajú rovnaké fyzické stránky.

Systémové volanie munmap(addr, length) by malo odstrániť mapovania vytvorené volaním mmap v požadovanom adresnom rozsahu. Ak proces modifikoval pamäť a táto bola namapovaná ako MAP_SHARED, modifikácie by mali byť najprv zapísané do súboru. Volanie munmap môže tiež iba čiastočne pokryť pôvodne mapovaný región. Môžete ale predpokladať, že sa odmapuje buď čiastočne región od začiatku alebo konca súboru, prípadne celý súbor (nikdy sa ale neodmapuje región v strede súboru).

Mali by ste implementovať toľko funkcionality systémových volaní mmap a munmap, aby ste prešli testom mmaptest. Ak mmaptest nepoužíva nejakú funkcionalitu systémového volania mmap, nemusíte túto funkcionalitu implementovať.

Keď budete hotoví, mali by ste vidieť takýto výstup:

Pomôcky:

  • Pridajte _mmaptest do UPROGS a implementujte systémové volania mmap a munmap, aby ste mohli skompilovať user/mmaptest.c. Na začiatok stačí, aby ste z implementovaných systémových volaní vrátili chybové kódy. V súbore kernel/fcntl.h nájdete implementované makro PROT_READ a ďalšie. Spustite mmaptest, ktorý by mal zlyhať pri prvom volaní mmap.
  • Tabuľku stránok napĺňajte lenivo obslúžením výpadkov stránok. To znamená, že v mmap nebudete alokovať fyzickú pamäť alebo čítať mapovaný súbor. Namiesto toho to urobíte pomocou obsluhy výpadkov stránok z funkcie usertrap (alebo pomocou pomocnej funkcie zavolanej odtiaľ), podobne ako ste to robili na piatom cvičení s lenivou alokáciou. Robíte to preto, aby bolo systémové volanie mmap rýchle aj pre veľké súbory a aby ste mohli mapovať aj súbory, ktoré sú väčšie ako fyzická pamäť.
  • Zaznamenajte, ktoré súbory namapoval mmap pre ktorý proces. Definujte štruktúru podobnú VMA (virtual memory area), o ktorej sme hovorili na piatej prednáške a piatom seminári. Zapísať by ste mali adresu, dĺžku, povolenia, súbor, atď. pre rozsah virtuálnej pamäte vytvorený volaním mmap. Keďže xv6 nemá pamäťový alokátor v jadre, môžete deklarovať pole pevnej dĺžky, do ktorého budete ukladať položky VMA podľa potreby. Dĺžka 16 by mala byť postačujúca.
  • Implementujte mmap: nájdite nevyužitý región vo virtuálnej pamäti procesu, do ktorého namapujete súbor a pridajte záznam VMA do tabuľky mapovaných regiónov daného procesu. Záznam VMA by mal obsahovať smerník na štruktúru struct file súboru, ktorý je mapovaný. Nezabudnite navýšiť počítadlo referencií v tejto štruktúre, aby nezanikla po uzavretí súboru (viď filedup). Spustite mmaptest: teraz by malo byť prvé volanie mmap úspešné, ale prvý prístup k mapovanej pamäti spôsobí výpadok stránky, čo vyústi ukončením procesu mmaptest.
  • Pridajte kód, ktorý pri výpadku stránky v regióne s mapovaným súborom alokuje stránku fyzickej pamäte, načíta do nej 4096 bajtov mapovaného súboru a namapuje ju do adresného priestoru užívateľa. Súbor prečítajte pomocou funkcie readi. Táto funkcia má na vstupe offset, od ktorého načíta súbor (nezabudnite zamknúť/odomknúť príslušný inode, ktorý čítate). Taktiež nezabudnite nastaviť správne povolenia počas mapovania stránky. Po spustení testu mmaptest by ste sa mali dostať až k prvému volaniu munmap, ktoré zlyhá.
  • Implementujte munmap: nájdite VMA pre príslušný adresný rozsah a odmapujte požadované stránky (použite uvmunmap). Ak munmap odmapoval všetky stránky súboru, mal by znížiť počítadlo referencií príslušnej štruktúry file. Ak stránka, ktorú odmapoval bola upravená a súbor bol mapovaný s príznakom MAP_SHARED, zapíšte stránku späť do súboru. Inšpiráciu k tomuto hľadajte vo funkcii filewrite.
  • Vaša implementácia by mala ideálne zapisovať len tie MAP_SHARED stránky, ktoré boli naozaj upravené. Bit dirty (D) v PTE na RISC-V architektúre signalizuje, že do stránky prebehol zápis. Toto ale mmaptest netestuje, tak sa zaobídete aj bez tejto úpravy.
  • Upravte exit, aby odmapoval regióny procesu s namapovanými súbormi (tak isto, ako to robí munmap). Po spustení nmaptest by mal prejsť test nmap_test, ale fork_test ešte asi nie.
  • Upravte fork, aby detský proces mal namapované presne tie isté regióny ako jeho rodič. Nezabudnite tiež zvýšiť počítadlo referencií štruktúry file pre každý záznam VMA. Pri obsluhe výpadku stránky v detskom procese je v poriadku, ak alokujete novú fyzickú stránku namiesto toho, aby ste ju zdieľali s rodičom. Bolo by to zaujímavé, ale vyžadovalo by to viac práce. Spustite mmaptest. Teraz by mali prejsť mmap_test aj fork_test.

Na záver spustite usertests, aby ste overili, či funguje aj zvyšok systému.

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.

Voliteľné úlohy

  • Ak dva procesy majú namapovaný rovnaký súbor (tak ako v teste fork_test), zdieľajte ich fyzické stránky. Na týchto stránkach budete potrebovať počítadlo referencií.
  • Vaše riešenie pravdepodobne alokuje novú fyzickú stránku pre každú stránku, ktorú prečíta z mapovaného súboru, aj keď obsah súboru je v keške bufferov. Upravte vašu implementáciu, aby ste použili dáta z kešky. Aby to fungovalo, musíte upraviť veľkosť bufferov, aby bola rovnaká ako veľkosť stránky (nastavte BSIZE na 4096). Namapované bloky musíte pripnúť do kešky, aby z nej nemohli byť vyhodené. Musíte teda upraviť počítadlo referencií.
  • Odstránte duplicitu kódu medzi vaším riešením lenivej alokácie a riešením mapovania súborov (vytvorte VMA pre oblasť lenivej alokácie).
  • Upravte exec, aby používal VMA pre rôzne sekcie spustiteľných súborov, aby načítanie inštrukcií pri behu programu prebiehalo na požiadanie. Takto budú programy štartovať rýchlejšie, pretože exec nebude musieť načítať dáta zo súborového systému.
  • Implementujte page-out a page-in: keď klesne veľkosť voľnej pamäte, kernel by mal presunúť určité časti procesov na disk. Keď proces vyžiada pamäť zo stránky, ktorá bola odložená, vrátite ju späť do fyzickej pamäte (a prípadne odložíte inú nepotrebnú stránku)