3. cvičenie – tabuľky stránok

Tabuľky stránok

V tomto cvičení preskúmate tabuľky stránok a upravíte ich za účelom zjednodušenia funkcií, ktoré kopírujú dáta z užívateľského priestoru do priestoru jadra.

Než začnete programovať, prečítajte si kapitolu 3 xv6 knižky a pozrite si príslušné súbory:

  • V kernel/memlayout.h je znázornené usporiadanie pamäte.
  • kernel/vm.c obsahuje väčšinu kódu, ktorý pracuje s virtuánou pamäťou (VM).
  • kernel/kalloc.c obsahuje kód na alokáciu a uvoľnovanie fyzickej pamäte.

Najprv sa prepnite do vetvy pgtbl:

Vypísanie tabuľky stránok (ľahká)

Vašou prvou úohou je napísať funkciu, ktorá vypíše obsah tabuľky stránok. Pomôže vám to pri pochopení, ako fungujú tabuľky stránok v RISC-V a funkciu môžete v budúcnosti použiť pri ladení systému.

Definujte funkciu vmprint(). Bude mať jeden argument typu pagetable_t a vypíše tabuľku stránok vo formáte, ktorý je uvedený nižšie. Funkciu môžete otestovať napríkad tak, že ju zavoláte v súbore exec.c tesne pred return argc. Aby bola tabuľka vypísaná len pre prvý proces, skontrolujte číso procesu: if(p->pid==1) vmprint(p->pagetable). Plný počet bodov za túto úlohu dostanete, ak prejdete testom pte printout.

Keď spustíte xv6, mali by ste vidieť podobný výstup. Ak ste funkciu zavolali vyššie popísaným spôsobom, jedná sa o stav tabuľky prvého procesu (init) tesne pred jeho spustením v systémovom volaní exec.

V prvom riadku je argument funkcie vmprint. Potom nasledujú riadky pre každý PTE, vrátane vnorených záznamov. Každý riadok PTE je odsadený znakmi .. podľa toho, ako hboko v strome sa nachádza. Každý riadok obsahuje index záznamu, bity pte a fyzickú adresu vytiahnutú z PTE. Nevypisujte neplatné záznamy. V príklade vyššie má tabuľka najvyššej úrovne mapovania pre záznamy 0 a 255. O úroveň nižšie, záznam 0 má namapovaný len index 0. Na najnižšej úrovni má tento index namapované záznamy 0, 1 a 2.

Fyzické adresy na vašom počítači môžu byť odlišné od tých v ukážke výstupu. Počet záznamov a virtuálne adresy by mali byť rovnaké.

Pomôcky:

  • vmprint() môžete definovať v kernel/vm.c.
  • Použite makrá na konci súboru kernel/riscv.h.
  • Inšpirujte sa funkciou freewalk().
  • Deklaráciu funkcie vmprint() dajte do kernel/defs.h, aby ste ju mohi zavoať z exec.c.
  • Použite formátovaciu značku %p na výpis PTE a adries ako 64-bitové hexadecimálne číslo.
Vysvetlite výstup funkcie vmprint() a jeho súvis s obrázkom 3-4 z xv6 knižky. Čo obsahuje stránka 0? Čo je v stránke 2? Keď proces beží v užívateľskom móde, môže prečítať/zapísať pamäť mapovanú stránkou 1?

Jedna tabuľka stránok jadra na každý proces (ťažká)

Xv6 má jednu tabuľku stránok jadra, ktorá sa používa počas vykonávania kódu jadra. Tabuľka stránok jadra priamo mapuje virtuálne adresy na identické fyzické adresy. Xv6 má tiež samostatnú tabuľku stránok pre adresný priestor každého používateľského procesu. Tieto tabuľky obsahujú iba mapovania užívateľskej pamäte pre konkrétny proces od adresy nula. Keďže tabuľka stránok jadra neobsahuje tieto mapovania, užívateľské adresy sú v jadre neplatné. To znamená, že ak chce jadro použiť užívateľský smerník zo systémového volania (napr. smerník na buffer v systémovom volaní write()), musí najprv preložiť smerník na fyzickú adresu. Cieľom tejto a ďalšej sekcie je umožniť jadru priamo dereferencovať užívateľské smerníky.

Vašou prvou úlohou je modifikovať jadro tak, aby každý proces používal vlastnú kópiu tabuľky stránok jadra počas vykonávania kódu v jadre. Upravte struct proc, aby uchovala tabuľku stránok jadra pre každý proces a modifikujte plánovač, aby prepol tabuľku stránok jadra pri prepínaní procesov. Zatiaľ stačí, aby bola každá tabuľka stránok jadra pre každý proces identická s globálnou tabuľkou stránok jadra. Úlohu ste zvládli, ak zbehnú testy usertests.

Prečítajte si kapitolu z knižky xv6 a súbory uvedené na začiatku tohoto cvičenia. Ľahšie sa vám bude pracovať s virtuálnou pamäťou, keď jej budete rozumieť. Chyby v tabuľkách stránok môžu spôsobovať výnimky kvôli chýbajúcim mapovaniam, čítanie a zápis nezamýšľaných fyzických stránok a spustenie inštrukcií z nesprávnych stránok pamäte.

Pomôcky:

  • Pridajte položku do štruktúry struct proc, kde bude uložená tabuľka stránok jadra.
  • Dobrý spôsob na vytvorenie tabuľky stránok jadra novému procesu je implementovať upravenú verziu funkcie kvminit(), ktorá vytvorí novú tabuľku stránok namiesto modifikovania kernel_pagetable. Túto funkciu budete volať z allocproc() (odporúčame všetky funkcie, ktoré pracujú s premennou kernel_pagetable dôkladne preskúmať a zvážiť, či ich netreba vzhľadom na zadanie modifikovať, aby sa v nich používala tabuľka stránok dodaná ako argument volania – viď napríklad funkciu kvmmap())
    • Ak nebude možné nejaký adresný rozsah namapovať (napr. z dôvodu nedostatku pamäti pre tabuľky stránok), musíte vrátiť späť všetky zmeny, ktoré ste urobili. To znamená, že musíte odmapovať všetky rozsahy, a to aj také, ktoré boli namapované čiastočne. Takto sa vyhnete únikom pamäte.
  • Tabuľka stránok jadra každého procesu musí mať mapovanie na zásobník jadra pre každý proces. Funkcia procinit(), ktorá je zavolaná počas bootovanie mapuje zásobníky jadra pre všetky procesy do tabuľky stránok jadra. Môžete to urobiť podobne, alebo namapovať iba zásobník jadra konkrétneho procesu.
  • Upravte funkciu scheduler(), aby načítala tabuľku stránok jadra pre proces do registra satp (viď kvminithart() pre inšpiráciu). Nezabudnite zavolať sfence_vma() po zavolaní w_satp().
  • Po tom, ako sa proces prestane vykonávať a vráti sa do plánovača, nastavte tabuľku stránok na kernel_pagetable.
  • Uvoľnite tabuľku stránok procesu vo funkciifreeproc(). V závislosti od toho, ako ste vyriešili problém so zásobníkom jadra, môže byť nutné v tej istej funkcii uvoľniť aj pamäť zásobníka pre jadro.
  • vmprint() sa vám môže hodiť pri ladení tabuliek stránok.
  • Nebojte sa modifikovať alebo pridať nové funkcie do xv6. Musíte to urobiť minimálne v kernel/vm.c a kernel/proc.c. (Ale neupravujte kernel/vmcopyin.c, kernel/stats.c, user/usertests.c a user/stats.c.)
  • Možno budete musieť upraviť kód, ktorý uvoľnuje tabuľky stránok, aby umožnil uvoľniť tabuľku stránok bez uvoľnenia fyzických stránok, ktoré predstavujú listy v strome.
  • Pri chýbajúcom mapovaní tabuľky stránok nastane v jadre výpadok stránky. Vo výpise chyby bude okrem iného hodnota sepc=0x00000000XXXXXXXX. Miesto výpadku môžete nájsť, keď vyhľadáte XXXXXXXX v súbore kernel/kernel.asm.

Zjednodušte copyin/copyinstr (ťažká)

Funkcia jadra copyin() číta pamäť, na ktorú ukazujú užívateľské smerníky. Robí to preložením virtuálnej adresy na fyzickú adresu, ktorú jadro vie priamo dereferencovať. Preklad sa vykonáva softvérovým kráčaním po tabuľke stránok procesu. Vašou úlohou je pridať užívateľské mapovania do každej tabuľky stránok jadra pre každý proces (ktoré ste vytvorili v predchádzajúcej sekcii), ktoré by umožnili funkcii copyin() (a príbuznej funkcii pre kopírovanie reťazcov copyinstr) priamo dereferencovať užívateľské smerníky.

Nahraďte telo funkcie copyin() v súbore kernel/vm.c volaním funkcie copyin_new() (definovanej v kernel/vmcopyin.c); to isté spravte pre copyinstr() a copyinstr_new(). Pridajte mapovanie užívateľských adries do každej tabuľky jadra pre každý proces tak, aby copyin_new() a copyinstr_new() fungovali. Úlohu ste splnili, ak prejdete testami usertests a taktiež všetkými zvyšnými testami make grade.

Táto schéma spolieha na to, že užívateľský adresný priestor sa neprekrýva s rozsahom virtuálnych adries, ktoré používa jadro pre svoje vlastné inštrukcie a dáta. Xv6 pre užívateľské priestory používa virtuálne adresy, ktoré začínajú na nule, zatiaľ čo pamäť jadra začína na vyšších adresách. To znamená, že veľkosť užívateľského procesu musí byť menšia ako najmenšia virtuálna adresa jadra. Po naštartovaní jadra je táto adresa 0xC000000, čo je zároveň adresa registrov PLIC, viď kvminit() v kernel/vm.c, kernel/memlayout.h, a obrázok 3.3 z knižky. Ak sa pozriete pozornejšie, skutočná najnižšia adresa, ktorú jadro využíva, je 0x2000000, adresa registrov CLINT. Xv6 ale používa tieto registre iba počas bootovacieho procesu, takže je bezpečné ich premapovať. Musíte upraviť xv6, aby užívateľské procesy neboli väčšie ako adresa PLIC.

Pomôcky:

  • Najprv nahraďte copyin() volaním copyin_new() tak, aby to fungovalo, než sa pustíte do copyinstr().
  • Na každom mieste, kde jadro modifikuje mapovanie virtuálnej pamäte pre užívateľský priestor procesu (t. j. modifikuje p->pagetable), doplňte zodpovedajúcu modifikáciu tabuliek virtuálnej pamäte pre jadro toho istého procesu (na prístup k virtuálnej pamäti jadra použite vami vytvorenú položku v štruktúre proc z predošlej úlohy). Zamerajte sa na systémové volania fork(), exec() a sbrk().
  • Nezabudnite namapovať užívateľskú tabuľku stránok prvého procesu do tabuľky stránok jadra vo funkcii userinit().
  • Aké povolenia musia byť nastavené v PTE pre užívateľské adresy v tabuľke stránok jadra pre procesy? (K tabuľke s nastaveným PTE_U nemôže jadro pristúpiť.)
  • Nezabudnite ošetriť PLIC limit.

Linux používa podobnú techniku, ako ste práve implementovali. Ešte pred pár rokmi sa štandardne používala rovnaká tabuľka stránok v užívateľskom priestore aj priestore jadra, kde boli spolu mapované užívateľské adresy a adresy jadra. Výhoda takéhoto riešenia bolo, že jadro nemuselo prepínať tabuľky stránok pri prechode medzi užívateľským priestorom a priestorom jadra. Po objavení zraniteľností Meltdown a Spectre musel byť tento spôsob nahradený.

Vysvetlite, prečo je potrebný druhý test srcva + len < srcva vo funkcii copyin_new(): uveďte konkrétne príklady hodnôt, pre ktoré prvý test (srcva+len >= p->z) zlyhá, ale druhý prejde.

Týmto je cvičenie hotové. Skontrolujte, či vaše riešenie prejde všetkými testami make grade. 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 nezabudnite svoje zmeny commitnúť do repozitára.

make grade môže zlyhať aj v prípade, že testy osamote zbehnú v poriadku. Je to spôsobené časovým limitom testov. Môžete ho zvýšiť v príslušnom súbore grade-lab-pgtbl (parametre timeout). Časový limit zmeňte iba v takom prípade, že viete, že samotný test zbehne v poriadku a nič iné v súbore neupravujte.

Pre úspešné testovanie je potrebné použiť aspoň 3 jadrá vo VirtualBoxe.

Voliteľné úlohy

  • Použite super-stránky, aby ste zmenšili počet PTE v tabuľkách stránok.
  • Rozšírte vaše riešenie, aby podporovalo ľubovoľné veľkosti užívateľských programov; teda aj také, ktoré sú väčšie ako PLIC.
  • Odmapujte prvú stránku užívateľského procesu, aby dereferencia nulového smerníka viedla k výpadku stránky. Užívateľský segment text musí začať napríklad na adrese 4096 namiesto 0.