This shows you the differences between two versions of the page.
so2:laboratoare:lab06:exercitii [2016/03/30 13:04] razvan.deaconescu [5. [1.5p] Workqueues] |
so2:laboratoare:lab06:exercitii [2018/03/28 09:17] (current) ionel.ghita [4. [1.5p] Operații blocante] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Laborator 6: Exerciții ====== | ====== Laborator 6: Exerciții ====== | ||
- | Pentru desfășurarea laboratorului pornim de la [[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip|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''):<code bash> | + | ===== Pregătirea laboratorului ===== |
- | student@asgard:~$ cd so2/ | + | |
- | student@asgard:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip | + | Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (''~/so2/linux/tools/labs''). |
- | student@asgard:~/so2$ unzip lab06-tasks.zip | + | |
- | student@asgard:~/so2$ tree lab06-tasks | + | Pașii de rezolvare sunt următorii: |
+ | * pregătirea scheletului de laborator | ||
+ | * compilarea modulelor de Kernel | ||
+ | * copierea modulelor pe mașina virtuală | ||
+ | * pornirea mașinii virtuale și testarea modulelor | ||
+ | |||
+ | ==== Pregătirea scheletului de laborator ==== | ||
+ | |||
+ | Scheletul de laborator este generat din sursele din directorul ''tools/labs/templates''. Putem genera scheletele pentru toate laboratoarele folosind următoarea comanda: | ||
+ | |||
+ | <code bash> | ||
+ | tools/labs $ make skels | ||
</code> | </code> | ||
- | Î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 [[:so2:resurse:masini-virtuale|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<code bash> | + | Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu ''LABS'': |
- | student@asgard:~/so2$ cp /path/to/module.ko ~/so2/qemu-vm/fsimg/root/modules/ | + | |
- | </code> 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<code bash> | + | <code bash> |
- | student@asgard:~/so2/qemu-vm$ make | + | tools/labs $ make clean |
+ | tools/labs $ LABS=<lab name> make skels | ||
+ | </code> | ||
+ | |||
+ | <note important> | ||
+ | Numele laboratorului curent este ''deferred_work''. | ||
+ | </note> | ||
+ | |||
+ | Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea ''<lab_name>/<task_name>'' variabilei ''LABS''. | ||
+ | |||
+ | <note> | ||
+ | Scheletul este generat în directorul ''tools/labs/skels''. | ||
+ | </note> | ||
+ | |||
+ | ==== Compilarea modulelor ==== | ||
+ | |||
+ | Comanda ''make build'' compilează toate modulele din directorul ''skels''. | ||
+ | |||
+ | <code bash> | ||
+ | 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 ./deferred_work/6-kthread ./deferred_work/1-2-timer ./deferred_work/3-4-5-deferred/kernel; do echo "obj-m += $i/" >> skels/Kbuild; done | ||
+ | ... | ||
+ | </code> | ||
+ | |||
+ | ==== Copierea modulelor pe mașina virtuală ==== | ||
+ | |||
+ | Putem copia modulele generate pe mașina virtuală folosind target-ul ''copy'' al comenzii make, atunci când mașina virtuală este oprită. | ||
+ | |||
+ | <code bash> | ||
+ | student@eg106:~/so2/linux/tools/labs$ make copy | ||
+ | student@eg106:~/so2/linux/tools/labs$ make boot | ||
+ | </code> | ||
+ | |||
+ | 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 [[https://ocw.cs.pub.ro/courses/so2/resurse/masini-virtuale#interactiunea_cu_masina_virtuala|Interacțiunea cu mașina virtuală]]. | ||
+ | |||
+ | ==== Testarea modulelor ==== | ||
+ | |||
+ | Modulele generate sunt copiate pe mașina virtuală în directorul ''/home/root/skels/<lab_name>''. | ||
+ | |||
+ | <code bash> | ||
+ | root@qemux86:~/skels/deferred_work# ls | ||
+ | 1-2-timer 3-4-5-deferred 6-kthread | ||
+ | root@qemux86:~/skels/deferred_work# ls 1-2-timer/ | ||
+ | timer.ko | ||
</code> | </code> | ||
- | După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU pentru a încărca și descărca modulul de kernel:<code> | + | 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:<code> |
- | # insmod modules/module-name.ko | + | root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko |
- | # rmmod module/module-name | + | root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko |
- | </code> unde ''module-name'' este numele modulului de kernel. | + | </code> |
<note> | <note> | ||
Pentru dezvoltarea laboratorului, este recomandat să folosim trei terminale sau, mai bine, trei tab-uri de terminal. Pentru a deschide un nou tab de terminal folosim combinația de taste ''Ctrl+Shift+t''. Cele trei tab-uri de terminal îndeplinesc următoarele roluri: | Pentru dezvoltarea laboratorului, este recomandat să folosim trei terminale sau, mai bine, trei tab-uri de terminal. Pentru a deschide un nou tab de terminal folosim combinația de taste ''Ctrl+Shift+t''. Cele trei tab-uri de terminal îndeplinesc următoarele roluri: | ||
- În primul tab de terminal dezvoltăm modulul de kernel: editare, compilare, copiere în directorul dedicat pentru mașina virtuală QEMU. Lucrăm în directorul aferent rezultat în urma decomprimării arhivei de sarcini a laboratorului. | - În primul tab de terminal dezvoltăm modulul de kernel: editare, compilare, copiere în directorul dedicat pentru mașina virtuală QEMU. Lucrăm în directorul aferent rezultat în urma decomprimării arhivei de sarcini a laboratorului. | ||
- | - În al doilea tab de terminal pornim mașina virtuală QEMU și apoi testăm modulul de kernel: încărcare/descărcare modul, rulare teste. Lucrăm în directorul aferent mașinii virtuale: ''~/so2/qemu-vm/''. | + | - În al doilea tab de terminal pornim mașina virtuală QEMU și apoi testăm modulul de kernel: încărcare/descărcare modul, rulare teste. Lucrăm în directorul aferent mașinii virtuale: ''~/so2/qemu-so2/''. |
- | - În al treilea tab de terminal pornim un server UDP care să primească [[:so2:laboratoare:lab02#netconsole|mesajele de netconsole]]. Nu contează în ce director ne aflăm. Folosim comanda<code bash> | + | - În al treilea tab de terminal pornim [[:so2:laboratoare:lab02#minicom|minicom]] sau un server UDP care să primească [[:so2:laboratoare:lab02#netconsole|mesajele de netconsole]]. Nu contează în ce director ne aflăm. Folosim comanda<code bash> |
student@asgard:~$ netcat -lup 6666 | student@asgard:~$ netcat -lup 6666 | ||
</code> | </code> | ||
Line 41: | Line 96: | ||
===== [10.5p] Exerciții ===== | ===== [10.5p] Exerciții ===== | ||
+ | |||
+ | <note important> | ||
+ | Înainte de începerea rezolvării laboratorului, rulați comanda ''%%git pull --rebase%%'' in directorul ''~/so2/linux'', pentru a obține ultima versiune a scheletului de laborator. | ||
+ | </note> | ||
==== 1. [2p] Timer ==== | ==== 1. [2p] Timer ==== | ||
Line 46: | Line 105: | ||
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. | 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 [[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip|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. | + | În directorul ''1-2-timer/'' 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. |
<note> | <note> | ||
Line 70: | Line 129: | ||
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''. | 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 [[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip|arhiva de sarcini]] a laboratorului. Urmăriți secțiunile marcate cu ''TODO 1'' în scheletul de laborator. | + | Porniți de la fișierele din subdirectorul ''3-4-5-deferred/kernel/''. Urmăriți secțiunile marcate cu ''TODO 1'' în scheletul de laborator. |
Va trebui să implementați următoarele operații ''ioctl''. | Va trebui să implementați următoarele operații ''ioctl''. | ||
Line 103: | Line 162: | ||
</note> | </note> | ||
- | 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 [[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip|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). | + | 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 scheletul laboratorului. 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). |
- | + | ||
- | <note tip> | + | |
- | **Pe stațiile din sala de laborator EG106**, pentru a compila codul sursă de user space folosiți compilatorul ''gcc-5''. Pentru aceasta rulați comanda:<code> | + | |
- | /usr/bin/gcc-5 -m32 -static -Wall -g -o test test.c | + | |
- | </code> | + | |
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''. | 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''. | ||
- | </note> | ||
<note tip> | <note tip> | ||
Line 140: | Line 193: | ||
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). | 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). | ||
+ | <note tip> | ||
+ | Pentru a obține un pointer la datele private ale modulului (structura ''my_device_data''), puteți folosi macro-ul [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/timer.h#L143|from_timer]], | ||
+ | care este similar cu [[https://elixir.bootlin.com/linux/v4.15/source/tools/include/linux/kernel.h#L26|container_of]]. | ||
+ | <code c> | ||
+ | static void timer_handler(struct timer_list *tl) | ||
+ | { | ||
+ | struct my_device_data *data = from_timer(data, tl, timer); | ||
+ | ... | ||
+ | } | ||
+ | </code> | ||
+ | </note> | ||
==== 5. [1.5p] Workqueues ==== | ==== 5. [1.5p] Workqueues ==== | ||
Line 147: | Line 211: | ||
<note tip> | <note tip> | ||
- | Adăugați un câmp ''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 [[http://lxr.free-electrons.com/source/include/linux/workqueue.h?v=3.13#L555|schedule_work]]. **Nu** folosiți funcția [[http://lxr.free-electrons.com/source/include/linux/workqueue.h?v=3.13#L571|schedule_delayed_work_on]]. | + | Adăugați un câmp ''work'' de tipul ''struct work_struct'' în structura de dispozitiv. Inițializați acest câmp. Submiterea work-ului după **N** secunde o veți face din rutina de tratare a timer-ului, folosind funcția [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/workqueue.h#L533|schedule_work]]. Timer-ul va fi planificat să ruleze după **N** secunde, rutina de tratare a timer-ului va planifica work-ul și acesta va rula cât mai aproape de acel moment. **Nu** folosiți funcția [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/workqueue.h#L578|schedule_delayed_work_on]]. |
- | Folosiți **workqueue**-ul implicit (adică **nu** creați un workqueue, adică **nu** apelați [[http://lxr.free-electrons.com/source/include/linux/workqueue.h?v=3.13#L452|create_workqueue]]). | + | Folosiți **workqueue**-ul implicit (adică **nu** creați un workqueue, adică **nu** apelați [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/workqueue.h#L428|create_workqueue]]). |
</note> | </note> | ||
Line 160: | Line 224: | ||
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. | 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 [[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-tasks.zip|arhiva de sarcini]] a laboratorului și urmăriți TODO-urile din codul sursă. | + | Porniți de la fișierele din subdirectorul ''6-kthread/'' din scheletul laboratorului și urmăriți TODO-urile din codul sursă. |
Veți crea și veți porni kernel thread-ul la încărcarea modulului. | Veți crea și veți porni kernel thread-ul la încărcarea modulului. | ||
Line 166: | Line 230: | ||
<note tip> | <note tip> | ||
Pentru crearea unui thread aveți două opțiuni: | Pentru crearea unui thread aveți două opțiuni: | ||
- | * Apelați macro-ul [[http://lxr.free-electrons.com/source/include/linux/kthread.h?v=3.13#L22|kthread_run]]. | + | * Apelați macro-ul [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/kthread.h#L35|kthread_run]]. |
- | * Apelați macro-ul [[http://lxr.free-electrons.com/source/include/linux/kthread.h?v=3.13#L13|kthread_create]] care creează un thread suspendat și apoi apelați funcția [[http://lxr.free-electrons.com/source/kernel/sched/core.c?v=3.13#L1674|wake_up_process]] pentru rularea acestuia; așa cum se întâmplă în cadrul macro-ului [[http://lxr.free-electrons.com/source/include/linux/kthread.h?v=3.13#L22|kthread_run]]. | + | * Apelați macro-ul [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/kthread.h#L15|kthread_create]] care creează un thread suspendat și apoi apelați funcția [[https://elixir.bootlin.com/linux/v4.15/source/kernel/sched/core.c#L2137|wake_up_process]] pentru rularea acestuia; așa cum se întâmplă în cadrul macro-ului [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/kthread.h#L35|kthread_run]]. |
Parcurgeți secțiunea [[:so2:laboratoare:lab06#Kernel threads|Kernel threads]] din laborator. | Parcurgeți secțiunea [[:so2:laboratoare:lab06#Kernel threads|Kernel threads]] din laborator. | ||
Line 190: | Line 254: | ||
Dorim să ne acomodăm cu modul de sincronizare între o rutină amânabilă (un timer) și contextul proces. | 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. | + | Pentru aceasta, dorim să colectăm informații periodice despre procesul idle (''swapper/0''), procesul cu PID-ul ''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 numărul de schimbări involuntare de până atunci ale procesului idle. Acest număr este dat de câmpul ''nivcsw'' (de tipul ''unsigned long'') din cadrul structurii ''task_struct'', adică folosind ''%%current->nivcsw%%''. |
- | Î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 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 numărului de schimbări involuntare. Dacă buffer-ul este plin nu se stochează nimic. |
+ | |||
+ | <note tip> | ||
+ | Citirea datelor o veți face la fiecare secundă dacă flag-ul dispozitivului este inițializat la valoarea ''TIMER_TYPE_ACCT''. Pentru inițializarea flag-ului veți comanda din user space dispozitivul (prin ''ioctl'') folosind executabilul de test, după încărcarea modulului și crearea dispozitivului, astfel:<code> | ||
+ | ./test t | ||
+ | </code> | ||
+ | </note> | ||
În rutina de read a modulului se transferă în user space datele din buffer și se resetează buffer-ul. | În rutina de read a modulului se transferă în user space datele din buffer și se resetează buffer-ul. | ||
Line 198: | Line 268: | ||
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. | 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/''. | + | <note tip> |
+ | Veți folosi un buffer temporar pentru a copia datele în user space. Nu puteți copia date în user space cu un spinlock deținut; rutina ''copy_to_user'' poate fi blocantă. | ||
+ | |||
+ | Spinlock-ul, bufferul și bufferul temporar folosit pentru copiere sunt definite în structura dispozitivului. | ||
+ | |||
+ | O parte din rutina de read este implementată; trebuie să implementați partea în care se face sincronizarea. | ||
+ | </note> | ||
+ | |||
+ | În implementare porniți de la codul din subdirectorul ''3-4-5-deferred/''. Urmăriți secțiunile marcate cu ''TODO 4'' în scheletul de laborator. | ||
+ | |||
+ | <note tip> | ||
+ | Pentru a testa modulul de kernel space puteți citi date din dispozitiv folosind comanda<code> | ||
+ | od -s /dev/deferred | ||
+ | </code> | ||
+ | </note> | ||
+ | |||
+ | ==== [1 karma] Citire date din user space folosind read ==== | ||
+ | |||
+ | Actualizați modulul de test din user space (''user/test.c'') ca să ofere opțiunea ''r''. În momentul în care se transmite opțiunea către modulul de test acesta citește conținutul buffer-ului din kernel space folosind apelul ''read()'' și afișează în format zecimal (''%lu'') rezultatele primite (vector de ''unsigned long''). | ||
==== Soluții ==== | ==== Soluții ==== | ||
[[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-sol.zip|Soluții exerciții laborator 6]] | [[http://elf.cs.pub.ro/so2/res/laboratoare/lab06-sol.zip|Soluții exerciții laborator 6]] | ||