This is an old revision of the document!
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
device_drivers.
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 ./device_drivers/kernel ./device_drivers/extra/char-driver-lin; 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/device_drivers# ls extra/ kernel/ user/ root@qemux86:~/skels/device_drivers# ls kernel/ so2_cdev.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
kernel/.user/.kernel/so2_cdev.c cu noi funcții.kernel/so2_cdev.c și folosiți macro-urile definite.
git pull --rebase in directorul ~/so2/linux, pentru a obține ultima versiune a scheletului de laborator.
Identificați, folosind cscope, definițiile următoarelor simboluri:
file;file_operations;generic_ro_fops;vfs_read, new_sync_read și generic_file_read_iter;
Urmăriți definiția funcției vfs_read. Observați că pentru un dispozitiv care nu are funcția read și nici funcția read_iter se va apela new_sync_read. Această funcție folosește read_iter care în mod implicit este definită la generic_file_read_iter.
Pentru cscope, folosiți comanda :cs f g nume pentru căutarea definiţiilor şi comanda :cs f s nume pentru căutarea simbolurilor. Pentru mai multe informaţii despre comenzile disponibile folosiţi comanda :help cscope.
Driver-ul va controla un singur dispozitiv cu majorul MY_MAJOR și minorul MY_MINOR (macro-urile definite în fișierul kernel/so2_cdev.c) Ca prim pas, va trebui să creați fișierul de tip caracter /dev/so2_cdev folosind utilitarul mknod.
Pentru a evita folosirea mknod la fiecare boot-are a mașinii, adăugați comanda mknod cu parametrii relevanți la finalul fișierului /etc/rcS din mașina virtuală.
Implementați înregistrarea și deînregistrarea dispozitivului cu numele so2_cdev, respectiv în funcția de intrare și cea de ieșire a modulului. Nu uitați că driver-ul controlează un singur dispozitiv. La acest exercițiu nu este nevoie să folosiți apelurile cdev_init și cdev_add. Le veți folosi la exercițiul 3.
Urmăriți comentariile marcate cu TODO 1.
Afișati, folosind macro-ul LOG_LEVEL, un mesaj după operațiile de înregistrare, respectiv deînregistrare, care să confirme realizarea cu succes a acestora.
Încărcați apoi modulul în kernel
insmod so2_cdev.ko
și consultați /proc/devices în secțiunea Character devices
cat /proc/devices | less
Identificați tipul de device înregistrat după majorul 42. Rețineți că în /proc/devices apar tipurile de dispozitive (adică majorul) nu și dispozitivele efective (adică minorii).
/dev nu sunt create în urma inserării modulului. Acestea se pot crea în două moduri:
mknod așa cum vom face în exercițiile următoare
Descărcați la final modulul din kernel:
rmmod so2_cdev
Modificați modulul de kernel pentru a încerca înregistrarea unui dispozitiv deja alocat. Consultați LXR pentru a identifica eroarea întoarsă la înregistrarea modulului.
Reveniți la configurația inițială a modulului.
/proc/devices pentru a obține un dispozitiv deja alocat.
Rulați comanda cat peste dispozitivul de tip caracter creat (/dev/so2_cdev). Citirea nu funcționează pentru că driver-ul nu are implementate funcțiile de deschidere, citire și închidere fișier.
Urmăriți comentariile marcate cu TODO 2 pentru a realiza următoarele:
struct cdev.open și release în driver.open și release.cat peste dispozitiv după inserarea modulului. Urmăriți mesajele afișate de kernel după rularea comenzii cat. Primiți mesajul de eroare pentru că driverul nu implementează funcția de citire din fișier.
Parcurgeți secțiunea open și release.
Restricționați accesul la dispozitiv cu variabile atomice, astfel încât un singur proces să poată deschide dispozitivul la un moment dat. Restul vor primi eroarea "Device busy" (''-EBUSY''). Restricționarea accesului se va face în funcția open expusă de driver. Urmăriți comentariile marcate cu TODO 3.
atomic_t în structura dispozitivului. open pentru a restricționa accesul la dispozitiv. Recomandăm folosirea atomic_cmpxchg 1).release, pentru a repermite accesul la dispozitiv.set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(1000);
cat /dev/so2_cdev & cat /dev/so2_cdev.
/dev/so2_cdev.
Implementați funcția read în driver. Urmăriți comentariile marcate cu TODO 4.
MESSAGE. Inițializarea acestui buffer se va face odată cu inițializarea dispozitivului.read copiați în bufferul de user space conținutul buffer-ului din kernel space.size și offset. Puteți presupune că buffer-ul din user space este suficient de mare. Nu este nevoie să verificați validitatea argumentului size al funcției read.read este numărul de octeți transmiși din buffer-ul din kernel space către buffer-ul din user space.cat /dev/so2_cdev.
cat /dev/so2_cdev nu se termină (folosiți Ctrl+C).
Citiți secțiunile read și write și Accesul la spațiul de adresă al procesului din laborator.
printk(LOG_LEVEL "Offset: %lld\n", *offset);
E important modificatorul de afișare %lld însemnând o afișare pentru un tip de date long long int. Tipul de date loff_t (folosit de offset) este un typedef pentru long long int.
Comanda cat citește până la sfârșitul fișierului, iar sfârșitul fișierului e semnalat prin întoarcerea valorii 0 din read. Astfel, pentru o implementare corectă va trebui să actualizați și să folosiți offset-ul primit ca parametru în funcția read și să întoarceți valoarea 0 atunci când utilizatorul a ajuns la sfârșitul buffer-ului.
Modificați driver-ul în așa fel încât rularea cat să se termine.
size.offset.read întoarce numărul de bytes care au fost copiați în buffer-ul utilizatorului.
offset se permite citirea și deplasarea poziției curente din fișier. Valoarea acesteia trebuie actualizată de fiecare dată când o citire se efectuează cu succes.
Adăugați posibilitatea de a scrie un mesaj care să îl înlocuiască pe cel predefinit. Implementați funcția write în driver. Urmăriți comentariile marcate cu TODO 5.
Ignorați pe moment parametrul offset. Puteți presupune că buffer-ul driverului este suficient de mare. Nu este nevoie să verificați validitatea argumentului size al funcției write.
Testați folosind comenzile
echo "arpeggio" > /dev/so2_cdev cat /dev/so2_cdev
Citiți secțiunile read și write și Accesul la spațiul de adresă al procesului din laborator.
Pentru acest exercițiu dorim să adăugăm operația ioctl MY_IOCTL_PRINT care să afișeze mesajul dat de macro-ul IOCTL_MESSAGE din driver. Urmăriți comentariile marcate cu TODO 6.
Pentru aceasta:
ioctl în driver.printk pentru a afișa mesajul din driver.user/so2_cdev_test.c) care să apeleze funcția ioctl cu parametrii corespunzători.
MY_IOCTL_PRINT este definită în fișierul include/so2_cdev.h din scheletul laboratorului (folosește _IOC pentru a defini operația). Acest fișier antet este inclus de ambele fișiere sursă (modulul de kernel și programul de test din user space).
Citiți secțiunile ioctl și open și release din laborator.
make build. După make copy, veți găsi executabilul în directorul /home/root/skels/device_drivers/user.
TODO 7.
1. (2 karma) Ioctl cu transmitere de mesaje
Adăugați două operații ioctl pentru modificarea mesajului asociat driver-ului. Folosiți buffer de lungime fixă (BUFFER_SIZE).
ioctl din driver operațiile: MY_IOCTL_SET_BUFFER pentru scrierea unui mesaj către dispozitiv;MY_IOCTL_GET_BUFFER pentru citirea unui mesaj de la dispozitiv.
2. (2 karma) Ioctl cu cozi de așteptare
Adăugați două operații ioctl în device driver pentru lucrul cu cozi de așteptare.
ioctl din driver operațiile: MY_IOCTL_DOWN pentru a adăuga procesul într-o coadă de așteptare;MY_IOCTL_UP pentru a scoate procesul dintr-o coadă de așteptare.wait_queue_head_t și un flag pentru condiția de așteptare.
La adăugarea procesului în coada de așteptare, acesta va rămâne blocat în execuție; pentru rularea comenzii de scoatere din coadă deschideți o nouă consolă în mașina virtuală cu Alt+F2; puteți reveni la consola anterioară cu Alt+F1. Dacă sunteți conectați prin SSH la mașina virtuală deschideți o nouă consolă.
3. (2 karma) Implementați flagul O_NONBLOCK.
O_NONBLOCK, taskul curent va fi pus în așteptare folosind cozi de așteptare
read, renunțați la condiția de exclusivitate pusă la punctul 4.fcntl),cat /dev/so2_dev?