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 pr_info
, 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
?