This is an old revision of the document!
Pentru desfășurarea laboratorului pornim de la arhiva de sarcini a laboratorului. Descărcăm și decomprimăm arhiva în directorul so2/
din directorul home al utilizatorului student
de pe sistemul de bază (stația asgard
):
student@asgard:~$ cd so2/ student@asgard:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip student@asgard:~/so2$ unzip lab06-tasks.zip student@asgard:~/so2$ tree lab06-tasks
În cadrul directorului lab06-tasks/
se găsesc resursele necesare pentru dezvoltarea exercițiilor de mai jos: fișiere schelet de cod sursă, fișiere Makefile și Kbuild, scripturi și programe de test.
Vom dezvolta exercițiile pe sistemul de bază (stația asgard
) și apoi le vom testa pe mașina virtuală QEMU. După editarea și compilarea unui modul de kernel îl vom copia în directorul dedicat pentru mașina virtuală QEMU folosind o comandă de forma
student@asgard:~/so2$ cp /path/to/module.ko ~/so2/qemu-vm/fsimg/root/modules/
unde /path/to/module.ko
este calea către fișierul obiect aferent modulului de kernel. Apoi vom porni, din directorul ~/so2/qemu-vm/
, mașina virtuală QEMU folosind comanda
student@asgard:~/so2/qemu-vm$ make
După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU pentru a încărca și descărca modulul de kernel:
# insmod modules/module-name.ko # rmmod module/module-name
unde module-name
este numele modulului de kernel.
Ctrl+Shift+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/qemu-vm/
.student@asgard:~$ netcat -lup 6666
Găsiți definițiile următoarelor simboluri în nucleul Linux:
jiffies
;timer_list
;spin_lock_bh
;
Urmărim crearea unui modul simplu de kernel care să afișeze un mesaj la TIMER_TIMEOUT
secunde de la încărcarea modulului în kernel.
În directorul 1-2-timer/
din arhiva de sarcini a laboratorului este scheletul de cod timer.c
de unde să porniți pentru implementare. Urmăriți secțiunile marcate cu TODO 1
în scheletul de laborator.
printk(LOG_LEVEL ... )
. Mesajele vor fi afișate la consolă și pot fi vizualizate și folosind dmesg
și consola de netconsole.
jiffies
. Pentru a indica timpul după TIMER_TIMEOUT
secunde folosim construcția jiffies + TIMER_TIMEOUT * HZ
.
Ne propunem realizarea unui timer periodic.
Modificați modulul anterior pentru a afișa mesajul în dmesg
o dată la fiecare TIMER_TIMEOUT
secunde. Urmăriți secțiunea marcată cu TODO 2
în scheletul de laborator.
Ne propunem să afișăm informații despre procesul curent după N secunde de la primirea unei comezi de tipul ioctl
apelată din user space. N este transmis ca paramentru prin ioctl
.
Porniți de la fișierele din subdirectorul 3-4-5-deferred/kernel/
din arhiva de sarcini a laboratorului. Urmăriți secțiunile marcate cu TODO 1
în scheletul de laborator.
Va trebui să implementați următoarele operații ioctl
.
MY_IOCTL_TIMER_SET
pentru planificarea unui timer să ruleze dupa un număr de secunde primit ca argument de rutina ioctl
. Timer-ul nu rulează periodic.3-4-5-deferred/user
) direct o valoare, nu un pointer.MY_IOCTL_TIMER_CANCEL
pentru dezactivarea timer-ului.
jiffies
. Pentru a indica timpul după N
secunde folosim construcția jiffies + N * HZ
.
ioctl
revedeți secțiunea aferentă din laboratorul 4.
Anularea unui timer este echivalentă cu apelarea mod_timer(..., 0)
.
În rutina de tratare a timer-ului, afișați identificatorul procesului curent (PID-ul) și numele imaginii de executabil.
current->pid
, iar imaginea de executabil folosind construcția current->comm
. Pentru detalii, revedeți Laboratorul 2.
Pentru a putea folosi device driver-ul din user-space, trebuie sa creați fișierul pentru dispozitivul de tip caracter /dev/deferred
folosind utilitarul mknod
. Alternativ, puteți rula scriptul makenode
din 3-4/deferred/kernel/
, care realizează aceste operații.
makenode
trebuie copiat în mașina virtuală QEMU pentru a-l rula acolo.
Activați și dezactivați timer-ul prin apelul operațiilor ioctl din user-space. Utilizați programul 3-4-5-deferred/user/test/
din arhiva de sarcini. Rulați programul pentru a testa planificarea și dezactivarea unui timer. Programul primește ca parametri în linie de comandă operația de tip ioctl și parametrii acesteia (dacă e cazul).
gcc-5
. Pentru aceasta rulați comanda:
/usr/bin/gcc-5 -m32 -static -Wall -g -o test test.c
Executabilul rezultat (test
) trebuie să îl copiați pe mașina virtuală QEMU la fel ca modulul de kernel și să îl rulați pe mașina virtuală pentru a valida implementarea corectă a ioctl
.
test
) fără argumente pentru a observa opțiunile de rulare în linia de comandă ale acestuia.
Pentru a activa timer-ul la 3
secunde folosind executabilul test
pe mașina virtuală QEMU folosiți comanda
./test s 3
Pentru a dezactiva timer-ul folosind executabilul test
pe mașina virtuală QEMU folosiți comanda
./test c
Observați că la fiecare rulare a timer-ului procesul afișat este swapper/0
cu PID-ul 0
. Procesul swapper/0
este procesul idle pe un sistem Linux. E procesul rulat când nu există altceva de rulat. Întrucât sistemul de operare de pe mașina virtuală nu face mare lucru, este natural că procesul swapper/0
va rula mai tot timpul. Iar rutina de rulare a timer-ului rulează foarte aproape de momentul întreruperii de ceas, care va întrerupe procesul care rulează atunci pe procesor, adică swapper/0
.
Urmărim să vedem ce se întâmplă atunci când într-o rutină de tratare a unui timer realizăm operații blocante. Pentru aceasta urmărim să apelăm în rutine de tratare a timer-ului o funcție numită alloc_io()
care simulează o operație blocantă.
Modificați modulul astfel încât la primirea comenzii MY_IOCTL_TIMER_SET
să se păstreze funcționalitatea de la task-ul numărul 3, iar la primirea comenzii MY_IOCTL_TIMER_ALLOC
rutina de tratare a timerului să apeleze funcția alloc_io()
. Urmăriți secțiunile marcate cu TODO 2
în scheletul de laborator.
Folosiți același timer. Pentru a diferenția funcționalitățile în rutina de tratare a timer-ului, folosiți un flag în structura device-ului. Pentru valorile pe care le poate lua flag-ul folosiți constantele TIMER_TYPE_ALLOC
și TIMER_TYPE_SET
definite în scheletul de cod. Pentru inițializare folosiți TIMER_TYPE_NONE
.
Rulați programul de test pentru a verifica funcționalitatea de la task-ul 3. Rulați din nou programul de test pentru a apela funcția alloc_io()
.
Observați că programul cauzează eroare pentru că se apelează o funcție blocantă în context atomic (handler-ul de timer rulează în context amânabil/întrerupere).
Vom modifica modulul pentru a preîntâmpina eroarea observată la task-ul numărul 4.
Pentru aceasta apelați funcția alloc_io()
folosind workqueues. Veți planifica un work din rutina de tratare a timer-ului care va fi planificat pentru rulare în context proces. În work handler (care rulează în context proces) veți apela funcția alloc_io()
. Urmăriți secțiunile marcate cu TODO 3
în scheletul de laborator.
work
de tipul struct work_struct
în structura de dispozitiv. Inițializați acest câmp. Submiterea work-ului o veți face din rutina de tratare a timer-ului, după N secunde, folosind funcția schedule_work. Nu folosiți funcția schedule_delayed_work_on.
Folosiți workqueue-ul implicit (adică nu creați un workqueue, adică nu apelați create_workqueue).
Dorim să creăm un modul simplu în care să creăm un kernel thread cu ajutorul căruia să afișăm identificatorul procesului curent.
Porniți de la fișierele din subdirectorul 6-kthread/
din arhiva de sarcini a laboratorului și urmăriți TODO-urile din codul sursă.
Veți crea și veți porni kernel thread-ul la încărcarea modulului.
Parcurgeți secțiunea Kernel threads din laborator.
Thread-ul se va sincroniza cu funcția de descărcare a modulului. Adică:
Pentru sincronizare folosiți două cozi de așteptare și două flag-uri aferente.
Pentru flag-uri folosiți variabile atomice. Pentru modul de utilizare al variabilelor atomice revedeți Laboratorul 3.
Dorim să ne acomodăm cu modul de sincronizare între o rutină amânabilă (un timer) și contextul proces.
Pentru aceasta, dorim să colectăm informații periodice despre procesul idle (swapper/0
) pe care să le scriem într-un buffer. Informațiile le vom colecta în momentul în care este invocată rutina de tratare a timer-ului. Informația colectată va fi timpul de rulare total de până atunci al procesului idle. Acest timp se găsește în construcția current->cputime_expires.sum_exec_runtime
de tipul unsigned long long.
În rutina de timer, dacă la orice moment de timp procesul întrerupt este procesul idle (adică are PID-ul 0
), se stochează într-un buffer o nouă valoarea a timpului de rulare. Dacă buffer-ul este plin nu se stochează nimic.
În rutina de read a modulului se transferă în user space datele din buffer și se resetează buffer-ul.
Folosiți un spinlock și operațiile corespunzătoare pentru a asigura accesul corect la buffer din rutina de timer și din rutina de read.
În implementare porniți de la codul din subdirectorul 3-4-5-deferred/
.
current->cputime_expires.sum_exec_runtime
sunt zero, folosiți construcția current->nvcsw
care contabilizează numărul de context switch-uri.
od /dev/deferred
În mod ideal veți apela read()
din cadrul fișierului test.c
pentru a citi datele în format întreg din kernel și pentru a le afișa formatate corespunzător.