Warning
This page is located in archive. Go to the latest version of this course pages.

Cvičení 10 : NOVA – Systémová volání a správa paměti

3. 12. 2019: Zveřejnili jsme testovací programy, které vám snad usnadní testování vašich implementací.

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. NOVA je mikrohypervizor původně vyvíjený na Drážďanské univerzitě, později ve firmě Intel a nyní firmami GENODE labs a Cyberus Technology. 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í:

  • vědět, co to jsou systémová volání a proč/k čemu se používají (viz přednášky)
  • popis instrukce sysenter
  • vědět, co dělá systémové volání brk v Linuxu
  • rozumět rozdílu mezi 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)
  • 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
    • při práci přes vzdálený přístup použijte
      ssh -X «login»@postel.felk.cvut.cz
      přepínač -X umožní spouštět i grafické aplikace (QEMU)

Zadání úlohy

Implementujte systémové volání brk s prototypem:

void *brk(void *address)

Toto systémové volání nastaví konec datového segmentu v adresním prostoru procesu (tzv. program break nebo jen break) na adresu danou parametrem address. Tím se zvětší nebo zmenší množství alokované paměti, které může program využívat ke svému běhu. Break je první adresa za koncem namapovaného datového segmentu.

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 break nastaven na hodnotu address. To znamená, že uživatelský program může používat paměť od adresy 0x1000 do o jedna menší než address. Přístup na stránky začínající na adrese vyšší či rovné address nebude programu dovolen.
  • 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.
  • Break nesmí jít nastavit na vyšší hodnotu než 0xC0000000. Tím by aplikace mohla přepsat jádro, které je namapováno od této adresy výš.
  • Při úspěšném dokončení je vrácena původní hodnota break před vykonáním systémového volání. Při chybě je vrácena hodnota 0.
  • Pokud je address rovno NULL (0), nejedná se o chybu a hodnota break se nemění. Toto volání slouží pouze ke zjištění aktuální hodnoty break.
  • 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 break bude nepřístupná paměť dealokována (a odmapová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 specifikaci multiboot (např. 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 příkaz
    make run
    .
  • Při zvětšování hodnoty program break musíte v jádře alokovat paměť 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.
    • Na konci funkce nastaví proměnné Ec::break_min a Ec::break_current, které pravděpodobně budete potřebovat ve své implementaci.
  • Při snižování hodnoty 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 fóru.

Ladění

Při vývoji operačního systému nelze používat debugger tak jednoduše, jak jste zvyklí při vývoji aplikací. Ladit váš kód můžete 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 (případně zkratku -s) emulátoru Qemu.

Abychom vám ladění usnadnili, v hlavním Makefile jsou připravena pravidla jak pro spouštění Qemu se zmiňovanými parametry, tak pro spouštění debuggeru gdb tak, aby šel ladit kód běžící v Qemu:

  • V jednom okně spusťte příkaz make rd, který spustí Qemu, které po startu počká na připojení debuggeru.
  • V druhém okně pak spusťte make du či make dk podle toho, jestli chcete ladit uživatelský program (hello) nebo jádro. Můžete v Makefile změnit argument příkazu break tak, aby se vykonávání programu zastavilo na funkci (či řádku), který potřebujete odladit.

Užitečné příkazy gdb (většinou se dají zkrátit na první znak):

  • Běh programu: next (n), step (s), continue ©
  • Výpis proměnných: print (např. p initialized_var nebo hexadecimálně: p/x initialized_var)
  • Výpis paměti: x (např: x/16 0x2000 vypíše 16 slov od adresy 0x200, x/16 &initialized_var vypíše 16 slov začínající na adrese proměnné initialized_var.
  • Informace: info registers vypíše hodnoty registrů, info locals vypíše hodnoty lokálních proměnných atd.
  • Zobrazení: layout src zobrazí zdrojový kód i příkazové okno, Ctrl-L překreslí obrazovku, tui disable vypne zobrazování zdrojového kódu.

Pro ladění můžete používat i monitorovací konzoli Qemu, která umožňuje zobrazit stav emulovaného CPU.

  • Do konzole se přepnete stiskem Ctrl-Alt-2 v grafickém okně (nebo stiskem Ctrl-a c pokud používáte přepínač -nographic)
  • Příkazy info mem nebo info tlb vypíší informace o mapování virtuálních adres na fyzické (tj. stránkovací tabulku).
  • info registers vypíše hodnoty registrů

Příklad

  • Předpokládejme, že break_start je na začátku Vašeho procesu nastaven na 0x5000.
  • Při volání funkce break(0x9000) dojde k alokování stránek 0x5000-0x5FFF, 0x6000-0x6FFF, 0x7000-0x7FFF a 0x8000-0x8FFF.
  • Při dalším volání break(0x7555) dojde k uvolnění stránky 0x8000-0x8FFF.
  • Při dalším volání break(0x7666) se neprovádí žádná alokace, ale měla by se vynulovat paměť 0x7555-0x7665.
  • Při dalším volání break(0xA000) se alokuje nová stránka pro 0x8000-0x8FFF a 0x9000-0x9FFF, a vynuluje se paměť 0x7666-0x7FFF (předpokládáme, že nově alokované stránky jsou už vynulované).

Materiály

courses/b4b35osy/cviceni/cviceni10_brk.txt · Last modified: 2019/12/03 13:14 by sojkam1