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/.
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.