Laborator 7: Exerciții

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.

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/qemu-so2/.
  3. În al treilea tab de terminal accesăm directorul ~/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:

  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

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

2. [1.5p] Î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.

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.

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

4. [2p] Citirea datelor de pe disc

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.

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. 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. [2p] Scrierea unui mesaj pe disc

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.

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

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: 2017/04/05 13:27 by razvan.deaconescu
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