Zadani 3. semestralni prace: ------------------------------------------------- Cilem 3. semestralni prace je rozsirit jadro z 2. semestralni prace o podporu behu uzivatelskych procesu. Bude nutne vytvorit infrastrukturu pro vytvareni a beh uzivatelskych procesu a pro systemova volani, ktera budou zpristupnovat relevantni funkce jadra uzivatelskym procesum. Cast teto infrastruktury bude nutne implementovat v uzivatelskem rezimu, kde je potreba procesum poskytnout zakladni prostredi pro beh. Zadani ma formu rozhrani, ktere je nutno naimplementovat. Toto rozhrani je urceno pro uzivatelske procesy, na rozhrani infrastruktury v jadre nejsou kladeny formalni pozadavky s vyjimkou obecnych pozadavku na uroven kodu a systematicnost. Implementace rozhrani musi projit sadou testu, ktere overi jeji zakladni funkcnost. Systemova volani nutna pro realizace uvedeneho rozhrani nejsou predmetem zadani, nicmene je nutne je zdokumentovat. Pokud zadani nespecifikuje nejaky detail, je zavazne chovani, ktere ocekavaji testy. Pokud testy dane chovani netestuji, zadani si podle uvazeni dodefinujte a sve rozhodnuti zdokumentujte. Obecne pozadavky tykajici se deklarace a pouzivani chybovych kodu, definice zakladnich typu pouzivanych v zadani a pozadavky na formu zdrojovych textu jsou shodne se zadanim 1. semestralni prace. Zakladni zadani ------------------------------------------------------------ Rozhrani systemovych volani --------------------------- Naimplementujte obecny mechanismus systemovych volani z uzivatelskeho prostoru do kernelu, ktery umozni volat na strane uzivatelskeho prostoru konkretni sluzby kernelu a predavat jim argumenty a na strane kernelu psat obsluzne rutiny techto sluzeb. Pri implementaci je kladen duraz na rozumnou rozsiritelnost o nove sluzby a jejich obsluzne rutiny a dostatecne oddeleni abstrakce (uzivatelske funkce nebudou predstavovat primo systemova volani, ale jen jejich wrappery, kernelove obsluzne rutiny nebudou primo soucasti obsluzne rutiny vyjimky, ale budou take oddeleny vhodnou strukturou). Respektujte pravidlo, ze systemova volani by se mela pouzivat jen na ty sluzby, ktere nelze snadno implementovat primo v uzivatelskem prostoru, kernel nelze chapat jako "sdilenou knihovnu", kterou pouzivaji uzivatelske procesy pro bezne funkce. Kernelove obsluzne rutiny systemovych volani musi dusledne testovat spravnost a konzistenci argumentu predanych z uzivatelskeho prostoru (zda napriklad ukazalate ukazuji na korektne namapovanou pamet uzivatelskeho procesu a tudiz pri praci s uzivatelskou pameti v kernelu nemuze vzniknout vyjimka apod.). Na nespravne nebo nekonzistentni argumenty by mel kernel reagovat chybovou navratovou hodnotou nebo ukoncenim procesu, nikoliv zastavenim vlastniho behu. Podpora uzivatelskeho procesu ----------------------------- Po uspesne inicializaci jadra dojde ke spusteni jedineho uzivatelskeho procesu, jehoz binarni obraz bude nahran spolecne s kernelem a dalsimi potrebnymi daty primo do pameti simulatoru. Tento uzivatelsky proces bude vyuzivat behovou infrastrukturu prostrednictvim staticky linkovane knihovny. Vytvorte ukazkovy uzivatelsky proces, ktery bude ve vice uzivatelskych vlaknech demonstrovat vystup na konzoli a synchronizaci. Zakladni vstupne/vystupni operace --------------------------------- * size_t putc(const char chr) Funkce vypise znak na konzoli. Vraci pocet vypsanych znaku. * size_t puts(const char *str) Funkce vypise retezec na konzoli. Vraci pocet vypsanych znaku. * size_t printf(const char *format, ...) Funkce vypise formatovany retezec na konzoli, stejne jako v knihovne libc. Formatovaci kody by mely podporovat znaky (%c), retezce (%s), cela cisla v desitkove (%d, %u, %i) a v sestnactkove (%x) soustave, ukazatele (%p). Neni nutne implementovat modifikatory pro zarovnavani a platne cislice. Funkce vraci pocet vypsanych znaku. * char getc(void) Funkce precte a vrati znak z klavesnice. Pokud neni ve vyrovnavaci pameti klavesnice zadny znak, zablokuje volajici vlakno a ceka na stisk klavesy. Cekani na znak by melo byt pasivni, aktivni polling je velmi nevhodne reseni. * ssize_t gets(char *str, const size_t len) Funkce precte nejvice len - 1 znaku z klavesnice do bufferu str. Cteni je ukonceno pri precteni (a ulozeni) znaku '\n' nebo pri dosazeni limitu. Prectene znaky jsou vzdy ukonceny znakem 0. Vraci EINVAL pokud len == 0, jinak pocet znaku ulozenych do bufferu bez ukoncovaci 0. Cekani na jednotlive znaky by opet melo byt reseno pasivne. Pozn.: Za velmi nevhodnou se z implementacniho hlediska povazuje realizace vsech vyse uvedenych funkci (predevsim printf() a gets()) primo formou systemovych volani. Implementace funkci putc(), puts() a getc() jako systemova volani je zrejme korektni. Zakladni ladici prostredky -------------------------- * makro assert(EXPR) Pokud neni definovan symbol NDEBUG, makro vyhodnoti vyraz EXPR a pokud je vysledek nulovy, vypise informaci o nazvu funkce, souboru a cisle radku, na kterem nebyl splnen predpoklad EXPR, a zavola exit(). Pokud je symbol NDEBUG definovan, neudela nic. * makro dprintf(ARGS...) Pokud neni definovan symbol NDEBUG, makro vypise na konzoli informaci o nazvu funkce a cislo radku, na kterem bylo pouzito a formatovany retezec ARGS. Pokud je symbol NDEBUG definovan, neudela nic. Dynamicky alokovana pamet ------------------------- Nasledujici funkce umoznuji uzivatelskemu procesu dynamicky alokovat a uvolnovat pamet. Spravce uzivatelske pameti typicky pracuje s jednou oblasti virtualni pameti, jejiz velikost je dana pametovymi naroky procesu. Velikost teto oblasti by nemela byt fixni po celou dobu behu procesu, ale v pripade potreby a dostatku fyzicke pameti by mela rust a naopak v pripade, ze jsou na jejim konci nevyuzite stranky, by se mela zmensovat. Implementace samotneho algoritmu alokatoru uzivatelske pameti muze byt velmi jednoducha, muzete napr. prevzit a upravit puvodni alokator z Kalista. * void *malloc(const size_t size) Funkce alokuje blok pameti pozadovane velikosti. Vraci NULL pokud nebylo mozne blok alokovat, jinak ukazatel na zacatek alokovaneho bloku. * void free(const void *ptr) Funkce uvolni blok pameti, na ktery ukazuje ptr. Rozhrani pro praci s vlakny --------------------------- Rozhrani pro praci s vlakny vychazi ze specifikace POSIX, je vsak misty velmi zjednodusene, popr. obsahuje nektere funkce, ktere puvodni POSIX specifikace neobsahuje. * int thread_create( thread_t *thread_ptr, void *(*thread_start)(void *), void *data) Funkce vytvori nove (attached) vlakno. Vraci chybovy kod, pokud se vlakno nepodari vytvorit, jinak EOK. Funkce @thread_start bude spustena v nove vytvorenem vlakne. Pocet vlaken v systemu je omezen pouze dostupnou pameti. Je-li @thread_ptr platny ukazatel, zapise do nej identifikaci noveho vlakna. * thread_t thread_self(void) Funkce vrati identifikator prave beziciho vlakna. * int thread_join(thread_t thr, void **thread_retval) * int thread_join_timeout( thread_t thr, void **thread_retval, const unsigned int usec) Funkce zablokuje volajici vlakno dokud vlakno @thread neskonci. Pokud je @thread_retval platny ukazatel, zapise ukazatel na navratovou hodnotu vlakna na misto odkazovane @thread_retval. Vraci EINVAL pokud je identifikace vlakna neplatna, pokud bylo vlakno odpojeno pomoci thread_detach(), pokud jiz na vlakno ceka nekdo jiny, nebo pokud vlakno vola funkci samo na sebe. Varianta _timeout ceka na ukonceni vlakna nejdele usec mikrosekund, pokud vlakno neni ukonceno do uplynuti teto doby, vraci ETIMEDOUT. * int thread_detach(thread_t thr) Funkce odpoji zadane vlakno. Vraci EINVAL pokud je identifikace vlakna neplatna, vlakno je jiz odpojene, vlakno jiz nebezi a ceka na join nebo pokud na vlakno jiz nekdo ceka v thread_join(). Odpojene (detached) vlakno se od pripojeneho (attached) vlakna lisi tim, ze neni mozne cekat na jeho ukonceni. Pri dobehnuti odpojeneho vlakna je automaticky uvolnena pamet alokovana pro vlakno. * int thread_cancel(thread_t thr) Funkce zrusi zadane (bezici) vlakno. Vlakno, ktere ceka na ukonceni ruseneho vlakna, je odblokovano. Funkce vraci EINVAL pokud je identifikace vlakna neplatna, jinak EOK. * void thread_sleep(const unsigned int sec) * void thread_usleep(const unsigned int usec) Funkce zablokuje prave bezici vlakno na zadanou dobu v sekundach (resp. mikrosekundach). Vlakno nesmi byt zablokovano na kratsi dobu nez bylo zadano, rozumne zduvodnene prodlouzeni teto doby lze tolerovat. * void thread_yield(void) Vlakno volajici thread_yield() se vzda rizeni dokud nebude znovu naplanovano k behu. * void thread_suspend(void) Vlakno volajici thread_suspend() se zablokuje, dokud jej jine vlakno neodblokuje pomoci funkce thread_wakeup(). * int thread_wakeup(thread_t thr) Funkce odblokuje zadane vlakno. Volajici vlakno pokracuje v behu, neni v dusledku tohoto volani preplanovano. Funkce vraci EINVAL pokud je identifikace vlakna neplatna, jinak EOK. * void thread_exit(void *thread_retval) Funkce ukonci provadeni volajiciho vlakna a zajisti zpristupneni ukazatele @thread_retval vlaknu volajicimu thread_join na ukoncene vlakno. * void exit(void) Funkce ukonci provadeni vsech vlaken aktualniho procesu, tedy efektivne ukonci cely proces. Pri ukoncovani nesmi dojit k deadlocku. Veskere prostedky procesu jsou uvolneny. Pokud v jadre jiz nebezi zadny uzivatelsky proces, ukonci se beh celeho jadra. Synchronizacni primitiva ------------------------ Stejne jako rozhrani pro vlakna vychazi rovnez rozhrani pro praci se synchronizacnimi primitivy ze specifikace POSIX, je vsak zjednodusene a v navaznosti na predchozi semestralni prace pouziva odlisne nazvy funkci a jine typy pro synchronizacni primitiva. - mutex (binarni semafor) * int mutex_init(struct mutex *mtx) Funkce inicializuje mutex do stavu odemceno. Pokud inicializace probehne v poradku, vraci EOK, v opacnem pripade ENOMEM/EINVAL podle charakteru chyby. * int mutex_destroy(struct mutex *mtx) Funkce provede uklid ridici struktury mutexu. Pokud je rusen mutex, na kterem jsou jeste zablokovana nejaka vlakna, ukonci se aktualni vlakno. Pokud je identifikator mutexu neplatny, funkce vraci EINVAL. Probehne-li operace v poradku, funkce vraci EOK. * int mutex_lock(struct mutex *mtx) * int mutex_lock_timeout (struct mutex *mtx, const unsigned int usec) Funkce se pokusi zamknout mutex, pokud to neni mozne, zablokuje vlakno na mutexu. Varianta _timeout ceka na zamknuti mutexu nejdele usec mikrosekund. Pokud se behem teto doby podari mutex zamknout, vraci EOK, jinak ETIMEDOUT. Pokud je casovy limit 0, k zablokovani vlakna nedochazi. Pokud je identifikator mutexu neplatny, funkce vraci EINVAL. * int mutex_unlock(struct mutex *mtx) Funkce odemkne zadany mutex a vzbudi vlakna, ktera byla zablokovana pri zamykani mutexu. Pokud je symbol DEBUG_MUTEX >= 1, bude implementace mutexu hlidat, zda je odemykan vlaknem, ktere ho puvodne zamknulo. Pokud bude mutex odemykat jine vlakno, ukonci se aktualni vlakno. Pokud je identifikator mutexu neplatny, funkce vraci EINVAL. Probehne-li operace v poradku, funkce vraci EOK. Integrace s testy ----------------- K integraci s testy vytvorite hlavickovy soubor librt.h, ktery bude obsahovat deklarace vsech vyse uvedenych funkci. Typicky bude obsahovat #include direktivy, ktere zajisti vlozeni vasich hlavickovych souboru s prislusnymi deklaracemi. Test bude dodan jako zdrojovy kod uzivatelskeho procesu. Dale je nutne pripravit knihovnu librt.a, ktera bude obsahovat kod behoveho prostredi procesu. S touto knihovnou bude slinkovan kod procesu. Start procesu vypada tak, ze jadro spusti proces od urcite adresy, kde bude typicky vstupni bod behoveho prostredi, ktery provede nezbytnou inicializaci (spravce pameti, popr. jine) a zavola main() funkci uzivatelskeho procesu. Pri navratu z funkce main() pak behove prostredi ukonci proces (tj. vsechna jeho bezici vlakna). Rozsirene zadani ----------------------------------------------------------- Diskove zarizeni ---------------- Implementujte v ramci jadra ovladac read-only diskoveho blokoveho zarizeni (s pouzitim zarizeni ddisk v MSIMu). Moznost cteni bloku dat bude vhodnym rozhranim zpristupneno uzivatelskym procesum. Rozhrani pro praci s procesy ---------------------------- Zjednodusene rozhrani v uzivatelskem prostoru, ktere umozni pracovat s uzivatelskymi procesy, jez pobezi v oddelenych adresovych prostorech. * int process_create( process_t *process_ptr, const void *img, const size_t size) Funkce vytvori novy uzivatelsky proces s nezavislym adresovym prostorem, jehoz binarni obraz velikosti @size bajtu bude nacten z adresoveho prostoru aktualniho procesu z adresy @img. Vraci chybovy kod, pokud se proces nepodari vytvorit, jinak EOK. Je-li @process_ptr platny ukazatel, zapise do nej identifikaci noveho procesu. * process_t process_self(void) Funkce vrati identifikator prave beziciho procesu. * int process_join(process_t proc) * int process_join(process_t proc, const unsigned int usec) Funkce zablokuje volajici vlakno dokud proces @proc neskonci. Vraci EINVAL pokud je identifikace procesu neplatna, pokud jiz na proces ceka nekdo jiny, nebo pokud vlakno vola funkci na svuj vlastni proces. Varianta _timeout ceka na ukonceni procesu nejdele usec mikrosekund, pokud proces neni ukoncen do uplynuti teto doby, vraci ETIMEDOUT. * int kill(process_t proc) Funkce zrusi zadany (bezici) proces. Vlakno, ktere ceka na ukonceni ruseneho procesu, je odblokovano. Funkce vraci EINVAL pokud je identifikace procesu neplatna, jinak EOK. Spousteni uzivatelskych procesu ------------------------------- V ramci zakladniho uzivatelskeho procesu (ktery bude spusten jadrem z fyzicke pameti) umoznete cist a spoustet z blokoveho zarizeni dalsi uzivatelske procesy. Na blokovem zarizeni (disku) neni nutne implementovat zadny souborovy system, postaci na nej linearne nahrat obsah binarnich obrazu jednotlivych procesu, ktere se maji spustit a vhodnou datovou strukturou na ulozenou na disku tyto procesy identifikovat. -- Posledni modifikace: 2010/10/10 19:40