This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
linux-kernel-dev:capitole:capitol-03 [2015/09/07 02:13] razvan [Resurse utile] |
linux-kernel-dev:capitole:capitol-03 [2016/08/31 07:50] (current) razvan [Resurse utile] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Drivere de dispozitiv ====== | + | ====== 3. Kernel API ====== |
===== Subiecte abordate ===== | ===== Subiecte abordate ===== | ||
- | * API-ul de kernel: printing, lucrul cu memoria, liste | + | * API-ul expus de kernel pentru module de kernel: printing, lucrul cu memoria, liste |
- | * Erori în kernel, depanarea erorilor: oops-uri și panic-uri, stack trace, objdump, addr2line | + | * Erori în kernel, depanarea erorilor |
- | * Dispozitive văzute în user space: major, minor, /dev | + | |
- | * Dispozitive virtuale | + | |
- | * Operații pe dispozitive: citire, scriere, structura file operations, structura cdev | + | |
- | * Generator de 1 | + | |
- | * Reply device | + | |
- | * Character numbering device | + | |
===== Resurse utile ===== | ===== Resurse utile ===== | ||
- | * TODO | + | * [[https://www.kernel.org/doc/htmldocs/kernel-api/|The Linux Kernel API]] |
+ | * [[http://ocw.cs.pub.ro/courses/so2/laboratoare/lab03|SO2: Kernel API]] | ||
===== Exerciții ===== | ===== Exerciții ===== | ||
Line 22: | Line 19: | ||
</code> | </code> | ||
- | Alocarea de memorie în mod dinamic. Atenție la dezalocare. | + | ==== Alocare dinamică a memoriei ==== |
- | Dezalocare într-un alt modul | + | Alocarea dinamică a memoriei în nucleul Linux se face, de regulă, folosind apelul ''kmalloc''. Acesta alocă memorie rezidentă (care ocupă în permanență memoria până la allocare). |
- | (tutorial) Afișarea listei de procese a sistemului | + | În directorul ''alloc/'' din arhiva de suport există un modul de kernel care alocă ''128KB'' de RAM într-o buclă ''for''. Întrucât modulul doar alocă memoria, nu o și dezalocă aceasta rămâne ocupată. |
- | (exercițiu) Afișarea zonelor de memorie ale procesului | + | Compilați modulul și apoi încărcați-l și descărcați-l de mai multe ori în kernel. Observați după fiecare încărcare/descărcare dimensiunea memoriei ocupate. Pentru a afla această informație puteți folosi una dintre comenzile de mai jos:<code> |
+ | free | ||
+ | cat /proc/meminfo | ||
+ | </code> | ||
+ | |||
+ | <note> | ||
+ | Dacă s-ar aloca foarte multă memorie sau dacă bucla for ar avea o durată mai mare, sistemul va rămâne fără memorie și la un moment dat va îngheța. În momentul în care sistemul are puțină memorie se activează OOM (//Out of Memory//) care începe să omoare procese ca să elibereze memoria. | ||
+ | </note> | ||
+ | |||
+ | ==== Eliberarea memoriei alocate dinamic == | ||
+ | |||
+ | Pentru anularea încărcării memoriei, trebuie să eliberăm memoria alocată dinamică. | ||
+ | |||
+ | Pentru aceasta definiți un vector în care rețineți pointerii către zonele de memorie alocate. Apoi, în funcția de ''exit'' a modulului, folosiți funcția ''kfree()'' pentru a elibera memoria astfel alocată. | ||
+ | |||
+ | <note tip> | ||
+ | Vectorul trebuie definit ca variabilă globală ca să fie accesibil atât în funcția de inițializare cât și în funcția de ''exit''. | ||
+ | </note> | ||
+ | |||
+ | ==== Afișarea listei de procese a sistemului ==== | ||
+ | |||
+ | Nucleul se ocupă de gestionarea sistemului și una dintre structurile esențiale este lista. În general nucleul folosește liste dublu înlănțuite pentru a permite parcurgeri în ambele sensuri. | ||
+ | |||
+ | În general un modul de kernel va avea nevoie să parcurgă listele definite în kernel. În anumite situații va și adăuga și șterge elemente dar cea mai frecventă operație este parcurgerea. | ||
+ | |||
+ | În directorul ''print-processes/'' din arhiva de suport a capitolului se găsește o implementare de modul de kernel care afișează PID-urile și comenzile proceselor sistemului (referite de pointeri la structuri de tipul ''task_struct'') atât în funcția de inițializare cât și în funcția de ieșire. În fiecare funcție folosește alt format: în inițializare parcurge în mod clasic lista de procese începând cu procesul curent, în vreme ce în funcția de ieșire folosește macro-ul ''for_each_process''. | ||
+ | |||
+ | ==== Afișarea listei de procese copil a procesului init ==== | ||
+ | |||
+ | Afișați pentru procesul init, PID-ul și comanda proceselor sale copil. | ||
+ | |||
+ | <note tip> | ||
+ | Structura ''task_struct'' a procesului init este indicată de variabila globală ''init_task''. | ||
+ | |||
+ | Santinela listei dublu înlănțuite este dată de câmpul ''children'' al structurii ''task_struct''. | ||
+ | |||
+ | Folosiți macro-ul ''list_for_each'' pentru a parcurge procesele copil și apoi macro-ul ''list_entry'' pentru a obține structura ''task_struct'' pentru fiecare proces din listă. Câmpul din cadrul structurii ''task_struct'' care definește structura de tip listă care leagă toate procesele copil este ''sibling''. | ||
+ | </note> | ||
+ | |||
+ | ==== Afișarea zonelor de memorie ale procesului ==== | ||
+ | |||
+ | Pentru acomodarea cu lucrul cu liste parcurgeți zonele de memorie virtuală ale procesului curent. Pentru fiecare zonă afișați adresa de start și cea de sfârșit. | ||
+ | |||
+ | <note tip> | ||
+ | O zona de memorie virtuală este dată de structura ''vm_area_struct'', definită în ''include/linux/mm_types.h''. | ||
+ | |||
+ | Primul element al listei de zone este câmpul ''mmap'' al structurii ''mm_struct'' care definește spațiul virtual de adresă al procesului. Iar pointerul la structura care definește spațiul virtual de adresă al procesului este dat de câmpul ''mm'' al structurii ''task_struct''. | ||
+ | </note> | ||
+ | |||
+ | ==== Depanarea unui oops ==== | ||
+ | |||
+ | Atunci când nucleul întâlnește o eroare afișează un mesaj de eroare similar //Segmentation fault// numit //oops//. Este o indicație că ceva nepotrivit a avut loc; sistemul de operare continuă execuția, dar este posibil ca eroarea să se propage. | ||
+ | |||
+ | Erorile grave se prezintă în formă de //kernel panic// și conduc la înghețarea sistemului. | ||
+ | |||
+ | În directorul ''oops/'' din arhiva de suport a laboratorului se găsește implementat un modul care accesează un pointer ''NULL'' rezultând în //oops//. Încărcați modulul pentru a observa acest lucru. Observați că nu se mai permite nucleului să fie descărcat pentru a nu cauza mai multe pagube. | ||
+ | |||
+ | Atunci când avem module de kernel complexe e dificil de verificat unde a apărut eroarea. Pentru aceasta putem folosi utilitarul ''addr2line'' care realizează o asociere între adresa în care a apărut oops-ul și linia din fișier. Folosirea ''addr2line'' depinde de două informații: | ||
+ | - Adresa unde a fost încărcat modulul, identificabilă din fișierul ''/proc/modules''. | ||
+ | - Adresa unde a apărut oops-ul, identificabilă din mesajul de oops, parcurgând registrul ''EIP'' (//instruction pointer//). | ||
+ | |||
+ | Se calculează offset-ul între cele două adrese, adică diferențaa dintre adresa unde a apărut oops-ul și adresa unde a fost încărcat modulul. Cu acest offset se rulează, **pe mașina virtuală VirtualBox**, comanda<code> | ||
+ | addr2line -e <module-name.o> <offset> | ||
+ | </code> | ||
+ | unde ''<module-name>.o'' este numele fișierului obiect cu extensia ''.o'' (**nu** ''.ko'') iar ''<offset>'' este offset-ul calculat anterior. | ||
+ | |||
+ | În cazul nostru comanda de rulat pe mașina virtuală VirtualBox va fi<code> | ||
+ | addr2line -e oops_mod.o 0x17 | ||
+ | </code> | ||
- | (tutorial) Depanarea unui oops | + | Output-ul acestei comenzi este linia din cod care a cauzat oops-ul. Așa cum era de așteptat această linie este chiar linia în care se derefernțiază pointer-ul inițializat la ''NULL''. |
- | (tutorial) Char device care face ioctl și afișare | + | ==== Interpretarea unui stacktrace ==== |
- | (exercițiu) Char device care face alocare și dezalocare cu ioctl | + | În directorul ''workqueue-bad/'' avem o implementare defectuoasă a afișării la 5 secunde a unui mesaj. Atunci când inserăm modulul și apoi îl eliminăm primim eroare. Motivul este că există încă planificat un ''work'' iar modulul este eliminat ducând la eliberarea zonelor alocate. Consecința este mesajul de eroare. |
- | (tutorial) Generator de "anaaremere" | + | Captura mesajului de eroare, prin stacktrace-ul afișat, ne oferă informații despre ce cauzează eroarea. |
- | (exercițiu) Generator de caractere de "1" (la nesfârșit) | + | <note tip> |
+ | Ce se întâmplă cu variabila **locală** ''work'' în momentul în care funcția ''scchedule_work()'' se încheie? | ||
+ | </note> | ||
- | (exercițiu) Reply device: ce scrii, apoi primești | ||
- | (exercițiu) La citire întoarce lungimea ultimului șir scris |