This is an old revision of the document!
Cel mai facil și convenabil mod de a face dezvoltare la nivelul nucleului este prin module de kernel. Modulele de kernel reprezintă fișiere obiect care pot aduce în mod dinamic (la rulare) funcționalități nucleului.
Arhiva de suport pentru exerciții se găsește aici. Descărcați arhiva și apoi decomprimați-o folosind comanda
unzip cap-02-tasks.zip
În prealabil, trebuie să refaceți scripturile din codul sursă al nucleului. Pentru aceasta accesați directorul linux-kernel-dev/linux-3.13
și rulați comanda
make scripts
Orice sistem de operare (nu doar Linux) folosește module pentru extinderea în mod dinamic a funcționalităților sistemului de operare. De multe ori acestea sunt forma în care sunt implementatea driverele de dispozitive (device drivers). Înainte de opera modulele este util să putem afla informații despre module.
Sunt două operații frecvente de aflare de informații și de investigare a modulelor de kernel:
Pentru a afișa modulele curente în nucleul Linux folosim comanda
lsmod
Această comandă o putem folosi și pe mașina virtuală VirtualBox și pe mașina virtuală QEMU. O altă formă de afișare a acelorași informații este comanda
cat /proc/modules
Pentru a afișa informații despre un modul în parte se folosește comanda
sudo modinfo <nume-modul>
unde <nume-modul>
este numele modulului. Se pot afișa informații și despre modulele încărcate și cele care nu au fost încărcate.
Modulele se găsesc de regulă în directorul /lib/modules/<kernel-version>
. Comanda modinfo
poate fi folosită doar dacă modulele se găsesc în acel director, motiv pentru care comanda va funcționa doar pe mașina virtuală VirtualBox.
Alte informații, dinamice, despre modulele existente în acel moment în kernel (fie compilate direct, built in, fie module încărcabile/descărcabile) se găsesc în /sys/module/
.
Aflați informații despre un modul de kernel din mașina virtuală VirtualBox și despre un modul de kernel din mașina virtuală QEMU folosind intrările din /sys/module/
.
Multe dintre modulele folosite de nucleu sunt module “de sistem”, adică sunt deja compilate și instalate. De obicei acestea rezită în directorul /lib/modules/<kernel-version>/
.
Aceste module pot fi descărcate încărcate la nevoie. Întrucât aceste module se găsesc doar pe mașina virtuală VirtualBox, doar acolo putem să operăm cu acestea.
Pentru a descărca un modul de sistem de pe mașina virtuală VirtualBox, să spunem modulul lp
, folosim una dintre comenzile:
sudo modprobe -r lp sudo rmmod lp
Dacă se vor afișa din nou modulele din kernel, se va vedea că modulul lp
lipsește.
Pentru a reinsera modulul de kernel lp
avem, din nou, două opțiuni
sudo modprobe lp sudo insmod /lib/modules/3.16.0-4-586/kernel/drivers/char/lp.ko
Comanda insmod
primește calea completă către modulul de kernel, un fișier cu extensia .ko
. Calea completă o putem afla cu ajutorul comenzii modinfo
.
modprobe
poate fi folosită doar pe module de sistem, cele localizate în directorul /lib/modules/<kernel-version/
. Comenzile insmod
și rmmod
pot fi folosite pentru orice fel de module, inclusiv module “custom”, motiv pentru care le vom folosi preponderent de acum încolo.
Copiați modulul lp.ko
în mașina virtuală QEMU, în fsimg/root/modules
și apoi încercați să-l încărcați folosind comanda
insmod lp.ko
De ce nu puteți să încărcați modulul în kernel?
În directorul cap-02-bin/
din arhiva de laborator găsiți modulul de kernel hello.ko
. Vrem să încărcăm și să descărcăm acest modul în mașina virtuală QEMU.
Copiați modulul pe mașina virtuală QEMU și porniți mașina virtuală.
Folosiți insmod
și rmmod
pentru a încărca și descărca modulul din kernel. Observați mesajele afișate în momentul încărcării și descărcării modulului din kernel.
După cum am precizat în capitolul trecut, mașina virtuală QEMU este minalistă și folosită doar pentru a testa (rapid) funcționalitate. Compilarea modulelor de kernel pe care le vom folosi în cadrul mașinii virtuale o vom face pe mașina virtuală VirtualBox.
Pentru a compila un modul de kernel acesta trebuie linkat la versiunea de nucleu corespunzătoare nucleului în care va fi inserat. De aceea, întotdeauna vom referi în procesul de compilare directorul cu sursele nucleului, în cazul de față /home/training/linux-kernel-dev/linux-3.13/
.
În subdirectorul hello/
din directorul cap-02-skel/
avem codul sursă (hello.c
) și fișierele de compilare Makefile
și Kbuild
pentru compilarea modului. În mașina virtuală VirtualBox folosim, în directorul hello/
, comanda
make
pentru a compila modulul de kernel. În urma compilării rezultă fișierul hello.ko
pe care îl vom copia în mașina virtuală QEMU și apoi pornim mașina virtuală și încărcăm și descărcăm modulul de kernel.
Actualizați modulul hello.c
pentru a afișa un mesaj și la descărcarea din kernel, la operația rmmod
.
Folosiți funcția printk
astfel încât mesajele să fie afișate în bufferul kernel-ului (adică să fie afișate cu dmesg
) dar să nu fie afișate la consolă.
printk
, inferior celui afișat în fișierul /proc/sys/kernel/printk
. Nivelurile de logging se găsesc în codul sursă al nucleului, în fișierul include/linux/kern_levels.h
.
Actualizați modulul hello.c
pentru a afișa PID-ul procesului curent atât la încărcarea modulului de kernel cât și la descărcarea acestuia.
current
, găsit în include/linux/sched.h
. Macro-ul este un pointer al structurii struct task_struct
definită, de asemenea, în include/linux/sched.h
.
Actualizați modulul hello.c
pentru a afișa PID-ul și executabilul/comanda aferentă procesului curent, atât la încârcarea modulului în kernel cât și la descărcarea acestuia.
comm
al structurii struct task_struct
aferente.
Actualizați modulul hello.c
pentru a afișa PID-ul și executabilul/comanda aferentă procesului părinte al procesului curent, atât la încârcarea modulului în kernel cât și la descărcarea acestuia.
struct task_struct
care este câmpul cu ajutorul căruia se determină procesul părinte (tot un pointer la o structură de tipul struct task_struct
.
În directorul hook/
din arhiva de suport capitolului se găsește un exemplu de folosire a framework-ului netfilter din cadrul nucleului Linux. Este framework-ul folosit și de utilitarul iptables
.
Parcurgeți codul sursă, observați ce se întâmplă și apoi obțineți modulul și testați-l în mașina virtuală QEMU.
ping
din mașina virtuală VirtualBox către mașina virtuală QEMU
ping 172.20.0.2
Adresa 172.20.0.2
este adresa interfeței eth0
a mașinii virtuale QEMU.
Urmăriți numerele de secvență ale mesajelor ICMP în output-ul comenzii ping
, adică partea cu icmp_seq
.
Și apoi pe mașina virtuală QEMU încărcați modulul hook.ko
. Observați acum care sunt numerele de secvență ale mesajelor ICMP. Observați că acum un pachet din două nu este afișat, pentru că este filtrat de modul.
Descărcați modulul hook.ko
și observați că acum numerele de secvență revin la numere consecutive, nemaifiind filtrate de modul.
Uneori dorim să separăm funcționalitatea unui modul în fișiere multiple pentru a nu încărca tot codul sursă într-un singur fișier. În acea situația avem nevoie de o actualizare a modului în care sunt constituite fișierele Kbuild
și Makefile
.
În directorul multi/
din directorul cap-02-skel/
din arhiva capitolului există un exemplu (academic) de modul de kernel din surse multiple. Urmăriți conținutul acestora și compilați fișierele folosind comanda
make
Urmăriți fișierele intermediare și fișierul modul final.
Copiați fișierul modul final în mașina virtuală QEMU, porniți mașina virtuală QEMU și încărcați și descărcați modululul din kernel.
Creați un modul care să aibă două fișiere cod sursă. Într-un fișier cod sursă sunt implementate funcțiile de inițializare și ieșire ale modulului. În celălalt fișier implementați o funcție care face dump în hexacimal la cel mult 4096 de octeți de la o adresă dată. Adresa dată trebuie să fie adresă de kernel space (>= 0xc0000000
). Funcția este apelată din primul modul.
Compilați modulul, copiați-l în mașina virtuală QEMU și testați-l.
Actualizați modulul de hook prin adăugarea unei noi structuri de operații și a unei funcții care să respingă pachetele de tipul echo reply
care pleacă de la stația locală, o dată la 3 pachete.
hooknum
valoarea NF_INET_LOCAL_OUT
.
Actualizați scriptul hello.c
astfel încât să primească un parametru message
care să fie inițializat la un șir. Acel șir este afișat folosind printk
la inserarea modulului.
cap-02-doc/
din arhiva capitolului.