Sei sulla pagina 1di 23

i

Universit degli studi di Napoli

"Parthenope"
Facolt di scienze e tecnologie
Corso di Laurea in Informatca

Linux Device Drivers

Autori:
Raffaela DAniello 0124000637
Luisa Barbarino 0124000484
Daniele Ioviero 0124000459
Fabio Nisci 0124000074

Relatore:
Prof. Alfredo Petrosino

Esame: Sistemi Operativi


Anno accademico 2015 - 2016

ii

2015. All rights reserved.


No part of this work may be reproduced in any form without prior permission of the
author.
This work is for operating system exam.
NOT for commercial use.

Indice
Indice

iii

Elenco delle figure

iv

Introduzione
1.1
Introduzione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Ruolo dei dispositivi di periferica . . . . . . . . . . . . . . . . . . . .
1.3
Kernel Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1
1
1
1

I Moduli
2.1
Le Classi di dispositivi e moduli . . .
2.2
Problemi di sicurezza . . . . . . . . .
2.3
Moduli caricabili . . . . . . . . . . .
2.4
I moduli di costruzione ed esecuzione
2.5
Compliazione ed esecuzione . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

4
4
4
5
5
6

Gestione e comunicazione
3.1
Modulo Kernel VS Applicazioni Utente . . . . .
3.2
Polling VS Interrupt . . . . . . . . . . . . . . .
3.3
Interrupt Handling . . . . . . . . . . . . . . . .
3.4
I/O Bloccante . . . . . . . . . . . . . . . . . . .
3.5
Chiamata di sistema da unapplicazione utente
3.6
Sleeping . . . . . . . . . . . . . . . . . . . . . .
3.7
Wait Queue . . . . . . . . . . . . . . . . . . . .
3.8
System call Select/Poll . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

8
8
9
9
10
10
11
11
12

Build and run


4.1
Costruiamo un driver . . . . . . . . . . . . . . . . . . . . . . . . . . .

14
14

Debug

18

iii

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

ELENCO DELLE FIGURE

5.1

Debugging moduli kernel: KGDB . . . . . . . . . . . . . . . . . . . .

iv

18

Elenco delle figure


1.1

Linux Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3.1
3.2

device_open() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
device_poll() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

10
12

Capitolo 1

Introduzione
1.1

Introduzione

Uno dei molti vantaggi del Sistema Operativo Linux che il suo interno aperto a tutti.
Il kernel Linux un corpo grande e complesso di codice. I drivers di periferica, sono distinte
scatole nere che fanno s che un particolare pezzo di hardware risponda ad un interfaccia
di programmazione ben definita. Le attivit dellutente sono effettuate tramite una serie
di chiamate standardizzate indipendenti dal driver specifico. Quindi i driver possono essere
costruiti separatamente dal resto del kernel e inseriti a runtime quando necessario.

1.2

Ruolo dei dispositivi di periferica

Come programmatore, si in grado di fare le proprie scelte circa il driver, e scegliere un accettabile compromesso tra il tempo di programmazione richiesto e la flessibilit del risultato,
ovvero il ruolo di un driver di periferica fornisce il meccanismo, non la politica. La maggior parte dei problemi di programmazione possono infatti essere divise in due parti: quali
funzionalit devono essere forniti (il meccanismo) e come possono essere utilizzate quelle
capacit (la politica). Esempio, la gestione Unix del display grafico diviso tra il server
X, che conosce lhardware e offre uninterfaccia unificata per programmi utente, e il gestore
delle finestre e di sessione, che implementano una particolare politica senza sapere nulla
lhardware. Gli utenti possono utilizzare lo stesso gestore di finestre su hardware diversi, e
viceversa diversi utenti possono eseguire diverse configurazioni sulla stessa workstation.
inoltre possibile guardare il driver da una prospettiva diversa: si tratta di uno strato di software che si trova tra le applicazioni e il dispositivo vero e proprio. Caratteristiche tipiche:
Queste includono il supporto per entrambi funzionamento sincrono e asincrono, la capacit
di essere aperto pi volte, la capacit di sfruttare tutte le funzionalit dellhardware, e la
mancanza di strati di software a semplificare le cose o fornire le operazioni legate alla
politica.

1.3

Kernel Linux

Gestione dei processi: Il kernel si occupa della creazione e distruzione dei processi,
della loro comunicazione.

CAPITOLO 1. INTRODUZIONE

Gestione della memoria: Le varie parti del kernel interagiscono con il sottosistema di
gestione della memoria attraverso una serie di chiamate di funzione, che vanno dal
semplice malloc ad altre pi complesse.
Filesystem: quasi tutto in Unix pu essere trattato come un file. Il kernel costruisce un
file system strutturato in cima hardware non strutturato, e la conseguente estrazione
di file molto utilizzato in tutto il sistema.
Controllo dispositivi funzionamento: a quasi tutti i sistemi associato un dispositivo
fisico. Con leccezione del processore, memoria e poche altre entit, qualsiasi e tutte le
operazioni di controllo del dispositivo vengono eseguite da codice (Driver di periferia)
che specifico per il dispositivo. Il kernel deve avere incorporato un device driver per
ogni periferica presente sul sistema, dal disco rigido alla tastiera e lunit a nastro.
Networking: deve essere gestita dal sistema operativo, perch la maggior parte delle
operazioni di rete non sono specifici di un processo: i pacchetti in ingresso sono eventi
asincroni. I pacchetti devono essere raccolti, identificati e spediti prima che un processo
si prenda cura di loro. Inoltre, tutti i problemi di risoluzione di routing e di indirizzo
sono implementati allinterno del kernel.

CAPITOLO 1. INTRODUZIONE

Figura 1.1: Linux Kernel

Capitolo 2

I Moduli
2.1

Le Classi di dispositivi e moduli

Character devices Un dispositivo a caratteri (char) pu essere letto come un flusso di


byte (come un file); Ai dispositivi Char si accede per mezzo di nodi del file system.
Lunica differenza rilevante tra un dispositivo char e un file normale che si pu sempre
spostare avanti e indietro nel file regolare, mentre la maggior parte dei dispositivi char
sono canali di dati solo, e quindi si pu solo accedere in modo sequenziale. Esistono,
tuttavia, i dispositivi char che sembrano aree dati, e ci si pu spostare avanti e indietro.
I dispositivi a blocchi: Un dispositivo a blocchi un dispositivo (ad esempio, un disco)
che pu ospitare un filesystem. Nella maggior parte dei sistemi Unix, un dispositivo di
blocco pu gestire solo operazioni I /O che consentono di trasferire uno o pi blocchi
interi, che sono di solito 512 byte di lunghezza. Linux, invece, consente allapplicazione di leggere e scrivere un dispositivo a blocchi, come un dispositivo char e quindi
permette il trasferimento di un qualsiasi numero di byte alla volta.
Le interfacce di rete: incaricato di inviare e ricevere pacchetti di dati, guidato dal
sottosistema di rete del kernel, senza sapere come le singole operazioni mappano i
pacchetti da trasmettere. Molte le connessioni di rete (in particolare quelli che utilizzano il protocollo TCP) sono stream-oriented, ma i dispositivi di rete sono, in genere,
progettati intorno la trasmissione e la ricezione di pacchetti. Un driver di rete non sa
nulla di singole connessioni; gestisce solo i pacchetti. Il kernel per comunicare con un
driver di dispositivo di, chiama le funzioni relative alla trasmissione dei pacchetti.
USB: il dispositivo stesso si presenta nel sistema come un dispositivo char (una porta
seriale USB, per esempio), un dispositivo a blocchi (un lettore di schede di memoria
USB), o un dispositivo di rete (uninterfaccia Ethernet USB)

2.2

Problemi di sicurezza

Nella distribuzione ufficiale del kernel, solo un utente autorizzato pu caricare moduli; la
chiamata di sistema init_module controlla se il processo chiamante autorizzato a caricare
un modulo nel kernel.

CAPITOLO 2. I MODULI

2.3

Moduli caricabili

Una delle buone caratteristiche di Linux la possibilit di estendere in fase di esecuzione


linsieme delle funzionalit offerte dal kernel. Ci significa che possibile aggiungere funzionalit al mentre il sistema attivo e funzionante. Ogni pezzo di codice che pu essere
aggiunto al kernel in fase di esecuzione chiamato un modulo.

2.4

I moduli di costruzione ed esecuzione

Il driver di periferica progettato, caricato, scaricato come modulo del kernel


Sviluppo: - Sorgente del kernel - Compilazioni - Controllare la distribuzione Linux su
come configurare.
Listing 2.1: "Esempio: modulo HelloWord"
1
3
5

/*
* " Hello , world !" minimal kernel module
*
* Fabio Nisci < f a b i o n i s c i @ s t u d e n t i . uniparthenope . it >
*
*/

7
9
11
13

/*
* The below are header files provided by the kernel which are
* required for all modules . They include things like the definition
* of the module_init () macro .
*/
# include < linux / init .h >
# include < linux / module .h >

15
17
19

/*
* This is the init function , which is run when the module is first
* loaded . The __init keyword tells the kernel that this code will
* only be run once , when the module is loaded .
*/

21
23
25
27
29
31

static int __init


hello_init ( void )
{
printk ( " Hello , world !\ n " ) ;
return 0;
}
/*
* The below macro informs the kernel as to which function to use as
* the init function .
*/

33

module_init ( hello_init ) ;
35
37
39

/*
* Similary , the exit function is run once , upon module unloading , and
* the module_exit () macro identifies which function is the exit
* function .
*/

41
43

static void __exit


hello_exit ( void )

CAPITOLO 2. I MODULI

{
printk ( " Goodbye , world !\ n " ) ;

45

}
47

module_exit ( hello_exit ) ;
49
51
53
55
57
59
61

/*
* MODULE _LICEN SE () informs the kernel what license the module source
* code is under , which affects which symbols it may access in the
* main kernel . Certain module licenses will " taint " the kernel ,
* indicating that non - open or untrusted code has been loaded .
* Modules licensed under GPLv2 do not taint the kernel and can access
* all symbols , but declaring it so is a legal statement that the
* source code to this module is licensed under GPLv2 , and so you must
* provide the source code if you ship a binary version of the module .
*/
MODU LE_LIC ENSE ( " GPL " ) ;
M O D U L E _ D E S C R I P T I O N ( " \" Hello , world !\" minimal module " ) ;
MODU LE_VER SION ( " printk " ) ;

Spiegazione:
Questo modulo definisce due funzioni, una per essere invocata quando il modulo caricato
nel kernel (hello_init) e una per quando il modulo viene rimosso (hello_exit). Le linee
module_init e module_exit utilizzano macro speciali del kernel per indicare il ruolo di
queste due funzioni. Unaltra macro speciale (MODULE_LICENSE) utilizzato per dire
al kernel che questo modulo ha una licenza gratuita; senza una tale dichiarazione, il kernel si
lamenta quando viene caricato il modulo. La funzione printk definita nel kernel di Linux e
messo a disposizione moduli; si comporta in modo simile alla funzione printf della libreria C
standard. Il kernel ha la propria funzione di stampa perch corre da sola, senza laiuto della
libreria C. Il modulo pu chiamare printk perch, dopo insmod il modulo legato al kernel e
pu accedere simboli pubblici del kernel (funzioni e variabili, come dettagliato nella sezione
successiva). Il KERN_ALERT indica la priorit del messaggio. Abbiamo specificato la
massima priorit in questo modulo, perch un messaggio con la priorit predefinita potrebbe
non presentarsi ovunque utile, a seconda della versione del kernel in esecuzione, la versione
del klogd daemon, e la configurazione. possibile ignorare questo problema, per ora.
possibile verificare il modulo con la insmod e rmmod utilities, come illustrato di seguito.
Notare che solo il superuser pu caricare e scaricare un modulo.

2.5

Compliazione ed esecuzione
Listing 2.2: "Makefile"

# obj - m is a list of what kernel modules to build . The . o and other


# objects will be automatically built from the corresponding . c file # no need to list the source files explicitly .

obj - m := hello_printk . o
6
8
10
12

# KDIR is the location of the kernel source . The current standard is


# to link to the associated source tree from the directory containing
# the compiled modules .
KDIR := / lib / modules / $ ( shell uname -r ) / build
# PWD is the current working directory and the location of our module
# source files .

CAPITOLO 2. I MODULI

14

PWD

16

# default is the default make


# with a working directory of
# source and compile only the
default :
$ ( MAKE ) -C $ ( KDIR ) M = $ ( PWD )

18
20

:= $ ( shell pwd )
target . The rule here says to run make
the directory containing the kernel
modules in the PWD ( local ) directory .
modules

Spiegazione: Si prega di notare ancora una volta che, per questa sequenza di comandi,
necessario disporre di un kernel configurato correttamente e costruita in un luogo dove
il makefile in grado di trovarlo (/usr/src/linux2.6.10 nellesempio mostrato). Otteniamo
nei dettagli di come i moduli sono costruiti nella sezione Compilazione e caricamento. A
Seconda del meccanismo che il sistema utilizza per fornire le linee di segnalazione, loutput
potrebbe essere diverso. In particolare, la schermata precedente stata presa da una console
di testo; se si esegue insmod e rmmod da un emulatore di terminale in esecuzione con il
sistema a finestre, non vedrete nulla sul vostro schermo. Il messaggio va a uno dei file
di registro di sistema, come /var /log /messaggi (il nome del file effettivo varia tra le
distribuzioni Linux. Come potete vedere, la scrittura di un modulo non cos difficile come
si potrebbe aspettare, almeno, fino a quando il modulo non tenuto a fare qualcosa di
utile. La parte difficile capire il vostro dispositivo e su come ottimizzare le prestazioni.
Andiamo pi a fondo la modularizzazione in questo capitolo e lasciare i problemi specifici
del dispositivo per i capitoli successivi.

Capitolo 3

Gestione e comunicazione
3.1

Modulo Kernel VS Applicazioni Utente

Compariamo un modulo kernel ad unapplicazione utente.


I moduli kernel devono essere inizializzati (init) e terminati (exit) in modo attento. Il
compito della funzione init di preparare, per successive invocazioni, le funzioni del
modulo. un approccio molto simile alla programmazione event-driven anche se con
delle differenze; infatti, nei moduli abbiamo la funzione exit che deve de-allocare tutto
quello che stato costruito con la init.
Nel programma helloword abbiamo visto tali funzioni implementate, ma questo era
tutto quello che cera nel codice; normalmente la costruzione di un driver impiega molto pi codice. Ad esempio, se abbiamo bisogno di un buffer dove memorizzare dati:
allochiamo dinamicamente il size del buffer con una malloc() e non dobbiamo dimenticare di effettuare una free() altrimenti il programma potrebbe andare in segmentation
fault ed uscire. Ma quando costruiamo un modulo kernel ci troviamo appunto in spazio kernel e se dal driver di dispositivo andassimo ad accedere ad una locazione non
inizializzata avremmo problemi molto pi seri di un segmentation fault perch danneggeremmo lintegrit del kernel ed anche ad esecuzioni successive potremmo aver reso
inconsistente lo stato del sistema operativo. Per questo molto importante effettuare
linizializzazione e la terminazione cos come lallocazione di memoria.
Non possibile usare printf, esiste solo la printk
Bisogna includere i file header del kernel linux (es <include/linux>)
Bug nel driver di dispositivo possono causare il crash del kernel
Eseguito nello spazio kernel quindi ho libero accesso a tutte risorse (attenzione)
Maggiore concorrenza nei moduli kernel che nelle applicazioni utente quindi bisogna
tener conto della gestione delle interrupt, timers e nei moderni SO SMP support
(supporto symmetric multiprocessing)
Come risultato il driver di dispositivo deve essere rientrante, gestire la concorrenza ed
evitare race condition

CAPITOLO 3. GESTIONE E COMUNICAZIONE

3.2

Polling VS Interrupt

Il polling e le interrupt sono due modi per far comunicare il processore ed il dispositivo.
Ricordiamo che il polling la verifica ciclica di tutte le unit/periferiche da parte del SO;
continuiamo a leggere la porta usando istruzioni continuamente finch non arrivano dati con
cui lavorare. Questo modo di ricezione dei dati pu essere appropriato solo per dispositivi
che hanno un I/O ad alta velocit. Questo perch arrivano molti dati velocemente e il
processore deve rimanere al passo. Quando, invece, abbiamo a che fare con dispositivi pi
lenti o in cui non ci si aspetta che i dati arrivino continuamente, una interrupt sar molto
pi appropriata, altrimenti con il polling sprecheremmo molti cicli di CPU controllando la
porta inutilmente. Una interrupt asincrona perch non sappiamo quando arriver levento
che la richiamer; basata sul bisogno del dispositivo, cio vogliamo che a prescindere dal
flusso dei dati il processore riesca a gestirli tutti Consideriamo unapplicazione utente il cui
scopo quello di suonare un allarme se il sensore che legge i dati in input legge valori in un
dato range. Diciamo che quando ci troviamo in quel range vogliamo agire in qualche modo
sui dati, ma non sappiamo quando questa situazione accadr e le operazioni del dispositivo
sono molto pi lente rispetto a quelle del processore. In questo caso luso di una interrupt
pi appropriato cos da non controllare sempre in polling, ma gestire linterrupt solamente
quando levento accade.

3.3

Interrupt Handling

Una interrupt un segnale che lhardware pu inviare per ottenere lattenzione del processore. Abbiamo spiegato come le interrupt sono gestite dal processore. Le interrupt del
processore sono gestite dal IRS (interrupt routine service). Tali routine sono essenzialmente
programmi che il processore deve eseguire allaccadere delle interrupt. Quindi allaccadere dellinterrupt dal dispositivo bisogna eseguire delle azioni. Lammontare di tali azioni
dipende dal dispositivo. Ci sono delle restrizione dellISR:
1. esso non eseguito nel contesto di un processo. Sappiamo che il SO pu gestire pi
processi/thread di tipo utente, ma quando ci troviamo in spazio kernel non abbiamo
tante informazioni sui processi/thread di tipo kernel e il loro contesto rispetto a quelli
utente; lISR non eseguito nello spazio utente, ma esso parte del SO quindi non
possiamo trasferire dati da/a lo spazio utente.
2. il processore non DEVE dormire quando nellISR. NellISR possiamo avere system
call, ma queste non possono essere bloccanti, cio non possiamo eseguire chiamate
come la wait_event, bloccare un semaforo/scheduler, etc. perch queste chiamate di
sistema potrebbero mettere lintero SO in stato sleep. Possiamo lasciare processi
utente dormire, ma non una buona idea per processi kernel.
Il ruolo dellISR:
da feedback al dispositivo sulla ricezione dellinterrupt (tramite la pulizia di un flag)
lettura/scrittura dei dati tipicamente avviene qui
pulizia dellINTERRUPT BIT
risveglia processi dormienti sul dispositivi in attesa di qualche evento

CAPITOLO 3. GESTIONE E COMUNICAZIONE

3.4

10

I/O Bloccante

Unapplicazione utente richiede qualcosa da un dispositivo (normalmente tramite una system


call). Quindi possiamo aprire il file del dispositivo e fare una read e il kernel reindirizzer
quella read sulla funzione appropriata del dispositivo. Se al momento della read i dati sono
disponibili nel dispositivo, essi vengono letti; ma se i dati non sono disponibili possiamo
decidere di far ritornare la funzione immediatamente (con un 1 per indicare errore) o laltra
opzione di bloccare loperazione mettendo il processo utente in sleep. Stesso discorso per
la write, se non c il buffer o il dispositivo troppo impegnato possiamo mettere il processo
in sleep. Il processo chiamante non si interessa di tali problemi, esegue solamente le system
call.I programmatori semplicemente invocano read e write e si aspettano che la funzione
ritorni dopo aver terminato il lavoro. Se vogliamo usare il blocking I/O dobbiamo far si che
sia il driver di dispositivo a gestirlo. Il driver deve:
bloccare il processo chiamante
metterlo a dormire finch la richiesta servita e pu procedere

3.5

Chiamata di sistema da unapplicazione utente

Figura 3.1: device_open()


Questa unillustrazione che mostra cosa accade quando richiamiamo una system call da
unapplicazione utente. Il riquadro blu lapplicazione utente (spazio utente); la prima cosa
da fare aprire il file del mio dispositivo my device assumendo di avere il minus number
e major number quando creiamo il file. Il major number identifica il driver associato al

CAPITOLO 3. GESTIONE E COMUNICAZIONE

11

dispositivo. I moderni kernel di Linux associano molti dispositivi allo stesso major number.
Il minor number e; usato dal kernel per determinare esattamente a quale dispositivo ci si
sta riferendo. In base a come scritto il driver possiamo ottenere o il puntatore diretto al
dispositivo dal kernel o usare il minor number come indice in un array di dispositivi. Proprio
grazie alla coppia minus+major number il kernel sar in grado di identificare il file e linkarlo
al modulo kernel mydriver. Una read allinterno dellapplicazione utente sar collegata ad
una funzione del modulo kernel grazie alla struttura file_operations che fa corrispondere
la .read a device_read. A questo punto controlliamo la lunghezza del buffer, se ci sono
dati disponibili; se tutto ok faccio la copy_to_user (API del kernel) cosicch i dati sul
dispositivo vengano inviati al processo utente tramite il buffer. Altrimenti se non abbiamo
dati in entrata allora mettiamo lapplicazione utente a dormire.

3.6

Sleeping

Cosa vuol dire che un processo sta dormendo? Noi sappiamo che sistema operativo usa lo
scheduler per dare un tipo di ordinamento al susseguirsi dei processi o dei thread, avvalendosi
dellutilizzo di una coda. Sappiamo che gli stati dei processi sono diversi, tra cui troviamo
lo stato sleeping. Un processo che si trova nello stato sleeping, vuol dire che viene rimosso
dalla coda di esecuzione dello scheduler. finch qualche evento futuro non cambia lo stato, il
processo non sar in scheludato sulla CPU e, di conseguenza, non verr eseguito. Per far s
che un processo vada nello stato sleeping in modo sicuro, bisogna osservare un paio di regole
importanti. La prima regola che non bisogna mai passare allo stato sleeping quando
ci si trova in un contesto atomico (fetch decode execute). Ricordiamo che un contesto
atomico si ha quando diverse operazioni devono essere eseguite senza alcun tipo di accesso
concorrente. La seconda regola da osservare che non possibile impostare lo stato sleeping
se gli interrupt sono disabilitati. Una cosa da ricordare e che quando si sveglia un processo
dallo Stato sleeping, non possiamo sapere quanto tempo rimasto fuori dalla CPU e ci
che, nel frattempo, pu essere cambiato. Un altro punto rilevante che un processo non
pu dormire finch non si assicurato che qualche altro processo, da qualche altra parte,
possa risvegliarsi.

3.7

Wait Queue

Abbiamo prima menzionato lutilizzo di una coda. La coda delle attese una struttura
kernel, ed una lista dei processi in attesa di uno specifico evento. Con questa struttura delle code di attesa, il kernel ha la possibilit di trovare i processi dormienti nel caso
in cui, nel prossimo futuro, decide di risvegliarli. Il driver di un qualsiasi device pu utilizzare questa struttura, e pu anche creare una specifica coda ed utilizzarla per i propri
scopi. La coda delle attese gestita da una struttura chiamata wait queue head e definita nel file <wait.h>. una struttura molto semplice, composta da spinlock ed una
linked list. Questa struttura contiene informazioni relative al processo dormiente ed inoltre a come si vorrebbe essere svegliati. La wait queue pu essere definita e inizializzato
staticamente con DECLARE_WAIT_QUEUE_HEAD (nome) oppure dinamicamente con
wait_queue_head_t nome_coda e init_waitqueue_head (& nome_coda);

CAPITOLO 3. GESTIONE E COMUNICAZIONE

3.8

12

System call Select/Poll

Le applicazioni che usano lI/O non bloccante, spesso usano le chiamate di sistema poll,
select ed poll. Queste chiamate di sistema permettono ad un processo di determinare se
possibile leggere o scrivere su uno o pi file aperti senza bloccare nulla. Queste chiamate di
sistema possono anche bloccare un processo finch un descrittore di file diventa disponibile
per la lettura o scrittura. Poll/Select vengono spesso richiamate in applicazioni che devono
utilizzare pi flussi di ingresso o uscita senza restare bloccati su uno di questi. La chiamata
di sistema poll stata aggiunta per ampliare la scala della funzione polling, agendo su
migliaia descrittori di file. Per utilizzare queste chiamate di sistema necessario il supporto
del driver del device, coinvolgendo quindi, un descrittore di file associato al driver. Se
guardiamo questo codice esempio:

Figura 3.2: device_poll()


Spiegazione:
Consideriamo il dispositivo mydevice (user space) mappato nel modulo kernel my driver (kernel space). La struttura file_operations permette lassociazione delle funzioni richiamate nelluser space a quelle contenute in mydriver in kernel space. Per esempio .read
andr ad eseguire la funzione device_read e cos via tutte le altre. Partendo dallapplicazione utente mydevice apriamo il file e chiamiamo la funzione poll che prima di chiamare
la system call vera e propria popola la struttura mypollfd con due campi: file handler ed
eventi. timeout indica quando vogliamo che la poll faccia timeout. Quando la funzione
poll in mydevice verr richiamata il kernel la reindirizzer verso la funzione device_poll.
Al suo interno troviamo poll_wait che unAPI del kernel (mai usarla nelluser space)
che metter in stato di sleep lapplicazione utente nella waiting queue. Ad un certo punto
avremo bisogno di risvegliare il processo addormentato, il risveglio dipende dal dispositi-

CAPITOLO 3. GESTIONE E COMUNICAZIONE

13

vo, ma nella maggior parte dei casi ci avvaliamo delle interrupt per risvegliare i processi.
Il blocco arancione mette in risalto la IRS che risveglia il processo. La wake_up avr
bisogno dellelemento della wait queue da risvegliare e il processo riprender lesecuzione.

Capitolo 4

Build and run


4.1

Costruiamo un driver

Il driver che costruiremo appartiene alla classe dei character devices. Il dispositivo implementa la lettura/scrittura di un carattere. Anche non essendo molto utile ai fini pratici,
fornisce un valido esempio sul funzionamento completo di un driver. semplice da implementare, in quanto non si interfaccia a nessun dispositivo hardware reale (oltre che al
computer stesso).
Listing 4.1: "Modulo Char Device"
2
4

/*
* chardev . c : Creates a read - only char device that says how many times
* you ve read from the dev file
* Host : Fabio Nisci < f a b i o n i s c i @ g m a i l . com >
*/

6
8
10
12
14
16
18
20
22

24
26
28
30

# include
# include
# include
# include

< linux / kernel .h >


< linux / module .h >
< linux / fs .h >
< asm / uaccess .h > /* for put_user */

/*
* Prototypes - this would normally go in a . h file
*/
int init_module ( void ) ;
void cleanu p_modu le ( void ) ;
static int device_open ( struct inode * , struct file *) ;
static int dev ice_re lease ( struct inode * , struct file *) ;
static ssize_t device_read ( struct file * , char * , size_t , loff_t *) ;
static ssize_t device_write ( struct file * , const char * , size_t , loff_t *) ;
# define SUCCESS 0
# define DEVICE_NAME " chardev " /* Dev name as it appears in / proc / devices
*/
# define BUF_LEN 80
/* Max length of the message from the device */
/*
* Global variables are declared as static , so are global within the file .
*/
static int Major ;
/* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open ?

14

CAPITOLO 4. BUILD AND RUN

32
34
36
38
40

* Used to prevent multiple access to device */


static char msg [ BUF_LEN ]; /* The msg the device will give when asked */
static char * msg_Ptr ;
static struct f il e _o p er at i on s fops = {
. read = device_read ,
. write = device_write ,
. open = device_open ,
. release = devi ce_rel ease
};

42
44
46
48

/*
* This function is called when the module is loaded
*/
int init_module ( void )
{
Major = r e gi st e r_ ch r de v (0 , DEVICE_NAME , & fops ) ;
if ( Major < 0) {
printk ( KERN_ALERT " Registering char device failed with % d \ n " , Major ) ;
return Major ;
}

50
52
54

printk ( KERN_INFO
printk ( KERN_INFO
printk ( KERN_INFO
printk ( KERN_INFO
printk ( KERN_INFO
printk ( KERN_INFO

56
58
60

" I was assigned major number % d . To talk to \ n " , Major ) ;


" the driver , create a dev file with \ n " ) ;
" mknod / dev /% s c % d 0 .\ n " , DEVICE_NAME , Major ) ;
" Try various minor numbers . Try to cat and echo to \ n " ) ;
" the device file .\ n " ) ;
" Remove the device file and module when done .\ n " ) ;

return SUCCESS ;

62

}
64
66
68
70
72
74

/*
* This function is called when the module is unloaded
*/
void cleanu p_modu le ( void )
{
/*
* Unregister the device
*/
u n r e g i s t e r _ c h r d e v ( Major , DEVICE_NAME ) ;
printk ( KERN_INFO " U n r e g i s t e r _ c h r d e v " ) ;
}

76
78

/*
* Methods
*/

80
82
84
86

/*
* Called when a process tries to open the device file , like
* " cat / dev / mycharfile "
*/
static int device_open ( struct inode * inode , struct file * file )
{
static int counter = 0;

88
90

if ( Device_Open )
return - EBUSY ;

92

Device_Open ++;

15

CAPITOLO 4. BUILD AND RUN

sprintf ( msg , " I already told you % d times Hello world !\ n " , counter ++) ;
msg_Ptr = msg ;
try _modul e_get ( THIS_MODULE ) ;

94
96

return SUCCESS ;
98
100
102
104

}
/*
* Called when a process closes the device file .
*/
static int dev ice_re lease ( struct inode * inode , struct file * file )
{
Device_Open - -;
/* We re now ready for our next caller */

106

/*
* Decrement the usage count , or else once you opened the file , you ll
* never get get rid of the module .
*/
module_put ( THIS_MODULE ) ;

108
110
112

return 0;
114

116

/*
* Called when a process , which already opened the dev file , attempts to
* read from it .
*/
static ssize_t device_read ( struct file * filp , /* see include / linux / fs . h
*/
char * buffer , /* buffer to fill with data */
size_t length , /* length of the buffer
*/
loff_t * offset )
{
/*
* Number of bytes actually written to the buffer
*/
int bytes_read = 0;

118
120

122
124
126
128
130
132
134

/*
* If we re at the end of the message ,
* return 0 signifying end of file
*/
if (* msg_Ptr == 0)
return 0;

136
138
140

/*
* Actually put the data into the buffer
*/
while ( length && * msg_Ptr ) {
/*
* The buffer is in the user data segment , not the kernel
* segment so "*" assignment won t work . We have to use
* put_user which copies data from the kernel data segment to
* the user data segment .
*/
put_user (*( msg_Ptr ++) , buffer ++) ;

142
144
146
148

length - -;
bytes_read ++;

150
152

16

CAPITOLO 4. BUILD AND RUN

17

/*
* Most read functions return the number of bytes put into the buffer
*/
return bytes_read ;

154
156
158

160

/*
* Called when a process writes to dev file : echo " hi " > / dev / hello
*/
static ssize_t
device_write ( struct file * filp , const char * buff , size_t len , loff_t * off )
{
printk ( KERN_ALERT " Sorry , this operation isn t supported .\ n " ) ;
return - EINVAL ;
}

162
164
166
168

Capitolo 5

Debug
5.1

Debugging moduli kernel: KGDB

GDB in grado di rilevare quando un modulo viene caricato sul target. Quindi carica il
file modulo oggetto modulo nella memoria di GDB per ottenere le informazioni di debug. Il
percorso di ricerca in cui GDB individua file di modulo si trova nella variabile solib-searchpath.
Sul target carichiamo il modulo:
target# insmod my_module.ko
Sullhost carichiamo i simboli del modulo ed impostiamo i breakpoint:
host% sh4-linux-gdb vmlinux
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License,
and you are welcome to change it and/or distribute copies of it
under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.
Type "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=sh4-linux"...
0x84031f10 in ?? ()
Automatically enabled KGDB extensions...
(gdb) set solib-search-path /user/my_module_2.6/
Reading symbols from /user/my_module_2.6/my_mod.ko...
expanding to full symbols...done.
Loaded symbols for /user/my_module_2.6/my_mod.ko
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0xc0168000 0xc01680c0 Yes /user/my_module_2.6/my_mod.ko
(gdb) b mod_stm_open
Breakpoint 1 at 0xc0168002: file /user/my_module_2.6/my_mod.c, line 43.
(gdb) c
Continuing.
18

CAPITOLO 5. DEBUG

19

Al fine di individuare i simboli dei moduli caricabili, GDB deve essere collegato alla porta
(in cui sono stati caricati i moduli). Ci significa che il comando set solib-search-path /user/
my_module_2.6/deve essere eseguito dopo che GDB si sia connesso al target, altrimenti
raccoglie i simboli, ma non sa dove si trovano. Questo problema pu essere risolto utilizzando
i punti di interruzione in sospeso. I problemi potrebbero verificarsi anche quando lo scarico
dei moduli se GDB ha ancora i punti di interruzione allinterno del modulo. Ad esempio,
se si scarica un modulo (utilizzando il comando rmmod),senza rimuovere tutti i punti di
interruzione rilevanti in GDB, qualsiasi tentativo di avviare una nuova sessione di debug
(ad esempio fermare lesecuzione del kernel con Ctrl + C) provoca il blocco del sistema .
Questo perch KGDB stato chiesto di eseguire azioni su punti di interruzione attivi che
non sono pi accessibili.