Laborator 3: 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 asgard):

student@asgard:~$ cd so2/
student@asgard:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab03-tasks.zip
student@asgard:~/so2$ unzip lab03-tasks.zip
student@asgard:~/so2$ tree lab03-tasks

În cadrul directorului lab03-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 asgard) ș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@asgard:~/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@asgard:~/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 pornim minicom sau un server UDP care să primească mesajele de netconsole. Nu contează în ce director ne aflăm. Folosim comanda
    student@asgard:~$ netcat -lup 6666

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

[0.5p] Intro

Identificați, folosind cscope sau LXR, definițiile următoarelor simboluri:

  • structura list_head;
  • funcția INIT_LIST_HEAD;
  • funcția list_add;
  • macro-ul list_for_each;
  • macro-urile list_entry, container_of și offsetof. Pentru offsetof, găsiți varianta generică, independentă de compilator.

Observați detaliile de implementare ale macro-urilor și funcțiilor.

[4.5p] Memoria

1. [1p] Alocarea de memorie în Linux

Intrați în directorul 1-mem/ și parcurgeți conținutul fișierului mem.c. Observați folosirea apelului kmalloc pentru alocare de memorie.

  1. Compilați codul sursă într-un modul de kernel folosind comanda make.
  2. Încărcați modulul în kernel folosind comanda insmod mem.ko.
  3. Vizualizați mesajele nucleului folosind comanda dmesg.
  4. Descărcați modulul din kernel folosind comanda rmmod mem.

Revedeți secțiunea Alocare memorie din laborator.

2. [1p] Folosirea de operații blocante în context atomic

Intrați în directorul 2-sched-spin/ și parcurgeți conținutul fișierului sched-spin.c. Hint: Funcția schedule_timeout, coroborată cu macro-ul set_current_state, forțează procesul curent să aștepte 5 secunde.

  1. Compilați codul sursă într-un modul de kernel folosind comanda make.
  2. Încărcați modulul în kernel folosind comanda insmod sched-spin.ko.
    • Observați că se așteaptă 5 secunde până la definitivarea comenzii de inserare.
  3. Descărcați modului din kernel.
  4. Decomentați liniile care conțin operații cu spinlock-uri. Compilați din nou codul sursă și reîncărcați modulul în kernel.

Ați obținut eroare. Urmăriți stack trace-ul. Care este cauza erorii? Hint: Urmăriți, în mesajul de eroare, linia care conține BUG pentru o descriere a erorii. Nu aveți voie să realizați operații blocante în context atomic. Contextul atomic este dat de o secțiune aflată între o operație de lock și una de unlock pe un spinlock.

Revedeți secțiunile Contexte de execuție, Locking și Spinlock-uri din laborator.

3. [2.5p] Alocarea și lucrul cu memoria în kernel

Intrați în directorul 3-memory/ și parcurgeți conținutul fișierului memory.c. Observați comentariile marcate cu TODO. Trebuie să alocați 4 structuri de tipul struct task_info și să le inițializați (în funcția memory_init), apoi să le afișați și dezalocați (în funcția memory_exit).

  1. (TODO 1) Structurile vor conține, respectiv:
    • PID-ul procesului curent, dat de macro-ul current, de tipul struct task_struct *.
      • Hint: Căutați câmpul relevant pentru PID în structura task_struct.
    • PID-ul procesului părinte al procesului curent.
      • Hints:
        • Căutați câmpul relevant din structura task_struct.
        • Căutați după șirul “parent”.
    • PID-ul procesului următor din lista de procese.
      • Hints:
        • Folosiți macro-ul next_task.
        • Macro-ul întoarce pointer-ul la următorul proces, adică de tipul struct task_struct *.
    • PID-ul următorului proces după următorul.
      • Hint: Folosiți de două ori macro-ul next_task.
  2. (TODO 2) Alocați structura struct task_info și inițializați câmpurile acesteia:
    • câmpul pid la PID-ul transmis ca parametru;
    • câmpul timestamp la valoarea variabilei jiffies, care menține timpul de activitate al sistemului.
      • Hint: Detalii despre variabila jiffies se găsesc în laboratorul 6.
  3. (TODO 3) Afișați cele patru structuri.
    • Folosiți printk pentru a afișa cele două câmpuri ale acestora: pid și timestamp.
  4. (TODO 4) Eliberați spațiul ocupat de structuri.
    • Folosiți kfree.

Structura task_struct conţine două câmpuri pentru a desemna părintele unui task:

  • real_parent indică spre procesul care a creat taskul respectiv sau spre procesul 1 (init) dacă părintele şi-a încheiat execuţia.
  • parent indică spre părintele curent al taskului (procesul care va fi semnalat în cazul în care taskul îşi încheie execuţia).

În general valorile celor două câmpuri sunt identice, dar apar şi situaţii când ele diferă, de exemplu atunci când se foloseşte apelul de sistem ptrace.

Revedeți secțiunea Alocare memorie din laborator.

[6p] Liste

4. [2p] Lucrul cu liste în kernel

Intrați în directorul 4-list/. Parcurgeți conținutul fișierului list.c și observați comentariile marcate cu TODO. Procesul curent va adăuga cele patru structuri menționate anterior într-o listă. Lista va fi construită în funcția list_init și în funcția task_info_add_for_current. Lista va fi afișată și ștearsă în funcția list_exit și în funcția task_info_purge_list.

  1. (TODO 0) Copiați de la exercițiul anterior (3-memory/) funcțiile și secțiunile indicate.
  2. (TODO 1) Completați funcția task_info_add_to_list pentru a aloca o structură struct task_info și a o adăuga la listă.
  3. (TODO 2) Completați funcția task_info_purge_list pentru a șterge toate elementele din listă.
  4. Compilați modulul de kernel. Încărcați și descărcați modulul urmărind mesajele afișate de kernel.

Revedeți secțiunea Liste din laborator.

Atunci când ștergeți elemente din listă, va trebui să folosiți apelul list_for_each_safe. Puteți folosi și apelul list_for_each_entry_safe.

5. [2p] Lucrul cu liste în kernel pentru prelucrarea proceselor

Intrați în directorul 5-list-full/. Parcurgeți conținutul fișierului list-full.c și observați comentariile marcate cu TODO. Pe lângă funcționalitățile de la 4-list vom adăuga următoarele:

  • Un câmp count care arată de câte ori a fost “adăugat” un proces.
    • Dacă un proces este “adăugat” de mai multe ori, nu se creează o nouă intrare în listă, ci:
      • Se actualizează câmpul timestamp.
      • Se incrementează contorul count.
  • Pentru implementarea facilității de contor, se adaugă o funcție task_info_find_pid care caută un pid în lista existentă.
    • Dacă este găsit se întoarce referința la structura task_info. Dacă nu, se întoarce NULL.
  • O facilitate de expirare.
    • Dacă un proces nu este “adăugat” în vreme de 3 secunde și dacă nu are contor mai mare de 5, atunci este considerat eliminat și este extras din listă.
    • Facilitatea de expirare este implementată deja în funcția task_info_remove_expired.
  1. (TODO 0) Copiați de la 3-memory și/sau 4-list secțiuni de cod acolo unde sunt indicate astfel.
  2. (TODO 1) Implementați funcția task_info_find_pid așa cum este descris mai sus și în comentariul funcției.
  3. (TODO 2) Modificați un câmp al unui element din listă astfel încât acesta să nu expire. Hint: Trebuie să nu satisfacă o parte a condiției de expirare din funcția task_info_remove_expired.
  4. Compilați, încărcați și descărcați modulul urmărind mesajele afișate. Va dura încărcarea modulului întrucât este prezent un “sleep” prin intermediul funcției schedule_timeout.

Pentru TODO 2 extrageți primul element din listă (cel referit de head.next) și puneți câmpul count al structurii de tip ti aferente pe o valoare suficient de mare (10) folosind funcția atomic_set.

6. [1p] Sincronizarea lucrului cu liste

Intrați în directorul 6-list-sync/.

  1. Copiați fișierul 5-list-full/list-full.c (rezolvarea anterioară) în 6-list-sync/list-sync.c.
  2. Folosiți un spinlock sau un read-write lock pentru a sincroniza accesul la lista folosită.
  3. Compilați, încărcați și descărcați modulul de kernel.

Parcurgeți secțiunea Spinlock-uri din laborator.

7. [1p] Testare modul de lucru cu liste

Intrați în directorul 7-list-test/ și parcurgeți conținutul fișierului list-test.c. Vom folosi un modul de testare. Acesta va apela funcții exportate de modulul de la 6-list-sync/. Funcțiile exportate sunt cele descrise cu extern în cadrul fișierului list-test.c.

Pentru exportarea funcțiilor de mai sus, din modulul de la 6-list-sync/ este nevoie de următorii pași:

  1. Funcțiile nu trebuie să fie statice.
  2. Folosiți macro-ul EXPORT_SYMBOL pentru a exporta simbolurile în kernel. De exemplu: EXPORT_SYMBOL(task_info_remove_expired);. Macro-ul trebuie folosit pentru fiecare funcție, după definirea funcției.
  3. Anunțați modulul de test de prezența funcțiilor exportate. După ce ați compilat modulul de la 6-list-sync/, analizaţi fişierul 6-list-sync/Module.symvers, după care copiaţi-l în 7-list-test/.
  4. Eliminați din modulul de la 6-list-sync/ secvența cu evitarea expirării unui element al listei (se bate cap în cap cu ce face testul).
  5. Compilați și încărcați modulul de la 6-list-sync/. Odată încărcat va expune funcțiile exportate și vor putea fi folosite de modulul de test. Puteţi verifica acest lucru căutând numele funcţiilor în /proc/kallsyms înainte şi după încărcarea modulului.
  6. Compilați modulul de test și apoi încărcați-l.
  7. Folosiţi lsmod ca să verificaţi că cele două module s-au încărcat. Ce observaţi?
  8. Descărcați modulul de test din kernel.

Care trebuie să fie ordinea de descărcare a celor două module (cel din 6-list-sync/ și cel de test)? Ce se întâmplă dacă folosiți altă ordine?

Soluții

so2/laboratoare/lab03/exercitii.txt · Last modified: 2017/03/08 13:49 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