Na minulom cvičení ste napísali niekoľko utilít, ktoré používali systémové volania. V tomto cvičení napíšete dvojicu systémových volaní, ktoré vám pomôžu lepšie pochopiť interné mechanizmy jadra xv6 a ako samotné systémové volania fungujú. S pridávaním systémových volaní sa stretnete aj v niektorých ďalších cvičeniach.
Než začnete programovať, preštudujte si kapitolu 2 z xv6 knižky, sekcie 4.3 a 4.4 kapitoly 4 a príslušné zdrojové súbory.
Na začiatok sa prepnite do vetvy syscall:
1 2 |
$ git fetch $ git switch syscall # alebo git checkout syscall |
Riešenia úloh z tohto cvičenia ukladajte do vetvy syscall.
Ak spustíte make grade, zistíte, že hodnotiaci skript nemôže spustiť (execnúť) trace and sysinfotest. Vašou úlohou je pridať potrebné systémové volania a podporný kód, aby testy prešli.
Vo väčšine prípadov si vystačite s ladením prostredníctvom výpisov, ale niekedy budete potrebovať krokovať kód po inštrukciách alebo pozrieť hodnoty premenných na zásobníku. Na toto a iné činnosti súvisiace s ladením kódu budeme používať nástroj gdb. Predtým, ako sa pustíte do riešení ďalších úloh si vyskúšajte prácu s GDB. V koreňovom priečinku xv6 spustite príkaz make qemu-gdb a v druhom okne/karte spustite gdb-multiarch (alebo riscv64-unknown-elf-gdb, prípadne iný príkaz pre spustenie gdb pre architektúru RISC-V) (bližšie informácie si prečítajte na stránke s pomôckami). Keď máte otvorené dve okná/karty, napíšte do okna s gbd:
1 2 3 4 5 6 7 8 9 10 |
(gdb) b syscall Breakpoint 1 at 0x80002142: file kernel/syscall.c, line 243. (gdb) c Continuing. [Switching to Thread 1.2] Thread 2 hit Breakpoint 1, syscall () at kernel/syscall.c:243 243 { (gdb) layout src (gdb) backtrace |
Príkazom layout src alebo stlačením skratky Ctrl-x a rozdelíte okno na dve polovice, kde budete vidieť aktuálnu pozíciu v zdrojovom kóde. Príkazom backtrace vypíšete funkcie na zásobníku. Pozrite si prezentáciu Používanie gdb (EN) od našich kolegov z MIT pre ďalšie užitočné príkazy GDB.
Odpovedajte na tieto otázky (odpovede uveďte do súboru answers-syscall.txt, pozn. odpovede nebudeme kontrolovať):
syscall
?
Zadajte dvakrát príkaz n, aby ste prešli za riadok struct proc *p = myproc();
. Zadajte print /x *p, čím vypíšete obsah štruktúry struct proc
aktuálneho procesu (viď kernel/proc.h) v hexadecimálnej forme.
p->trapframe->a7
a čo reprezentuje? (Pomôcka: pozrite sa do user/initcode.S na prvý program, ktorý xv6 spustí.)
Procesor beží v móde jadra, takže môžeme vypísať hodnoty privilegovaných registrov, ako napríklad sstatus
(viď privilegované inštrukcie RISC-V pre bližší popis):
1 |
(gdb) print /x $sstatus |
V ďalších častiach tohto cvičenia (a aj v nasledujúcich cvičeniach) sa môžete stretnúť s programátorskou chybou, v ktorej xv6 vyvolá kernel panic. Skúsme takúto chybu zámerne vyvolať. Najprv ukončite gdb príkazom quit a na prvej karte nezabudnite ukončiť bežiace qemu. Nahraďte výraz num = p->trapframe->a7;
výrazom num = * (int *) 0;
na začiatku funkcie syscall
, zavolajte make qemu, a uvidíte:
1 2 3 4 5 6 7 |
xv6 kernel is booting hart 2 starting hart 1 starting scause 0x000000000000000d sepc=0x000000008000215a stval=0x0000000000000000 panic: kerneltrap |
Ukončite qemu.
Aby ste zistili miesto v kóde, kde panic nastal, hľadajte vypísanú hodnotu registra sepc
v súbore kernel/kernel.asm, ktorý obsahuje inštrukcie v jazyku assembly pre skompilované jadro.
num
?
Aby ste mohli skontrolovať stav procesora a jadra počas zlyhania vykonania inštrukcie, spustite gdb a nastavte breakpoint na adresu epc zlyhanej inštrukcie (adresa inštrukcie sa vo vašom prípade môže líšiť):
1 2 3 4 5 6 7 8 |
(gdb) b *0x000000008000215a Breakpoint 1 at 0x8000215a: file kernel/syscall.c, line 247. (gdb) layout asm (gdb) c Continuing. [Switching to Thread 1.3] Thread 3 hit Breakpoint 1, syscall () at kernel/syscall.c:247 |
Príkazom layout asm prepnete horné okno do zobrazenia inštrukcií jazyka assembly. Skontrolujte, či sa zlyhávajúca inštrukcia zhoduje s inštrukciou, ktorý ste našli v kernel/kernel.asm.
scause
vyššie? (Pozrite si popis registra scause
v príručke privilegovaných inštrukcií architektúry RISC-V).
Aj keď pri panicu vypíše jadro kód scause, často sa musíte pozrieť na ďalšie informácie, aby ste zistili presný problém, ktorý spôsobil panic. Napríklad, aby ste zistili, ktorý proces bežal keď nastal panic, môžete si vypísať jeho meno:
1 |
(gdb) print p->name |
Týmto končíme krátky úvod do hľadania chýb pomocou gdb. Podľa potreby sa počas riešenia cvičení môžete vrátiť k prezentácii Používanie gdb (EN). Na stránke s pomôckami nájdete ďalšie užitočné tipy k ladeniu systému.
trace()
, ktorým bude možné riadiť trasovanie. Bude mať jeden argument, celočíselnú „masku“, ktorej bity určia, ktoré systémové volania budú trasované. Napríklad, na trasovanie systémového volania fork()
program zavolá trace(1 << SYS_fork)
, kde SYS_fork
je číslo systémového volania definované v kernel/syscall.h. Vaším cieľom je modifikovať jadro xv6 tak, aby tesne pred ukončením systémového volania jadro skontrolovalo, či je v maske nastavené číslo prebiehajúceho systémového volania. V takom prípade jadro vypíše riadok s id procesu, názvom systémového volania a jeho návratovou hodnotou. Systémové volanie trace()
by malo povoliť trasovanie pre proces, ktorý ho zavolal a pre jeho detské procesy, ktoré neskôr vytvorí zavolaním fork()
, ale nie pre ostatné procesy.
V repozitári nájdete užívateľský program trace, ktorý spustí iný program so zapnutým trasovaním (viď user/trace.c). Ak ste volanie správne naimplementovali, mali by ste vidieť niečo takéto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
$ trace 32 grep hello README 3: syscall read -> 1023 3: syscall read -> 966 3: syscall read -> 70 3: syscall read -> 0 $ $ trace 2147483647 grep hello README 4: syscall trace -> 0 4: syscall exec -> 3 4: syscall open -> 3 4: syscall read -> 1023 4: syscall read -> 966 4: syscall read -> 70 4: syscall read -> 0 4: syscall close -> 0 $ $ grep hello README $ $ trace 2 usertests forkforkfork usertests starting test forkforkfork: 407: syscall fork -> 408 408: syscall fork -> 409 409: syscall fork -> 410 410: syscall fork -> 411 409: syscall fork -> 412 410: syscall fork -> 413 409: syscall fork -> 414 411: syscall fork -> 415 ... $ |
V prvom príklade trace spustí trasovanie programu grep, konkrétne systémového volania read. Číslo 1<<SYS_read
je rovné 32. V druhom príklade sa jedná o ten istý proces, ale trasujú sa všetky systémové volania. Číslo 2147483647 má nastavených všetkých 31 dolných bitov. V treťom príklade program nie je trasovaný, takže nie je ani vypísaný žiadny výstup. V štvrtom príklade sú trasovaní všetci potomkovia testu forkforkfork zo sady testov usertests. Vaše riešenie je korektné, ak program má rovnaké správanie ako je uvedené vyššie (ID procesov môžu byť rozdielne).
Pomôcky:
Pridajte $U/_trace
do premennej UPROGS
v súbore Makefile.
Spustite make qemu a uvidíte, že kompilátor nemôže skompilovať súbor user/trace.c, pretože užívateľské útržky kódu pre systémové volanie ešte neexistujú: pridajte prototyp systémového volania do user/user.h, útržok do user/usys.pl, a číslo systémového volania do kernel/syscall.h. Súbor Makefile spustí perl skript user/usys.pl, ktorý vytvorí user/usys.S, kde sú uložené skutočné útržky systémových volaní, ktoré obsahujú inštrukciu ecall
architektúry RISC-V, ktorá slúži na prechod do jadra. Potom, ako opravíte kompilačné problémy, spustite trace 32 grep hello README. Uvidíte, že zlyhá, lebo samotné systémové volanie v jadre ešte nie je implementované.
Pridajte funkciu sys_trace()
do kernel/sysproc.c. Jej úloha je uložiť argument systémového volania do novej premennej v štruktúre proc
(viď kernel/proc.h). Funkcie na prevzatie argumentov systémového volania nájdete v kernel/syscall.c a príklady ich použitia nájdete v kernel/sysproc.c.
Upravte fork()
(viď kernel/proc.c), aby skopíroval trasovaciu masku z rodiča do detského procesu.
Upravte funkciu syscall()
v kernel/syscall.c, aby vypísala trasovací výstup. Musíte pridať pole názvov systémových volaní, ktoré sa budú vypisovať.
Po úspešnom dokončení úlohy nezabudnite vytvoriť commit!
sysinfo()
, ktoré zbiera informácie o bežiacom systéme. Má jeden argument: smerník na štruktúru struct sysinfo
(viď kernel/sysinfo.h). Jadro by malo naplniť položky tejto štruktúry: položka freemem
by mala obsahovať počet bajtov voľnej pamäte a položka nproc
by mala obsahovať počet procesov, ktorých state
nie je UNUSED
. Úlohu ste úspešne zvládli ak testovací program sysinfotest vypíše sysinfotest: OK.
Pomôcky:
Pridajte $U/_sysinfotest
do premennej UPROGS
v súbore Makefile.
Po spustení make qemu zlyhá kompilácia súboru user/sysinfotest.c. Pridajte systémové volanie sysinfo
podobným postupom ako v predchádzajúcej úlohe. Aby ste mohli deklarovať prototyp sysinfo()
v user/user.h, musíte deklarovať existenciu štruktúry struct sysinfo
:
1 2 |
struct sysinfo; int sysinfo(struct sysinfo *); |
Keď opravíte problémy s kompiláciou, spustite sysinfotest. Zlyhá, pretože ste ešte neimplementovali systémové volanie v jadre.
sysinfo
musí skopírovať štruktúru struct sysinfo
do užívateľského priestoru. Pozrite sa ako na to vo funkciách sys_fstat()
(kernel/sysfile.c) a filestat()
(kernel/file.c). Bude vás zaujímať funkcia copyout()
. Táto funkcia má štyri argumenty: tabuľku stránok procesu (podľa ktorej určí adresu, kam má štruktúru uložiť); cieľovú adresu, ktorú si vyžiadal užívateľský proces; zdrojovú adresu objektu v jadre a počet bajtov, ktoré sa budú kopírovať.
Aby ste získali množstvo voľnej pamäte, pridajte funkciu do kernel/kalloc.c.
Na získanie počtu procesov pridajte funkciu do kernel/proc.c.
Po úspešnom dokončení úlohy nezabudnite vytvoriť commit!
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.