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
block_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 ./block_device_drivers/1-2-3-6-ram-disk/kernel ./block_device_drivers/4-5-relay-disk; 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>
.
root@qemux86:~/skels/block_device_drivers# ls 1-2-3-6-ram-disk 4-5-relay-disk root@qemux86:~/skels/block_device_drivers# ls 4-5-relay-disk/ relay-disk.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
.~/so2/linux/
cu sursele nucleului unde putem folosi Vim și cscope pentru parcurgerea codului sursă.student@eg106-pc:~$ netcat -lup 6666 </code>
Pentru buna desfășurare a laboratorului recomandăm să aveți deschise trei tab-uri de browser:
Găsiți definițiile următoarelor simboluri în sursele kernel-ului Linux:
bio
;bio_vec
;bio_for_each_segment
;gendisk
;block_device_operations
;request
.
git pull --rebase
in directorul ~/so2/linux
, pentru a obține ultima versiune a scheletului de laborator.
Creați un modul de kernel care să permită înregistrarea, respectiv deînregistrarea unui dispozitiv de tip bloc. Porniți de la fișierele din directorul 1-2-3-6-ram-disk/kernel
din scheletul laboratorului.
Urmăriți secțiunile marcate cu TODO 1
în scheletul de laborator. Folosiți macrodefinițiile existente (MY_BLOCK_MAJOR
, MY_BLKDEV_NAME
). Verificați valoarea întoarsă de funcția de înregistrare și, în caz de eroare, întoarceți cod de eroare.
Compilați modulul, copiați-l pe mașina virtuală și încărcați-l în nucleu. Verificați daca dispozitivul a fost încărcat cu succes consultând /proc/devices
. Veți vedea dispozitivul cu majorul 240
.
Descărcați modulul de kernel și verificați că a fost deînregistrat.
Schimbați valoarea macroului MY_BLOCK_MAJOR
la valoarea 254
. Compilați modulul, copiați-l pe mașina virtuală și încărcați-l în nucleu. Observați că încărcarea eșuează întrucât există deja un alt driver/dispozitiv care are majorul 254
înregistrat în nucleu.
Restaurați valoarea 240
pentru macroul MY_BLOCK_MAJOR
.
Modificați modulul anterior pentru a adăuga un disc asociat driverului. Analizați macrodefinițiile, structura struct my_block_dev
și funcțiile existente din fișierul ram-disk.c
.
Urmăriți comentariile marcate cu TODO 2
. Folosiți funcțiile create_block_device
și delete_block_device
.
Completați funcția my_block_request
pentru prelucrarea cozii de cereri fără a face o prelucrare efectivă a cererii: afișați mesajul "request received"
și următoarele informații: sectorul de start, dimensiunea totală, dimensiunea datelor din bio-ul curent, direcția de tratare. Pentru a valida tipul unei cereri, folosiți funcția blk_rq_is_passthrough (funcția returnează 0
în cazul de care suntem interesați, atunci când cererea este generată de către sistemul de fișiere).
Folosiți __blk_end_request_all pentru încheierea prelucrării cererii.
Inserați modulul în kernel. Folosiți dmesg
pentru a observa un mesaj transmis de modul. În momentul adăugării dispozitivului se transmite o cerere către acesta. Verificați prezența /dev/myblock
și dacă nu există creați dispozitivul folosind comanda
mknod /dev/myblock b 240 0
Pentru a genera cereri de scriere, folosiți comanda
echo "abc" > /dev/myblock
Observați că se creează o cerere de scriere precedată de una de citire. Cererea de citire are loc pentru a citi blocul de pe disc și a “actualiza” în conținutul său ceea ce a fost furnizat de utilizator, fără a suprascrie restul. După citire și actualizare, are loc scrierea.
Modificați modulul anterior pentru a crea un RAM disc: cererile către dispozitiv vor rezulta în citiri/scrieri într-o zonă de memorie.
Zona de memorie aferentă dev->data
este deja alocată în codul sursă din modul folosind vmalloc1). Pentru dezalocare în modul se folosește vfree.
Parcurgeți comentariile marcate cu TODO 3
pentru a completa funcția my_block_transfer
care să scrie/citească informația din cerere în/din zona de memorie. Funcția va fi apelată pentru fiecare cerere din cadrul funcției de prelucrarea a cozii de cereri: my_block_request
. Pentru a scrie/citi în/din zona de memorie folosiți memcpy
. Pentru determinarea informației de scris/citit, folosiți câmpurile structurii request
bio_data(rq→bio)
.
Pentru testare folosiți fișierul de test user/ram-disk-test.c
, care se compilează automat la make build
, se copiază pe mașina virtuală la make copy
și se rulează folosind, pe mașina virtuală QEMU, comanda
./ram-disk-test
Nu este nevoie să încărcați modulul în nucleu, va fi încărcat de test prin rularea comenzii de mai sus.
Există posibilitatea ca unele teste să pice din cauza nesincronizării datelor transmise (flush).
Scopul acestui exercițiu este să citiți datele de pe discul PHYSICAL_DISK_NAME
(/dev/vdb
) direct din kernel.
dd if=/dev/zero of=qemu/mydisk.img bs=1024 count=1
și adăugați argumentul -drive file=qemu/mydisk.img,if=virtio,format=raw
comenzii qemu
, în fișierul qemu/Makefile
(în variabila QEMU_OPTS
)
Urmăriți comentariile marcate cu TODO 4
în directorul 4-5-relay-disk/
și completați funcțiile open_disk
și close_disk
. Folosiți funcțiile blkdev_get_by_path și blkdev_put. Dispozitivul trebuie deschis în mod read-write exclusiv (FMODE_READ | FMODE_WRITE | FMODE_EXCL
), iar ca holder trebuie să folosiți modulul curent (THIS_MODULE
).
Completați funcția send_test_bio
. Va trebui să creați un nou bio pe care să-l completați, să-l submiteți și să-l așteptați. Citiți primul sector al discului. Pentru așteptare folosiți submit_bio_wait.
0
. La această valoare trebuie inițializat câmpul bi_iter.bi_sector
al structurii bio.
Pentru operația de citire folosiți macro-urile REQ_OP_READ
și bio_set_op_attrs.
După terminarea operației afișați primii 3 octeți din datele citite de bio. Folosiți formatul "%02x"
la printk
pentru afișarea datelor și macrourile kmap_atomic, respectiv kunmap_atomic.
page
.
Pentru testare folosiți scriptul test-relay-disk
, care este copiat pe mașina virtuală la make copy
. Dacă nu este copiat, asigurați-vă că este executabil (chmod +x test-relay-disk
). Nu este nevoie să încărcați modulul în nucleu, va fi încărcat de test. Pentru a rula scriptul folosiți comanda
./test-relay-disk
Scriptul scrie "abc"
la începutul discului indicat de PHYSICAL_DISK_NAME
. În urma rulării, modulul va afișa 61 62 63
(valorile hexazecimale corespunzătoare).
Urmăriți comentariile marcate cu TODO 5
pentru scrierea unui mesaj (BIO_WRITE_MESSAGE
) pe disc.
Funcția send_test_bio
primește ca argument tipul operației (citire sau scriere). Apelați în relay_init
funcția pentru citire iar în relay_exit
funcția pentru scriere. Recomandăm folosirea macro-urilor REQ_OP_READ
și REQ_OP_WRITE
.
În cadrul funcției send_test_bio
, dacă operația este de scriere, completați buffer-ul aferent bio-ului cu mesajul BIO_WRITE_MESSAGE
. Folosiți macrourile kmap_atomic, respectiv kunmap_atomic pentru lucrul cu buffer-ul aferent bio-ului.
bio_set_op_attrs
Rulați scriptul test-relay-disk
pentru testare folosind comanda
./test-relay-disk
Scriptul va afișa la ieșirea standard mesajul "read from /dev/sdb: 64 65 66"
.
În implementarea de la exercițiul 3, am prelucrat doar un bio_vec
al bio
-ului curent din request
. Dorim să prelucrăm toate bio_vec
-urile din toate bio
-urile. Pentru aceasta vom parcurge toate bio
-urile request
-ului și toate bio_vec
-urile (numite și segmente) fiecărui bio
.
Adăugați, în cadrul implementării ramdisk-ului (directorul 1-2-3-6-ram-disk
) suport pentru prelucrarea cererilor din coada de cereri la nivel de bio. Urmăriți comentariile marcate cu TODO 6
.
Configurați macro-ul USE_BIO_TRANSFER
la valoarea 1.
Implementați funcția my_xfer_request
. Folosiți rq_for_each_segment pentru a parcurge structurile bio_vec ale fiecărui bio din request.
iter.iter.bi_sector
).
iter.bio
).
Folosiți macrourile kmap_atomic, respectiv kunmap_atomic pentru maparea paginilor fiecărui bio și accesarea bufferelor acestuia. Pentru transferul efectiv, apelați funcția my_block_transfer
implementată la exercițiul anterior.
Pentru testare folosiți fișierul de test ram-disk-test.c
.
</code>
îl rulați folosind, pe mașina virtuală QEMU, comanda
./ram-disk-test
Nu este nevoie să încărcați modulul în nucleu, va fi încărcat de test prin rularea comenzii de mai sus.
Există posibilitatea ca unele teste să pice din cauza nesincronizării datelor transmise (flush).