9. 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 zdieľ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í budete implementovať tieto systémové volania na mapovanie súborov do pamäte.

Pred cvičením si zopakujte sekcie 3.1 Paging hardware, 3.6 Process address space, 4.6 Page-fault exceptions, 8.9 Code: Inodes a 8.13 File descriptor layer z xv6 knižky a preštudujte si príslušný kód.

Ak chcete pracovať s počítačmi v učebni, postupujte podľa sekcie Stiahnutie repozitára na cvičení na stránke Synchronizácia repozitára.

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

Riešenia úloh z tohto cvičenia ukladajte do vetvy mmap.

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

Z manuálovej stránky môžete vidieť, že 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 adresu, na ktorú bude namapovaný súbor určí jadro. Funkcia mmap vráti túto adresu, alebo číslo 0xffffffffffffffff, ak sa súbor nepodarilo namapovať. Argument len 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.

Manuálová stránka (otvoríte ju spustením príkazu man 2 munmap) uvádza deklaráciu funkcie munmap:

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 implementácie spomenutých makier, napríklad PROT_READ. 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 na cvičení, v ktorom ste implementovali COW fork. 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 VMA (virtual memory area), ktorá bude obsahovať pomocné údaje k mapovaniu. 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. Ak veľkosť súboru nie je zaokrúhlená na hranicu stránky, koniec poslednej namapovanej stránky za obsahom súboru vyplňte nulami. 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 (inšpirujte sa funkciou 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. Teraz by mal prejsť mmap_test.
  • 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.
  • Upravte exit, aby odmapoval regióny procesu s namapovanými súbormi (tak isto, ako to robí munmap). Po spustení mmaptest by mali prejsť testy mmap_test a fork_test.

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

Spustite príkaz make grade, aby ste overili správnosť svojho riešenia. Po úspešnom dokončení úlohy nezabudnite vytvoriť commit! 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. Na záver aj túto zmenu commitnite do repozitára. Týmto je cvičenie ukončené.

Voliteľné úlohy

Prvý riešiteľ úlohy môže dostať bonusové body po kontrole riešenia. Informujte sa u prednášajúceho alebo cvičiaceho. Voliteľnú úlohú môžete riešiť až po vyriešení všetkých úloh základnej časti cvičenia! Riešenia prijímame iba do 10.12.2023.

  • 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)