====== NOVA – Systémová volání a správa paměti ====== Na tomto cvičení se seznámíte s jádrem miniaturního OS NOVA a implementujete do něj systémové volání ''brk''. [[http://hypervisor.org/|NOVA je mikrohypervizor]] původně vyvíjený na [[http://os.inf.tu-dresden.de/|TU Dresden]], později ve firmě Intel a nyní převážně projektem [[https://genode.org/|GENODE]]. Na cvičeních však nebudete pracovat s kompletní verzí jádra NOVA, ale se zjednodušenou verzí pro výuku, která má pouze 2 tisíce řádek kódu. ===== Domácí příprava ===== Pro toto cvičení budete potřebovat následující: * aspoň trochu rozumět kódu v C++ a [[https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html|inline assembleru]] (viz [[courses:b4b35osy:cviceni:cviceni8_asm-syscalls|8. cvičení]]) * vědět, co to jsou [[https://en.wikipedia.org/wiki/System_call|systémová volání]] a proč/k čemu se používají (viz přednášky) * popis instrukce [[http://x86.renejeschke.de/html/file_module_x86_id_313.html|sysenter]] * vědět, co dělá systémové volání [[https://linux.die.net/man/2/brk|brk]] * rozumět rozdílu mezi [[https://blog.codinghorror.com/understanding-user-and-kernel-mode/|uživatelským prostorem a prostorem jádra]] (viz přednášky) * vědět, jak CPU překládá virtuální adresy na fyzické (i386 používá dvoustupňové mapování 10-10-12) * viz přednášky, APO, https://www.youtube.com/watch?v=KNUJhZCQZ9c, případně další * {{:courses:b4b35osy:cviceni:nova.zip|stáhnout}}, rozbalit a ideálně i vyzkoušet (make run) operační systém NOVA * na vlastním pc budete potřeboval balíčky:libc6-dev-i386, qemu-system-i386 * přečíst hlavní Makefile * přečíst funkci ''Ec::syscall_handler'' * přečíst funkci ''Ptab::insert_mapping'' (byla na přednáškách) * podívat se na třídu ''Kalloc'' ===== Zadání úlohy ===== Implementujte systémové volání ''brk'' s prototypem: void *brk(void *address) Toto systémové volání nastaví konec adresního prostoru procesu (tzv. program break) na danou adresu (parametr ''address''). Tím se zvětší nebo zmenší množství alokované paměti, které může program využívat ke svému běhu. Program break je první adresa za koncem namapovaného virtuálního adresního prostoru. Vaše řešení by mělo splňovat následující požadavky: * Po úspěšném návratu ze systémového volání je program break nastaven na hodnotu ''address''. To znamená, že uživatelský program může používat paměť od adresy 0x1000 do ''address-1''. Přístup na stránky začínající na adrese vyšší či rovné ''address'' nebude aplikaci dovolen. * Program break nesmí jít nastavit na nižší hodnotu, než je jeho hodnota při spuštění programu. Tím by se program připravil o část svého kódu, dat nebo zásobníku. * Program break nesmí jít nastavit na vyšší hodnotu než 0xC0000000. Tím by aplikace mohla přepsat jádro. * Při úspěšném dokončení je vrácena původní hodnota program break před vykonáním systémového volání. Při chybě je vrácena hodnota 0. * Pokud je address NULL (0), nejedná se o chybu a hodnota program break se nemění. * Při žádném volání ''brk'' nesmí dojít k "pádu" systému. * ABI systémového volání bude následující. Vstup: AL=3, ESI=address. Výstup: EAX=návratová hodnota. * Nově alokovaná paměť bude inicializována na nulu. * Při snižování hodnoty program break bude nepřístupná paměť dealokována, aby mohla být opět alokována později. * Při chybě alokace paměti v průběhu systémového volání nebude adresní prostor uživatelské aplikace změněn a částečně alokovaná paměť bude dealokována. * Při kompilaci nevypisuje kompilátor žádná varování. Odevzdává se archiv se souborem ''ec_syscall.cc'' obsahující vaši implementaci, ideálně vytvořený pomocí make hw10 ===== Nápověda ===== * Operační systém NOVA s testovací aplikací ''user/hello.c'' můžete nabootovat buď na fyzickém počítači pomocí zavaděče podporujícího {{https://www.gnu.org/software/grub/manual/multiboot/multiboot.html|multiboot}} (např. {{https://www.gnu.org/software/grub/|GRUB 2}}), ale pravděpodobně bude efektivnější ho pouštět jako virtuální stroj například v emulátoru Qemu. Pro to stačí spustit make run * Při vývoji operačního systému nelze používat debugger tak, jak jste zvyklí při vývoji aplikací. Ladit váš kód můžete buď přidáváním příkazů ''printf()'' na potřebná místa v kódu. Pokud vám to nestačí můžete použít parametr ''-gdb'' emulátoru Qemu, ale to je trochu komplikovanější. * Při zvětšování program break musíte alokovat paměť v jádře a namapovat ji do adresního prostoru uživatelské aplikace modifikováním stránkovacích tabulek. Inspirací vám může být funkce ''Ec::root_invoke()'', která připravuje paměť pro spouštěný program. Funkce čte hlavičky z binárky aplikace, které si můžete zobrazit příkazem ''readelf --program-headers hello''. * Při snižování program break naopak musíte mapování zrušit a paměť dealokovat. * Při startu uživatelského programu (např. hello) je jeho paměťová mapa následující (trochu zjednodušeno): * 0x00001000 – 0x00001fff – zásobník (4 kB), počáteční hodnota registru ESP je 0x2000. * 0x00002000 — 0xXXXXX000-1 — data programu (viz .data ve výstupu příkazu ''readelf --sections hello'') * 0xXXXXX000 – 0xYYYYY000-1 — kód programu (viz .text ve výstupu příkazu ''readelf --sections hello'') * 0xYYYYY000 — program break * Ke kódu jádra NOVA není žádná dokumentace, ale části, které budete potřebovat, jsou tak jednoduché, že byste měli být schopni jim porozumět na základě čtení kódu (a komentářů). Pokud však i přes veškerou vaší snahu něčemu nerozumíte, ptejte se na {{https://cw.felk.cvut.cz/forum/forum-1418.html|fóru}}.