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 eg106-pc
):
student@eg106-pc:~$ cd so2/ student@eg106-pc:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab07-tasks.zip student@eg106-pc:~/so2$ unzip lab07-tasks.zip student@eg106-pc:~/so2$ tree lab07-tasks
În cadrul directorului lab07-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 eg106-pc
) ș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@eg106-pc:~/so2$ cp /path/to/module.ko ~/so2/qemu-so2/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-so2/
, mașina virtuală QEMU folosind comanda
student@eg106-pc:~/so2/qemu-so2$ 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-so2/
.~/so2/linux-4.9.11/
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
.
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/
din arhiva de sarcini a 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 7
. 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 7
î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
.
Decomentați apelurile de funcții create_block_device
și delete_block_device
.
Urmăriți secțiunile marcate cu TODO 2
în scheletul de laborator. 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.
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 ram-disk-test.c
. Îl compilați folosind, pe sistemul fizic, comanda
make -f Makefile.test
și apoi î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).
Scopul acestui exercițiu este să citiți datele de pe discul PHYSICAL_DISK_NAME
direct din kernel.
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.
Pentru testare folosiți scriptul 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.
Trebuie să actualizați funcția send_test_bio
pentru a primi 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"
.
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
. Îl compilați folosind, pe sistemul fizic, comanda
make -f Makefile.test
și apoi î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).