Sledovanie referencií rámca

Sledovanie referencií rámca

Správu fyzickej pamäte má na starosti alokátor fyzickej pamäte implementovaný v kernel/kalloc.c; ide predovšetkým o dvojicu funkcií kalloc() a kfree(). Na správu fyzickej pamäte slúži riadiaca štruktúra alokátora kmem:

Položka lock slúži na zabezpečenie prístupu k zoznamu voľných blokov freelist. V položke freelist je uchovávaná adresa začiatku zoznamu voľných rámcov fyzickej pamäte. V prípade, že je rámec voľný, adresa nasledovného voľného bloku sa uchováva na začiatku samotného bloku. Ak blok voľný nie je, ale je alokovaný, v zozname voľných blokov sa nenachádza. Inicializácia alokátora prebieha v dvojriadkovej funkcii kinit():

Prvý riadok inicializuje zámok, na druhom sa volá špeciálna funkcia uvoľnenia rozsahu fyzickej pamäte: prvý argument je začiatok oblasti, druhý koniec. Funkcia prechádza celý rozsah fyzickej pamäte a pre každú adresu, ktorá je začiatkom stránky, volá funkciu free(), čím sa daná stránka dostane do zoznamu voľnej pamäte.

Samotné funkcie kalloc a kfree sú triviálne: prvá odoberá zo zoznamu voľných blokov, druhá do neho pridáva. Manipulácia so zoznamom sa robí pri zamknutom zámku. Okrem toho kfree kontroluje vstupnú adresu, ktorá musí byť zarovnaná na stránku a z rozsahu platných fyzických adries. Obe funkcie vypĺňajú alokovanú/uvoľnenú stránku zvolenou konštantou, aby sa ľahšie hľadali programátorské chyby.

Informácia o stave rámcov

Z uvedeného vidno, že okrem zoznamu voľných rámcov nemá alokátor žiadnu informáciu o stave rámcov. Na úspešné dokončenie COW potrebujeme nejaký mechanizmus, pomocou ktorého budeme vedieť, ktorý rámec má koľko mapovaní. Je zrejmé, že rámce, ktoré sú voľné, budú mať 0 mapovaní (voľný rámec nemôže byť predsa pripojený do žiadneho virtuálneho adresného priestoru). Aby sme vedeli sledovať počet mapovaní, rozšírime štruktúru kmem o ďalšiu položku:

cntref bude pole, pre ktoré bude platiť, že každý prvok zodpovedá práve jednému rámcu pamäte. Tí vnímavejší si neskôr môžu všimnúť, že pre niektoré rámce fyzickej pamäte v poli nebudú jestvovať záznamy, a pre niektoré rámce fyzickej pamäte sa zasa nikdy ich stav v poli nebude meniť. Ale to sú už raz dané čary programovania&hellip

Je zrejmé, že pole referencií potrebujeme nejako inicializovať. Na tento účel sa najvhodnejšou javí funkcia incializácie alokátora, kinit.

Pri postupnej inicializácií pomocou premennej frames počítame počet dostupných rámcov. Začiatok poľa kmem.cntref určíme za symbolom end jadra xv6. Autori xv6 zaviedli tento symbol preto, aby bolo jasné, na akej fyzickej adrese končia údaje a kód jadra. Za touto adresou je fyzická pamäť voľná. Preto si môžeme dovoliť zaokrúhliť (ale ono by to malo fungovať aj bez zaokrúhlenia — experimentálne to overte) hodnotu prvého bajtu voľnej fyzickej pamäte end nahor na celé stránky. Potom budeme postupne prechádzať celú voľnú fyzickú pamäť až po hornú hranicu PHYSTOP (xv6 dokáže pracovať iba s fyzickou pamäťou do veľkosti PHYSTOP). Pre každú stránku pamäte nastavíme v poli cntref počet referencií na 1. Prečo na 1, keď sa má jednať o voľné bloky pamäte? Pretože sme programátori… Keď sa pozrieme na posledný riadok funkcie kinit, voláme funkciu freerange. Ako sme už vysvetlili vyššie, v tejto funkcii sa znovu prechádza celý interval voľnej pamäte, a na každý rámec sa volá funkcia free. Ešte sme úpravu tejto funkcie síce nespomínali, ale prezradíme toľko, že vo funkcii free sa bude znižovať počet referencií na rámec. Akonáhle klesne na 0, rámec sa pridá do zoznamu voľných stránok fyzickej pamäte.

Prevod fyzickej adresy na index do poľa

Niekto si možno všimne, že pri prístupe do poľa cntref máme k dispozícií fyzickú adresu, nie index. Nejakým spôsobom však musíme previesť fyzickú adresu na index do poľa, inak by sme nevedeli pole rozumne použiť. Na to nám poslúži makro PA2IND():

Vstupom makra je fyzická adresa pa. Ak od tejto adresy odpočítame začiatočnú adresu oblasti, pre ktorú chceme evidovať referencie, a takto získanú hodnotu podelíme veľkosťou stránky, dostaneme číslo stránky, pre ktorú chceme zistiť/nastaviť počet referencií (toto vypočítané číslo stránky je priamo indexom do poľa cntref). Upozornenie: takto vypočítané číslo stránky nemá nič spoločné s PPN v PTE záznamoch! Pri sledovaní referencií nás totiž nezaujíma celá fyzická pamäť (od adresy 0), ale iba časť pamäte od adresy PGROUNDUP(end). Nikdy totiž nebudeme uvoľňovať fyzickú pamäť kódu a údajov jadra! Preto fyzická pamäť v intervale <0; PGROUNDUP(end)) nie je predmetom sledovania referencií, a žiaden index poľa cntref nie je s touto pamäťou spojený.

Pre názornosť prikladáme veľmi zjednodušenú schému fyzickej pamäte, na ktorej vidno rozdelenie na rámce, hodnoty a premenné použité pre správu referencií:

Funkcie na inkrementáciu/dekrementáciu počtu referencií

Napísať funkcie na zvyšovanie či znižovanie počtu referencií je jednoduché, keď už máme k dispozícií pole, ktoré bude sledovať tieto počty pre jednotlivé rámce voľnej fyzickej pamäte a makro, pomocou ktorého získame index do poľa na základe fyzickej adresy.

Funkcia dec_ref najprv získa zámok pre prácu s fyzickou pamäťou; zámok môže byť užitočný, pokiaľ by viaceré procesory súčasne začali vykonávať kód dec_ref/inc_ref pre tú istú fyzickú adresu. Ak by nebol použitý riadený prístup po jednom na modifikáciu prvku poľa cntref[pa], súčasná modifikácia viacerých cpu jedného pamäťového miesta by mohla viesť ku nekonzistencii počtu referencií pre fyzickú stránku pa.

Po získaní zámku je nutné skontrolovať správnosť volania funkcie dec_ref. Táto funkcia nesmie byť vyvolaná pre stránku, ktorá má počet referencií 0, t.j. pre nepoužitú (voľnú) stránku. Keby k takému volaniu prišlo, jedná sa o chybu prográtora jadra, a preto o takejto situácii treba ihneď informovať — napríklad pomocou ukončenia behu OS spolu s výpisom, kde ku chybe prišlo.

Po kontrole je možné znížiť počet refrencií na stránku o 1, uložiť návratovú hodnotu (tou je aktuálny počet referencií), uvoľniť zámok a vrátiť počet referencií na stránku. Vrátenú hodnotu vhodne využijeme vo funkcii kfree.

Funkcia inc_ref je jednoduchšia: po získaní zámku iba zvýši hodnotu počítadla referencií pre fyzickú stránku pa. Treba si uvedomiť, že takto napísaná funkcia v sebe obsahuje minimálne jednu nedotiahnutú vec — a tou je pretečenie počítadla referencií. Podobne ako v prípade funkcie dec_ref kontrolujeme validitu volania vzhľadom na hodnotu počítadla, tak aj v tejto funkcii treba doplniť kontrolu validity volania vzhľadom na maximálny možný počet referencií. Táto hodnota ovšem závisí od typu, ktorý používame na počítanie. V tomto návode pole cntref obsahuje prvky typu uint64, takže podľa toho treba zvážiť implementáciu testu. Ovšem, najlepšie by bolo test napísať dostatočne univerzálne na to, aby sa pri preklade vždy určila správna maximálna hodnota podľa použitéhoh typu prvkov poľa cntref.

Použitie referencií v kalloc a kfree

Vo funkcii kfree je použitie dec_ref triviálne:

Tieto dva riadky kódu treba pridať do tela funkcie pred volanie funkcie, ktorá premazáva obsah stránky! Ako funguje uvoľňovanie stránky? Najprv sa skontroluje platnosť argumentu (fyzická adresa musí byť zarovnaná na stránku, musí byť v správnom rozsahu fyzických adries), potom sa zavolá dec_ref. Ak je aj po znížení počtu referencií hodnota počítadla pre stránku väčšia než nula, znamená to, že ešte jestvuje mapovanie tejto stránky do nejakého (resp. viacerých) virtuálneho priestoru. Preto funkcia kfree nebude pokračovať vo fyzickom uvoľnení rámca a jeho zaradení do zoznamu voľných, ale skončí beh. Nejedná sa o chybu, presne takéto správanie očakávame.

Všimnime si, že volanie dec_ref je vykonané ešte predtým, než kfree zamkne zámok. Môže prísť ku chybe a nekonzistencii údajov? Nemalo by byť volanie dec_ref vnorené do oblasti uzamknutia (samozrejme v tom prípade by sme dec_ref modifikovali tak, aby sa v nej neuzamykal zámok)? Skúste sa nad tým zamyslieť…

Vyššie uvedený výsek kódu už patrí do funkcie kalloc. Po alokovaní novej stránky sa zvýši počet referencií na ňu. Ovšem, je tu jeden chytáčik a problémik. Ak svoj kód upravíte podľa tohto vzoru, po spustení a vyvolaní funkcie kalloc príde na mieste vykonávania inc_ref ku uviaznutiua (angl. deadlock). Funkciu inc_ref sme napísali totiž tak, že sa pokúša získať zámok kmem.lock. Ten však už vlastní funkcia kalloc, preto funkcia inc_ref musí čakať na uvoľnenie zámku. Ten sa však neuvoľní, pokým neskončí funkcia inc_ref, pretože až po jej návrate do funkcie kalloc prichádza k uvoľneniu zámku. Takýto návrh nie je dobrý.

Nakoľko už dopredu vieme, že funkciu inc_ref budeme potrebovať v tej podobe, v akej je (t.j. so zámkom), musíme nahradiť volanie inc_ref vo funkcii kalloc kódom funkcie inc_ref bez použitia zámkov. Na tento účel môžeme vytvoriť novú funkciu:

A namiesto volania inc_ref vo funkcii kalloc použijeme inc_ref_internal. Pozorný čitateľ si istotne všimol, že funkcia inc_ref_internal má jediný výkonný riadok kódu. Preto by si nejaký chytrolín povedal, že na jeden riadok kódu predsa nepotrebujeme volať funkciu, a rovno nahradí volanie inc_ref riadkom kmem.cntref[PA2IND(pa)]++. Áno, fungovalo by to. Ale v reálnom projekte života môže takéto zjednodušenie priniesť trápenie a žiaľ, ak by prišlo k rozširovaniu projektu o novú funkcionalitu. V prípade použitia funkcie stačí zmeniť jej telo, kdežto pri zjednodušenom prístupe bez funkcie je nutné prehľadávať kód, kde všade sa používa pole cntref. Prístup, ktorý používa na obalenie ADT (abstract data type) funkcie, sa pri programovaní bežne využíva, pretože uľahčuje modifikáciu a rozvíjanie kódu.

Použitie počítania referencií vo zvyšku kódu xv6

V prvom rade je potrebné odkomentovať všetky testy v súbore user/cowtest.c (ak sme niektoré zakomentovali). Taktiež je potrebné dať do pôvodného stavu hack použitý pre vývoj prvej časti (na prednáške, v súbore kernel/vm.c viď funkcia uvmunmap)

Potom prezrieme funkcie uvmcopy a uvmcow, a skúsime sa zamyslieť, čo a ako.

Funkcia uvmcopy je volaná pri systémovom volaní fork, ktoré duplikuje rodiča. Ak si spomenieme na prednášku, vynorí sa nám v mysli obraz počmáranej tabule, na ktorej sa prednášajúci snažil znázorniť, čo duplikovanie rodičovského virtuálneho adresného priestoru znamená: nealokuje sa žiadna fyzická pamäť (okrem pamäte nutnej na vytvorenie tabuľky stránok v detskom procese); záznamy v tabuľke stránok detského procesu ukazujú na fyzickú pamäť, ktorú používa rodič. Keďže zatiaľ jediným miestom, kde sa zvyšuje počet referencií na stránku, je funkcia kalloc, a tá sa pri uvmcopy nevolá, musíme si rozmyslieť, kde do funkcie uvmcopy dáme volanie inc_ref tak, aby sme vhodne upravili počet referencií fyzických stránok.

Letmým pohľadom na funkciu uvmcopy určíme, že najlepšie miesto je pred zavolaním funkcie mappages. Letmý pohľad však, žiaľ, nestačí. Čo ak volanie mappages zlyhá? Fyzická stránka, ktorá sa mala mapovať na nejakú virtuálnu adresu (mapovanie zlyhalo) bude mať zvýšený počet referencií, čo nebude v súlade so skutočnosťou (keďže sa mapovanie nepodarilo, tak počet referencií bude o 1 vyšší ako má byť). Treba to urobiť lepšie…

Vo funkcii uvmcow pred vytvorením nového mapovania (pomocou mappages) musíme pôvodné mapovanie najprv zrušiť pomocou volania funkcie uvmunmap (na prednáške bolo vysvetlené prečo). Nakoľko sme doteraz nemali implementované počítadlo referencií, tak sme ako posledný argument funkcie uvmunmap mali hodnotu 0. Ak chceme správne využívať počítadlo refrencií (a uvoľnenie pamäte, ak počet klesne na 0), musíme sa zamyslieť, či a ako zmeniť túto hodnotu.

Po upravení uvmcopy a uvmcow je potrebné (podľa zadania) upraviť ešte funkciu copyout:

Kto by nevedel, kam umiestniť tento pseudokód: ešte pred volanie walkaddr.

Bude to fungovať?

Nie, nebude!

Prečo?

Z pedagogických dôvodov sme neuviedli všetky kontroly rozsahov adries (napr. vo funkciách uvmcow, copyout), ktoré je potrebné implementovať, aby boli všetky testy programu usertests úspešné.

Veľa zdaru!

Príloha

Implementácia prvej časti labu robená na prednáške.