Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (~/so2/linux/tools/labs
).
Pașii de rezolvare sunt următorii:
Scheletul de laborator este generat din sursele din directorul tools/labs/templates
. Putem genera scheletele pentru toate laboratoarele folosind următoarea comanda:
tools/labs $ make skels
Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu LABS
:
tools/labs $ make clean tools/labs $ LABS=<lab name> make skels
kernel_api
.
Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name>
variabilei LABS
.
tools/labs/skels
.
Comanda make build
compilează toate modulele din directorul skels
.
student@eg106:~/so2/linux/tools/labs$ make build echo "# autogenerated, do not edit " > skels/Kbuild echo "ccflags-y += -Wno-unused-function -Wno-unused-label -Wno-unused-variable " >> skels/Kbuild for i in ./kernel_api/7-list-test ./kernel_api/2-sched-spin ./kernel_api/1-mem ./kernel_api/6-list-sync ./kernel_api/5-list-full ./kernel_api/4-list ./kernel_api/3-memory; do echo "obj-m += $i/" >> skels/Kbuild; done ...
Putem copia modulele generate pe mașina virtuală folosind target-ul copy
al comenzii make, atunci când mașina virtuală este oprită.
student@eg106:~/so2/linux/tools/labs$ make copy student@eg106:~/so2/linux/tools/labs$ make boot
Alternativ, putem copia fișierele prin scp
, pentru e evita repornirea mașinii virtuale. Pentru detalii despre folosirea interacțiunea prin rețea cu mașina virtuală citiți Interacțiunea cu mașina virtuală.
Modulele generate sunt copiate pe mașina virtuală în directorul /home/root/skels/<lab_name>/<task_name>
.
root@qemux86:~/skels/kernel_api# ls 1-mem 3-memory 5-list-full 7-list-test 2-sched-spin 4-list 6-list-sync root@qemux86:~/skels/kernel_api$ ls 1-mem mem.ko
După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU (sau în minicom
) pentru a încărca și descărca modulul de kernel:
root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko
Ctrl+Shift+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/linux/tools/labs/skels
.~/so2/linux/tools/labs
.student@asgard:~$ nc -l -p 6000 -u
Identificați, folosind cscope, definițiile următoarelor simboluri:
list_head
;INIT_LIST_HEAD
;list_add
;list_for_each
;list_entry
, container_of
și offsetof
. Pentru offsetof
, găsiți varianta generică, independentă de compilator.Observați detaliile de implementare ale macro-urilor și funcțiilor.
cscope
parcurgeți secțiunea cscope din primul laborator.
Intrați în directorul 1-mem/
și parcurgeți conținutul fișierului mem.c
.
Observați folosirea apelului kmalloc
pentru alocare de memorie.
make build
și make copy
).insmod mem.ko
.dmesg
.rmmod mem
.
Intrați în directorul 2-sched-spin/
și parcurgeți conținutul fișierului sched-spin.c
. Hint: Funcția schedule_timeout, coroborată cu macro-ul set_current_state, forțează procesul curent să aștepte 5 secunde.
make build
și make copy
).insmod sched-spin.ko
.TODO 0
, astfel încât să includeți operația de sleep într-o secțiune atomică, folosind spinlock-ul deja definit. Compilați din nou codul sursă și reîncărcați modulul în kernel. Hint: spin_lock
și spin_unlock
.
Ați obținut eroare. Urmăriți stack trace-ul. Care este cauza erorii? Hint: Urmăriți, în mesajul de eroare, linia care conține BUG
pentru o descriere a erorii. Nu aveți voie să realizați operații blocante în context atomic. Contextul atomic este dat de o secțiune aflată între o operație de lock
și una de unlock
pe un spinlock.
Intrați în directorul 3-memory/
și parcurgeți conținutul fișierului memory.c
. Observați comentariile marcate cu TODO.
Trebuie să alocați 4 structuri de tipul struct task_info
și să le inițializați (în funcția memory_init
), apoi să le afișați și dezalocați (în funcția memory_exit
).
struct task_info
și inițializați câmpurile acesteia:pid
la PID-ul transmis ca parametru;timestamp
la valoarea variabilei jiffies
, care menține timpul de activitate al sistemului.jiffies
se găsesc în laboratorul 6.current
, de tipul struct task_struct *
.struct task_struct *
.printk
pentru a afișa cele două câmpuri ale acestora: pid
și timestamp
.kfree
.
task_struct
conţine două câmpuri pentru a desemna părintele unui task:
real_parent
indică spre procesul care a creat taskul respectiv sau spre procesul 1 (init) dacă părintele şi-a încheiat execuţia.parent
indică spre părintele curent al taskului (procesul care va fi semnalat în cazul în care taskul îşi încheie execuţia).În general valorile celor două câmpuri sunt identice, dar apar şi situaţii când ele diferă, de exemplu atunci când se foloseşte apelul de sistem ptrace.
Intrați în directorul 4-list/
. Parcurgeți conținutul fișierului list.c
și observați comentariile marcate cu TODO. Procesul curent va adăuga cele patru structuri menționate anterior într-o listă. Lista va fi construită în funcția list_init
și în funcția task_info_add_for_current
. Lista va fi afișată în funcția task_info_print_list
și ștearsă în funcția task_info_purge_list
, ambele fiind apelate din list_exit
.
task_info_add_to_list
pentru a aloca o structură struct task_info
și a o adăuga la listă. Funcția este deja apelată de 4 ori din task_info_add_for_current
.task_info_purge_list
pentru a șterge toate elementele din listă.
Atunci când ștergeți elemente din listă, va trebui să folosiți apelul list_for_each_safe. Puteți folosi și apelul list_for_each_entry_safe.
Intrați în directorul 5-list-full/
. Parcurgeți conținutul fișierului list-full.c
și observați comentariile marcate cu TODO. Pe lângă funcționalitățile de la 4-list
vom adăuga următoarele:
count
care arată de câte ori a fost “adăugat” un proces.timestamp
.count
.task_info_find_pid
care caută un pid în lista existentă.task_info
. Dacă nu, se întoarce NULL.task_info_remove_expired
.task_info_find_pid
așa cum este descris mai sus și în comentariul funcției.task_info_remove_expired
.schedule_timeout
.
head.next
) și puneți câmpul count
al structurii de tip ti
aferente pe o valoare suficient de mare (10
) folosind funcția atomic_set
.
Intrați în directorul 6-list-sync/
.
6-list-sync/list-sync.c
conține aceeași funcționalitate ca cea adăugată la exercițiul anterior.
Intrați în directorul 7-list-test/
și parcurgeți conținutul fișierului list-test.c
. Vom folosi un modul de testare.
Acesta va apela funcții exportate de modulul de la 6-list-sync/
. Funcțiile exportate sunt cele descrise cu extern în cadrul fișierului list-test.c
.
Pentru exportarea funcțiilor de mai sus, din modulul de la 6-list-sync/
este nevoie de următorii pași:
EXPORT_SYMBOL(task_info_remove_expired);
. Macro-ul trebuie folosit pentru fiecare funcție, după definirea funcției.6-list-sync/
secvența cu evitarea expirării unui element al listei (se bate cap în cap cu ce face testul).6-list-sync/
. Odată încărcat va expune funcțiile exportate și vor putea fi folosite de modulul de test. Puteţi verifica acest lucru căutând numele funcţiilor în /proc/kallsyms
înainte şi după încărcarea modulului.lsmod
ca să verificaţi că cele două module s-au încărcat. Ce observaţi?
Care trebuie să fie ordinea de descărcare a celor două module (cel din 6-list-sync/
și cel de test)? Ce se întâmplă dacă folosiți altă ordine?