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 atât în bufferul kernel-ului (adică să fie afișate cu dmesg
) cât și 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
.
include/linux/sched.h
folosiți construcția
#include <linux/sched.h>
La fel veți proceda și în cazul altor fișiere de tip header în care sunt definite structuri, tipuri de date sau macro-uri pe care le folosiți în modulul vostru.
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 în cadrul funcției de inițializare a acestuia.
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
. Este vorba de câmpul hooknum
din cadrul structura icmp_nf_ops
.
Pachetele de tipul echo reply
au câmpul icmp_type
al structurii icmp_hdr
egal cu valoarea ICMP_ECHOREPLY
.
Puteți găsi definițiile de tipuri de câmpuri cu ajutorul cscope
cu o comandă de genul
vi -t ICMP_ECHO
Cscope este un program pentru parcurgerea eficientă a surselor C. Pentru a-l folosi, trebuie generată o bază de date cscope din sursele existente. Într-un tree Linux, este suficientă folosirea make ARCH=x86 cscope
. Precizarea arhitecturii prin variabila ARCH este opțională, dar recomandată; altfel, unele funcții dependente de arhitectură vor apărea de mai multe ori în baza de date.
Cscope poate fi folosit și stand-alone, dar este mult mai util în combinație cu un editor. Pentru a folosi cscope cu Vim, este necesar să instalați ambele pachete și să adăugați următoarele linii în fișierul .vimrc
(mașina din laborator are deja configurările făcute):
if has("cscope") " Look for a 'cscope.out' file starting from the current directory, " going up to the root directory. let s:dirs = split(getcwd(), "/") while s:dirs != [] let s:path = "/" . join(s:dirs, "/") if (filereadable(s:path . "/cscope.out")) execute "cs add " . s:path . "/cscope.out " . s:path . " -v" break endif let s:dirs = s:dirs[:-2] endwhile set csto=0 " Use cscope first, then ctags set cst " Only search cscope set csverb " Make cs verbose nmap <C-\>s :cs find s <C-R>=expand("<cword>")<CR><CR> nmap <C-\>g :cs find g <C-R>=expand("<cword>")<CR><CR> nmap <C-\>c :cs find c <C-R>=expand("<cword>")<CR><CR> nmap <C-\>t :cs find t <C-R>=expand("<cword>")<CR><CR> nmap <C-\>e :cs find e <C-R>=expand("<cword>")<CR><CR> nmap <C-\>f :cs find f <C-R>=expand("<cfile>")<CR><CR> nmap <C-\>i :cs find i ^<C-R>=expand("<cfile>")<CR>$<CR> nmap <C-\>d :cs find d <C-R>=expand("<cword>")<CR><CR> " Open a quickfix window for the following queries. set cscopequickfix=s-,c-,d-,i-,t-,e-,g- endif
Script-ul caută un fișier numit cscope.out
în directorul curent, sau în directoarele părinte ale acestuia. Dacă Vim găsește acest fișier, puteți folosi combinația Ctrl+]
sau Ctrl+\ g
(combinația control-\, urmată de tasta g) pentru a sări direct la definiția cuvântului de sub cursor (funcție, variabilă, structură etc.). Similar, puteți folosi Ctrl+\ s
pentru a merge la locurile unde este folosit cuvântul de sub cursor.
Puteți lua un fișier .vimrc
cscope-enabled (and other goodies) de la https://github.com/ddvlad/cfg/blob/master/_vimrc. Următoarele indicații se bazează pe acest fișier, dar au listate și comenzile de bază vim care obțin același efect.
Dacă există mai multe rezultate (de obicei există) vă puteți deplasa între ele folosind F6
și F5
(:cnext
și :cprev
) sau deschizând o subfereastră nouă cu rezultatele, folosind :copen
. Ca să închideți subfereastra folosiți comanda :cclose
.
Pentru a vă întoarce la locația precedentă, folosiți Ctrl+o
(litera o, nu cifra zero). Comanda poate fi invocată de mai multe ori și funcționează chiar dacă cscope a schimbat fișierul pe care îl editați.
Pentru a merge la definiția unui simbol direct când porniți vim, folosiți vim -t task_struct
. Sau, dacă ați deschis Vim și vreți ulterior să căutați un simbol după nume, puteți folosi comanda :cs find g <symbol_name>
(unde <symbol_name>
este numele simbolului.
Dacă ați găsit mai multe match-uri și dacă ați deschis o subfereastră cu toate match-urile (folosind :copen
) și dacă sunteți în căutarea unui simbol de tip structură, este indicat să căutați în subfereastră (folosind /
– slash) caracterul {
(acoladă deschisă).
cscope
îl puteți obține folosind :cs help
.
Pentru mai multe informații, folosiți help-ul integrat al Vim: :h cscope
sau :h copen
.
Dacă sunteți utilizatori emacs, wiki-ul emacs conține informații relevante pentru configurarea cscope.
Pentru o interfață mai simplă, Kscope este un frontend pentru cscope care foloseşte QT. Este lightweight, foarte rapid și foarte ușor de folosit. Permite căutare folosind expresii regulate, grafuri de apel etc. Kscope nu mai este, în momentul de fața, menținut. Există şi un port al versiunii 1.6 pentru Qt4 şi KDE 4 care păstrează integrarea editorului Kate şi este mai uşor de folosit decât ultima versiune prezentă pe SourceForge.
cscope.out
generat sau dacă s-a stricat, îl puteți genera folosind
make ARCH=x86 cscope
Folosiți direct Vim și comenzile cscope pentru parcurgerea codului sursă cu indicațiile de mai jos.
Determinați fișierul în care sunt definite următoarele tipuri de date:
struct task_struct
struct semaphore
struct list_head
spinlock_t
struct file_system_type
struct task_struct
se caută șirul task_struct
.
De obicei veți obține mai multe match-uri caz în care:
:copen
. Vă apare o fereastră secundară cu toate match-urile.{
), un caracter sigur pe linia de definire a structurii. Pentru căutarea acoladei deschise folosiți, în Vim, construcția /{
.Enter
ca să vă ajungă editorul în codul sursă unde e definită variabila.:cclose
.
Determinați fișierul în care sunt declarate următoarele variabile globale la nivelul nucleului:
sys_call_table
file_systems
current
chrdevs
:cs f g <symbol>
(unde construcția <symbol>
reprezintă numele simbolului căutat).
Determinați fișierul în care sunt declarate următoarele funcții:
copy_from_user
vmalloc
schedule_timeout
add_timer
:cs f g <symbol>
(unde construcția <symbol>
reprezintă numele simbolului căutat).
Parcurgeți secvența de structuri:
struct task_struct
struct mm_struct
struct vm_area_struct
struct vm_operations_struct
Adică parcurgeți din aproape în aproape structurile: accesați o structură și apoi găsiți câmpuri cu tipul de date al următoarei structuri, accesați-o pe aceasta etc. Rețineți în ce fișiere sunt definite; o să vă fie utile la alte laboratoare.
cscope
) atunci când sunteți plasați cu cursorul pe acesta, folosiți construcția Ctrl+]
.
Pentru a reveni în match-ul anterior (înante de căutare/salt) folosiți construcția Ctrl+o
. Pentru a avansa în căutare (pentru a reveni la match-urile de dinainte de Ctrl+o
) folosiți construcția Ctrl+i
.
La fel ca mai sus, parcurgeți secvența de apeluri de funcții:
bio_alloc
bio_alloc_bioset
bvec_alloc
kmem_cache_alloc
slab_alloc
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 și la link-urile din secțiunea de resurse:
Module Parameters