Laborator 7: Exerciții

Pregătirea laboratorului

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:

  • 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:

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

Numele laboratorului curent este block_device_drivers.

Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name> variabilei LABS.

Scheletul este generat în directorul tools/labs/skels.

Compilarea modulelor

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

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ă.

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ă.

Testarea modulelor

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 

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:

  1. Î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.
  2. Î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/linux/tools/labs.
  3. În al treilea tab de terminal accesăm directorul ~/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:

  1. În primul tab să fie deschis breviarul laboratorului.
  2. În al doilea tab să fie deschisă pagina curentă.
  3. În al treilea tab să fie deschisă pagina LXR pentru parcurgerea codului sursă din nucleu.

Citiți cu atenție toate precizările unui exercițiu înainte de a începe rezolvarea acestuia.

[0.5p] Intro

Găsiți definițiile următoarelor simboluri în sursele kernel-ului Linux:

  • structura bio;
  • structura bio_vec;
  • macro-ul bio_for_each_segment;
  • structura gendisk;
  • structura block_device_operations;
  • structura request.

[10.5p] Exerciții

Î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.

1. [1p] Dispozitiv de tip bloc

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.

2. [2p] Înregistrare disc

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).

Pentru moduri de obținere a informațiilor de afișat consultați secțiunea Cereri pentru dispozitive de tip bloc.

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.

3. [2p] RAM disc

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 secțiunea Prelucrarea cererilor.

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

Pentru a afla dimensiunea datelor din request folosiți macro-ul blk_rq_cur_bytes. Nu folosiți macro-ul blk_rq_bytes.

Pentru a afla buffer-ul cererii folosiți construcția bio_data(rq→bio).

O descriere a macro-urilor utile este în secțiunea Cereri pentru dispozitive de tip bloc.

Informații utile se găsesc în exemplul de block device driver din Linux Device Drivers.

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).

4. [2p] Citirea datelor de pe disc

Scopul acestui exercițiu este să citiți datele de pe discul PHYSICAL_DISK_NAME (/dev/vdb) direct din kernel.

Înainte de rezolvarea exercițiului, este necesar să adaugăm discul la mașina virtuală. Pentru aceasta, generați un fișier pe care îl vom folosi ca imaginea discului folosind comanda

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.

Primul sector al discului este sectorul cu indexul 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.

Ca argument pentru kmap_atomic folosiți chiar pagina alocată mai sus în cod în cadrul variabilei 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).

5. [1.5p] Scrierea unui mesaj pe disc

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.

Trebuie să actualizați tipul operației structurii bio folosind macrodefiniția 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".

6. [2p] Prelucrarea cererilor din coada la nivel de bio

Î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.

Urmăriți indicațiile și secvențele de cod din secțiunea Folosirea conținutului unei structuri bio.

Folosiți iteratorul segmentelor bio pentru a obține sectorul curent (iter.iter.bi_sector).

Folosiți iteratorul request-urilor pentru a obține referința la bio-ul curent (iter.bio).

Folosiți macro-ul bio_data_dir pentru a afla direcția de citire sau scriere pentru un 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).

Soluții

1) Pentru simplitate, se folosește funcția vmalloc pentru alocarea vectorului în memorie; funcția vmalloc alocă o zonă de memorie contiguă în spațiul de adrese virtuale, iar funcția vfree o dealocă. Mai multe despre alocarea memoriei în kernel găsiți în Linux Device Drivers 3rd Edition, Chapter 8. Allocating memory
so2/laboratoare/lab07/exercitii.txt · Last modified: 2018/04/12 11:40 by elena.sandulescu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0