PLUTO Journal nr.6
|
|
|
|
Articoli
|
Device Drivers #3
di
Georg v. Zezschwitz and Alessandro Rubini
Ristampato con il permesso del
Linux Journal.
|
This material is reprinted with permission of
Linux Journal.
|
Il Dèmone è nei Dettagli
Questo articolo è il terzo di cinque parti sulla scrittura di device
driver a carattere, e introduce i concetti riguardanti la lettura e la
scrittura del dispositivo, come pure l'implementazione di
ioctl() .
Sulla base dell'ambiente che abbiamo predisposto nei due precedenti
articoli, cercheremo questa volta di riempire la struttura con il
materiale relativo alle interrupt. Straordinariamente, Linux ci nasconde
la maggior parte delle questioni problematiche, così che non dovremo
scrivere nemmeno una linea di assembler.
|
The Devil's in the Details
This article, the third of four sections on writing character device
drivers, introduces the different concepts of reading, writing, and
ioctl -calls, and how to form them into concrete code.
Based on the clean code environment of the two previous articles, we
will now fill the mold with all the nasty interrupt stuff.
Astonishingly, Linux hides most of this from us, so we do not need a
single line of assembler...
Leggere e scrivere
Finora il nostro driver per la skel -machine è in grado di
agganciarsi e sganciarsi dal kernel senza problemi, ma non siamo ancora
stati in grado di leggere o scrivere nemmeno un carattere. Inizieremo
quindi fornendo il codice per le funzioni skel_read() e
skel_write() , che abbiamo presentato nel precedente
articolo, sotto il paragrafo Fops e filp. Ad entrambe
le funzioni vengono passati quattro argomenti:
|
Reading and writing
Right now, our magic skel -machine driver can load and even
unload (painlessly, unlike in dos), but we have neither read nor written
a single character. So we will start filling the
skel_read() and skel_write() functions
introduced in the previous article (under fops and
filp) with content. Both function are supplied with four
arguments:
| |
Static int skel_read (struct inode *inode,
struct file *file,
char *buf, int count)
Static int skel_write (struct inode *inode,
struct file *file,
char *buf, int count)
La struttura inode ci fornisce le informazioni che abbiamo
già usato all'interno di skel_open() . Per esempio, avevamo
determinato quale scheda l'utente sta aprendo (ricordiamo che il nostro
driver supporta dispositivi multipli) usando inode->i_rdev ;
avevamo poi trasferito questa informazione, insieme all'indirizzo di I/O
della scheda e il numero di IRQ, nel campo private_data
della struttura file . Possiamo quindi ignorare queste
informazioni nella read() , ma se non avessimo usato
private_data l'unico modo per sapere a quale scheda stiamo
per parlare sarebbe la lettura di inode .
|
The inode structure supplies us with information we used
already during the skel_open() call. For example, we
determined from inode->i_rdev which of perhaps multiple
boards the user wants to open, and transfered this information, together
with information about this board's base address and interrupt to the
private_data entry of the file descriptor. Therefore, we
might ignore this information now, but if we did not use this hack,
inode is our only chance to find out to which board we are
talking.
|
La struttura file , invece, contiene informazioni
probabilmente più utili: si possono esplorare tutti i suoi campi in
<linux/fs.h> . Se si vuole usare il campo
private_data lo si trova qui; anche il campo
f_flags spesso utile in quanto rivela, per esempio, se il
programma desidera operare in modo bloccante o non-bloccante -- questo
argomento verrà spiegato dettagliatamente più avanti.
|
The file structure contains information that is probably
more valuable. You can explore all its elements in its definition in
<linux/fs.h> . If you use the private_data
entry, you find it here, and you should also make use of the
f_flags entry, revealing to you, for instance, if the user
wants blocking or non-blocking mode. (We explain this topic in more
detail later on.)
|
Gli argomenti buf e count dicono dove mettere
i dati che leggiamo (o dove trovare i dati che dobbiamo scrivere), e
quanti bytes ci sono. Bisogna però ricordare che ogni processo ha il suo
spazio di indirizzamento privato, mentre il codice del kernel risiede in
uno spazio di indirizzamento che è comune a tutti i processi. Quando le
chiamate di sistema eseguono per conto di uno specifico processo,
eseguono nello spazio del kernel ma devono (e possono) accedere allo
spazio utente. Storicamente lo spazio utente veniva indirizzato in
assembler usando il registro fs ; le versioni attuali di
linux nascondono le istruzioni specifiche all'interno di funzioni
chiamate get_user_byte() per leggere un byte dallo spazio
utente, put_user_byte() per scriverne uno, eccetera.
Queste funzioni si chiamavano precedentemente con nomi come
get_fs_byte , mentre adesso solo memcpy_tofs()
e memcpy_fromfs() revelano la vecchia implementazione,
anche su piattaforme diverse da x86. Chi volesse esplorare tutte queste
funzioni può trovarne le definizioni in .
|
The buf and count arguments tell us where to
put the bytes we read (or where to find the bytes we shall write) and
how many bytes there are. But you must remember that every process has
its own private address space. When in kernel code, you have an address
space which is common to all the processes. When system calls execute on
behalf of a specific process, they run in kernel address space, but are
still able to access the user space. Historically, this was done through
assembler using the fs register; current linux kernels hide
the specific code within functions called get_user_byte()
for reading a byte from user address space, put_user_byte()
for writing one, and so on. They were formerly known as
get_fs_byte and similar, and only
memcpy_tofs() and memcpy_fromfs() reveal these
old days even on a DEC Alpha. If you want to explore them all, you'll
find the definition in .
|
Immaginiamo ora un hardware ideale che non aspetta altro che di ricevere
i nostri dati, legge e scrive velocemente, e viene visto attraverso una
semplice porta dati da 8 bit all'indirizzo base della nostra
interfaccia. Nonostante questo non sia realistico, il programmatore
impaziente può provare il codice seguente:
|
Let us imagine ideal hardware that is always hungry to receive our data,
reads and writes quickly, and is accessed through a simple 8-bit
data-port at the base address of our interface. Though it is
unrealistic, the impatient of you might try the following code:
|
Static int skel_read (struct inode *inode,
struct file *file,
char *buf, int count) {
int n = count;
char *port = PORT0 ((struct Skel_Hw*)(file->private_data));
while (n--) {
Wait till device is ready
aspetta che la periferica sia pronta
put_user_byte (inb_p (port), buf);
buf++;
}
return count;
}
Occorre spendere alcune parole sulla funzione inb_p() :
questa è la funzione di lettura dei dati dall'hardware. Si può decidere
di usare il suo equivalente veloce, inb() , che omette una
minima attesa che può essere necessaria quando si pilota dell'hardware
lento, ma noi preferiamo l'approccio più cauto.
|
Notice the inb_p() function call. This is the actual I/O
read from the hardware. You might decide to use its fast equivalent,
inb() , which omits a minimal delay some slow hardware might
need, but I prefer the safe way.
|
La funzione equivalente skel_write() è quasi uguale, tranne
che bisogna rimpiazzare la linea contenente put_user_byte()
con la seguente:
|
The equivalent skel_write() function is nearly the same.
Just replace the put_user_byte() line by the following:
|
outb_p (get_user_byte (buf), port);
C'è da dire, però che questa implementazione ha un sacco di svantaggi.
Cosa succede se il kernel si ferma in una di queste funzioni aspettando
per un dispositivo che non diventerà mai pronto a ricevere/restuire
dati?
|
However, these lines have a lot of disadvantages. While using them, what
if Linux sticks in them while waiting for a device that never becomes
ready?
|
Torniamo per un attimo ai discorsi teorici: cosa ci aspettiamo dal
nostro driver? Certamente dovrebbe dedicare il tempo di attesa ad altri
processi che usino il nostro costoso processore, e dovrebbe avere un
buffer di ingresso e uno di uscita in modo da gestire i dati che
arrivano mentre i processi non sono in skel_read() o
skel_write() . Dovrebbe avere un time-out per riconoscere
gli errori, e dovrebbe supportare operazioni bloccanti e non bloccanti.
|
Let us return back to theory for a short time. What do we expect from
our driver? It should dedicate the time in the waiting loop to other
processes making use of our expensive CPU, and it should have an input
and output buffer for bytes arriving while we are not in
skel_read() and corresponding skel_write()
calls. It should have a time-out in case of errors, and it should
support blocking and non-blocking modes.
|
Modo bloccante e non bloccante
|
Blocking and Non-Blocking Mode
|
Immaginiamo un processo che voglia leggere 256 bytes. Sfortunatamente,
in nostro buffer di ingresso è vuoto quando skel_read()
viene chiamata. Cosa dovremmo fare allora: ritornare dicendo che non ci
sono ancora dati, o aspettare finchè non arriva almeno qualche
byte?
|
Imagine a process which would like to read 256 bytes. Unfortunately,
our input buffer is empty when skel_read() is called. So
what shall we do: return and say that there is no data yet, or wait till
at least some bytes have arrived?
|
La risposta è entrambe le cose. Il modo
blocking significa che l'utente vuole attendere finchè non ci
sono dati da leggere. Il modo non-blocking significa che il
driver deve ritornare il più presto possibile -- deve solo leggere
tutti i bytes che sono disponibili. Regole simili si applicano alla
scrittura: una scrittura bloccante significa "non ritornare
finchè non puoi accettare alcuni dati", mentre una non
bloccante significa "ritorna anche se non puoi accettare niente".
|
The answer is both. Blocking mode means the
user wants us to wait till some bytes are read. Non-blocking
mode means to return as soon as possible---just read all the bytes that
are available. Similar rules apply to writing: Blocking mode
means ``Don't return till you can accept some data,'' while
non-blocking mode means: ``Return even if nothing is
accepted.''
|
Le chiamate read() e write() di solito
ritornano il numero di bytes trasferiti. Se, però, l'operazione è
`non-blocking' e non possono essere trasferiti dati, bisogna ritornare
-EAGAIN che significa ``Play it again, Sam''. Una
volta si ritornava -EWOULDBLOCK , che in Linux è la stessa
cosa che -EAGAIN .
|
The read() and write() calls usually return the
number of data bytes successfully read or written. If, however, the
device is non-blocking and no bytes can be transferred,
-EAGAIN should be returned (meaning: ``Play it again,
Sam''). Some old code returns -EWOULDBLOCK , which is
the same as -EAGAIN under Linux.
|
Forse adesso state sorridendo felici come quando io ho sentito per la
prima volta di questi due modi. Se questi concetti sono nuovi per voi,
potrete trovare utili i seguenti suggerimenti. Ogni dispositivo viene
aperto per default in modo `blocking', ma l'utente può scegliere di
usare il modo `non-blocking' settando O_NONBLOCK nella
chiamata open() . Si può anche cambiare il comportamento di
un file più avanti usando la funzione fcntl() . La
chiamata fcntl() è abbastanza semplice, e la pagina del
manuale fornisce informazioni sufficienti per un programmatore.
|
Maybe now you are smiling as happily as I did when I first heard about
these two modes. If these concepts are new for you, you might find the
following hints helpful. Every device is opened by default in blocking
mode, but you may choose non-blocking mode by setting the
O_NONBLOCK flag in the open() call. You can
even change the behaviour of your files later on with the
fcntl() call. The fcntl() call is an easy one,
and the man page will be sufficient for any programmer.
|
La bella addormentata
|
Sleeping Beauty
|
C'era una volta una bella pricipessa che era stata posta in un lungo e
profondo sonno da una strega. Il mondo quasi si era dimenticato di lei e
del suo castello circondato ormai dalle rose; finchè un giorno un bel
principe venne e la baciò, svegliandola dal suo sonno... e successero
tutte le altre belle cose di cui parlano le fiabe.
|
Once upon a time, a beautiful princess was sent by a witch into a long,
deep sleep, lasting for a hundred years. The world nearly forgot her and
her castle, twined about by roses, until one day, a handsome prince came
and kissed her, waking her up...and all the other nice things happened
that fairy tales tell about.
|
Il nostro driver mentre attende i dati deve fare esattamente quello che
faceva la bella addormentata: dormire, lasciando girare il mondo per
conto suo. Linux offre un meccanismo per questo, chiamato
interruptible_sleep_on() . Ogni processo che chiama questa
funzione cade addormentato e lascia il suo tempo al resto del mondo. Il
processo rimane all'interno di questa funzione finchè un altro processo
chiama wake_up_interruptible() . Questo `principe' è di
solito un gestore di interrupt che ha ricevuto o spedito dati; oppure
Linux stesso, se viene raggiunta una condizione di timeout.
|
Our driver should do exactly what she did while it is waiting for data:
sleep, leaving the world spinning around. Linux provides a mechanism for
that, called interruptible_sleep_on() . Every process
reaching this call will fall asleep and contribute its time slices to
the rest of the world. It will stay in this function till another
process calls wake_up_interruptible() , and this prince is
usually an interrupt handler which has successfully received or sent
data, or Linux itself, if a time-out condition has occured.
|
Installazione di un gestore di interrupt
|
Installing an interrupt handler
|
Il precedente numero di questa rubrica aveva mostrato un gestore di
interruzione minimale, chiamato skel_trial_fn() , ma il suo
funzionamento non era stato spiegato. Presentiamo qui un gestore di
interrupt completo, che gestirà sia l'input che l'output al dispositivo
hardware. Quando il driver attende in modo `blocking' che il
dispositivo diventi pronto, si addormenta chiamando
interruptible_sleep_on() . Una interruzione valida termina
il sonno, facendo ripartire skel_write() .
|
The previous issue of this column showed a minimal interrupt handler,
which was called skel_trial_fn() , but its workings were not
explained. Here, a ``complete'' interrupt handler is introduced, which
will handle both input and output to the actual hardware device. Figure
1 explains a simple version of its concept: When the driver is waiting
for the device to get ready (blocking), it goes to sleep by calling
interruptible_sleep_on() . A valid interrupt ends this
sleep, restarting skel_write() .
|
Non vi e` doppio ciclo che occorre quando si lavora con in buffer
interno di output. La ragione è che se possiamo scrivere solo
dall'interno di skel_write() non c'è bisogno di un buffer
di output. Ma il nostro driver deve anche essere in grado di ricevere
dati anche quando non si trova in skel_read() , a dovrebbe
spedire i dati di output in background anche quando non si trova in
skel_write() . Perciò, cambieremo skel_write()
in modo da sostituire la scrittura diretta all'hardware con il
salvataggio in un buffer di output, e lascieremo al gestore di
interruzione il compito di fare il trasferimento reale verso l'hardware.
L'interrupt e skel_write() saranno poi collegati dal
`meccanismo della bella addormentata' e dal buffer di output.
|
Figure 1 does not include the double-nested loop structure we need when
working with an internal output buffer. The reason is that if we can
only perform writing within the skel_write() function there
is no need for an internal output buffer. But our driver should catch
data even while not in skel_read() and should write out our
data in the background even when not in skel_write() .
Therefore, we will change the hardware writing in
skel_write() to write to an output buffer and let the
interrupt handler perform the real writing to the hardware. The
interrupt and skel_write() will now be linked by the
``Sleeping Beauty'' mechanism and the output buffer.
|
Il gestore di interruzione viene installato e disiinstallato durante le
chiamate di apertura e chiusura del dispositivo, come suggerito
nell'articolo precedente. Il compito viene gestito dalle seguenti
funzioni del kernel:
|
The interrupt handler is installed and uninstalled during the
open() and close() calls to the device, as
suggested in the previous issue. This task is handled by the following
kernel calls:
|
#include <linux/sched.h>
int request_irq(unsigned int irq,
void (*handler)(int, struct pt_regs *),
unsigned long flags,
const char *device);
void free_irq(unsigned int irq);
handler è la funzione che vogliamo installare. Il ruolo
dell'argomento flags è di selezionare le caratterische del
gestore di interruzione, la più importante è se il gestore di
interruzione è veloce (SA_INTERRUPT è settato in
flags ) oppure lento (SA_INTERRUPT non è
settato). Un gestore veloce viene fatto girare con tutte le interruzioni
disabilitate, mentre uno lento viene eseguito con tutte le altre
interruzione (tranne la sua stessa) abilitate.
|
handler is the actual interrupt handler we wish to install.
The role of the flags argument is to set a few features of
the handler, the most important being its behaving as a fast
(SA_INTERRUPT is set in flags ) or as a
slow ( SA_INTERRUPT is not set) handler. A fast
handler is run with all interrupts disabled, while a slow one is
executed with all interrupts except itself enabled.
|
Infine, l'argomento device viene usato per identificare il
gestore quando si legge /proc/interrupts .
|
Finally, the device argument is used to identify our
handler when looking at /proc/interrupts .
|
NdT:
Con la versione 1.3.70 del kernel viene aggiunto un argomento a
request_interrupt()
, a
free_interrupt()
e al gestore di interruzione vero e proprio. Purtroppo non ho tempo di
specificare le differenze prima che questa versione del Pluto Journal
venga pubblicata
-- mea culpa.
|
|
Il gestore di interruzione installato tramite request_irq()
riceve come argomenti solo il numero di interruzione e il contenuto,
spesso inutile, dei registri del processore.
|
The handler function installed by request_irq() is only
passed the interrupt number and the (often useless) contents of the
processor registers.
|
Il nostro gestore di interruzione dovrà quindi come prima cosa
determinare a quale scheda l'interruzione si riferisce. Se non possiamo
trovare di quale scheda si tratta, chiameremo questa situazione
interruzione spuria e la ignoreremo. Nella maggior parte dei
casi le interruzioni sono usate per dire che il dispositivo è pronto o
per accettare una lettura o una scrittura, quindi dovremo trovare
tramite dei test hardware cosa il dispositivo si aspetta che noi
facciamo.
|
Therefore, we'll first determine which board the calling interrupt
belongs to. If we can't find any boards, a situation called a
spurious interrupt has occured, and we should ignore it. Mostly
interrupts are used to tell the device is ready either for reading
or writing, so we have to find out by some hardware tests what the
device wants us to do.
|
Naturalmente, dovremo lasciare il nostro gestore di interruzione il più
velocemente possibile; stranamente, però, printk() (e
quindi PDEBUG() ), sono permesse anche all'interno di un
gestore di interruzione veloce. Questa è una caratteristica
molto comoda della implementazine di Linux. Guardando in
kernel/printk.c si troverà che l'implementazione è basata,
ancora una volta, su code di attesa, in quanto la effettiva consegna dei
messaggi ai files di log viene gestita da un processo esterno
(solitamente klogd).
|
Of course, we should leave our interrupt handler quickly. Strangely
enough, printk() (and thus the PDEBUG line) is
allowed even within fast interrupt handlers. This is a very useful
feature of the linux implementation. If you look at
kernel/printk.c you'll find out that the implementation is
based on wait queues, as the actual delivery of messages to log files is
handled by an external process (usually klogd).
|
Linux può gestire un timeout durante
interruptible_sleep_on() . Per esempio, se si stanno
mandando dei dati ad un dispositivo che dovrebbe rispondere entro un
tempo limitato, la gestione di un timeout al fine di segnalare un errore
di I/O (ritornando -EIO ) al processo che sta usando il
dispositivo potrebbe essere una buona scelta.
|
As shown in figure 2, Linux can care for a timeout when in
interruptible_sleep_on() . For example, if you have are
using a device you send an answer to, and it is expected to reply within
a limited time, causing a time-out to signal an I/O error
(-EIO ) in the return value to the user process might be a
good choice.
|
Certamente il processo utente potrebbe gestire la cosa da solo, usando
il mecchanismo di alarm() , ma risulta decisamente più
comodo gestire la cosa nel driver stesso. Il tempo limite (il timeout)
viene specificato da SKEL_TIMEOUT , che viene contato in
`jiffies'. Un `jiffy' è il tempo del battito del cuore di un sistema
Linux, e la variabile jiffies viene incrementata ad ogni
battito. La frequenza dell'interruzione del clock (il battito in
questione) è HZ , definito in
(incluso da <linux/sched.h> ). Tale frequenza varia tra
le differenti architetture supportate e vale, per esempio, 100 Hz su x86
e 1kHz su Alpha). Per definire un timeour prima di
interruptible_sleep_on() bastano le seguenti linee:
|
Certainly the user process could care for this, too, using the alarm
mechanism. But it is definitly easier to handle this in the driver
itself. The timeout criteria is specified by SKEL_TIMEOUT ,
which is counted in jiffies. Jiffies are the steady heartbeat of a Linux
system, a steady timer being incremented every few milliseconds. The
frequency is defined by HZ in
(included by <linux/sched.h> ) and varies on the
different architectures (100 Hz Intel, 1 kHz Alpha). You just have to
set
|
#define SKEL_TIMEOUT (timeout_seconds * HZ)
/* ... */
current->timeout = jiffies + SKEL_TIMEOUT
se interruptible_sleep_on dovesse protrarsi oltre il
timeout, current->timeout verrebbe azzerato prima che la
funzione ritorni, un controllo di current->timeout è quindi
sufficiente per riconoscere la condizione di errore.
|
and if interruptible_sleep_on timed out,
current->timeout will be cleared after return.
|
Bisogna fare attenzione al fatto che le interruzioni possono venire
generate anche durante l'esecuzione di skel_read() e
skel_write() . Le variabili che possono essere cambiate
all'interno di un gestore di interruzione devono perciò essere
dichiarate volatile , e devono essere protette contro le
corse critiche (race conditions). La classica sequenza di codice per
proteggere le regioni critiche è la seguente:
|
Be aware that interrupts might happen right within
skel_read() and skel_write() . Variables that
might be changed within the interrupt should be declared as
volatile . They also need to be protected to avoid race
conditions. The classic code sequence to protect a critical region is
the following:
|
unsigned long flags;
save_flags (flags);
cli ();
critical region
regione_critica
restore_flags (flags);
Ed ecco, infine, il codice vero e proprio per la lettura e la scrittura.
|
Finally, the code itself:
|
#define SKEL_IBUFSIZ 512
#define SKEL_OBUFSIZ 512
#define SKEL_TIMEOUT (5*HZ) /* for 5 seconds timeout */
/* This should be inserted in the Skel_Hw structure */
typedef struct Skel_Hw {
volatile int ibuf_wpos; /* write position in input-buffer */
int ibuf_rpos; /* read position in input-buffer */
char *ibuf; /* the input-buffer itself */
int obuf_wpos; /* write position in output-buffer */
volatile int buf_rpos; /* read position in output-buffer */
char *obuf;
struct wait_queue *skel_wait_iq;
struct wait_queue *skel_wait_oq;
...
}
#define SKEL_IBUF_EMPTY(b) ((b)->ibuf_rpos==(b)->ibuf_wpos)
#define SKEL_OBUF_EMPTY(b) ((b)->obuf_rpos==(b)->obuf_wpos)
#define SKEL_IBUF_FULL(b) (((b)->ibuf_wpos+1)%SKEL_IBUFSIZ==(b)->ibuf_rpos)
#define SKEL_OBUF_FULL(b) (((b)->obuf_wpos+1)%SKEL_OBUFSIZ==(b)->obuf_rpos)
Static int skel_open (struct inode *inode, struct file *filp) {
/* .... */
/* First we allocate the buffers */
board->ibuf = (char*) kmalloc (SKEL_IBUFSIZ, GFP_KERNEL);
if (board->ibuf == NULL)
return -ENOMEM;
board->obuf = (char*) kmalloc (SKEL_OBUFSIZ, GFP_KERNEL);
if (board->obuf == NULL) {
kfree_s (board->ibuf, SKEL_IBUFSIZ);
return -ENOMEM;
}
/* Now we clear them */
ibuf_wpos = ibuf_rpos = 0;
obuf_wpos = obuf_rpos = 0;
board->irq = board->hwirq;
if ((err=request_irq(board->irq, skel_interrupt, SA_INTERRUPT, "skel")))
return err;
}
Static void skel_interrupt(int irq, struct pt_regs *unused) {
int i;
Skel_Hw *board;
for (i=0, board=skel_hw; iirq==irq) break; /* spurious */
if (i==skel_boards) return;
if (board_is_ready_for_input())
skel_hw_write (board);
if (board_is_ready_for_output())
skel_hw_read (board);
}
Static inline void skel_hw_write (Skel_Hw *board){
int rpos;
char c;
while (! SKEL_OBUF_EMPTY (board) && board_ready_for_writing()) {
c = board->obuf [board->obuf_rpos++];
write_byte_c_to_device();
board->obuf_rpos %= SKEL_OBUF_SIZ;
}
/* Sleeping Beauty */
wake_up_interruptible (board->skel_wait_oq);
}
Static inline void skel_hw_read (Skel_Hw *board) {
char c;
/* If space left in the input buffer & device ready: */
while (! SKEL_IBUF_FULL (board) && board_ready_for_reading()) {
read_byte_c_from_device();
board->ibuf [board->ibuf_wpos++] = c;
board->ibuf_wpos %= SKEL_IBUFSIZ;
}
wake_up_interruptible (board->skel_wait_iq);
}
Static int skel_write (struct inode *inode,
struct file *file,
char *buf, int count) {
int n;
int written=0;
Skel_Hw board = (Skel_Hw*) (file->private_data);
for (;;) {
while (writtenobuf [board->obuf_wpos] = get_user_byte (buf);
buf++; board->obuf_wpos++;
written++;
board->obuf_wpos %= SKEL_OBUFSIZ;
}
if (written) return written;
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
current->timeout = jiffies + SKEL_TIMEOUT;
interruptible_sleep_on ( &(board->skel_wait_oq));
/* Why did we return? */
if (current->signal & ~current->blocked)
/* If the signal is not not being blocked */
return -ERESTARTSYS;
if (!current->timeout)
/* no write till timout: i/o-error */
return -EIO;
}
}
Static int skel_read (struct inode *inode,
struct file *file,
char *buf, int count) {
Skel_Hw board = (Skel_Hw*) (file->private_data);
int bytes_read = 0;
if (!count) return 0;
if (SKEL_IBUF_EMPTY (board)) {
if (file->f_flags & O_NONBLOCK)
/* Non-blocking */
return -EAGAIN;
current->time_out = jiffies+SKEL_TIMEOUT;
for (;;) {
skel_tell_hw_we_ask_for_data();
interruptible_sleep_on ( &(board->skel_wait_iq));
if (current->signal & ~current->blocked)
return -ERESTARTSYS;
if (! SKEL_IBUF_EMPTY (board))
break;
if (!current->timeout)
/* Got timeout: return -EIO */
return -EIO;
}
}
/* if some bytes are here, return them */
while (! SKEL_IBUF_EMPTY (board)) {
put_user_byte (board->ibuf [board->ibuf_rpos], buf);
buf++; board->ibuf_rpos++;
bytes_read++;
board->ibuf_rpos %= SKEL_IBUFSIZ;
if (--count == 0) break;
}
if (count) /* still looking for some bytes */
skel_tell_hw_we_ask_for_data();
return bytes_read;
}
Come usare select()
|
Handling select()
|
L'ultima importante funzione di I/O che mostriamo è
select() , una delle parti più interessanti di Unix, nella
nostra opinione.
|
The last important I/O function to be shown is select() ,
one of the most interesting parts of Unix, in my opinion.
|
La chiamata di sistema select() viene usara per attendere
che qualche dispositivo diventi pronto, ed è una delle funzioni più
difficili per il novello programmatore C. Mentre il suo uso dall'interno
dell'applicazione non viene mostrato qui, la parte dell'implementazione
specifica del device driver è mostrata qui sotto, e la sua
caratteristica che colpise di più è la sua compattezza.
|
The select() call is used to wait for some device to become
ready, and is one of the most scary functions for the novice C
programmer. While its use from within the application is not shown here,
the driver-specific part of the system call is shown, and its most
impressive feature is its compactness.
|
Ecco il codice completo:
|
Here's the full code:
|
Static int skel_select(struct inode *inode,
struct file *filp,
int sel_type,
select_table *wait) {
Skel_Clientdata *data=filp->private_data;
Skel_Hw *board=data->board;
if (sel_type==SEL_IN) {
if (! SKEL_IBUF_EMPTY (board))
return 1; /* readable */
skel_tell_hw_we_ask_for_data();
select_wait(&(board->skel_wait_iq), wait);
/* not readable */
return 0;
}
if (sel_type==SEL_OUT) {
if (! SKEL_OBUF_FULL (board))
return 1; /* writable */
/* hw knows */
select_wait (&(board->skel_wait_oq), wait);
return 0;
}
return 0; /* exception condition: cannot happen */
}
Come si vede, il kernel si prende cura di tutti i problemi della
gestione delle code di attesa, e occurre solo controllare che il
dispositivo sia pronto.
|
As you see, the kernel takes care of all the hassle of managing wait
queues, and you have only to check for readiness.
|
Quando abbiamo scritto la prima implementazione di select()
per il nostro driver, non avevamo ancora capito la implementazione delle
pre id attesa, ma questo in effetti non serve ad un programmatore.
Occorre solo sapere che il codice mostrato funziona. Le code di attesa
sono problematiche, e di solito durante la scrittura di un driver
non si ha il tempo di affrontare il problema nei dettagli.
|
When we first wrote a select() call for a driver, we didn't
understand the wait_queue implementation, and you don't need to either.
You only have to know that the code works. wait_queue s
are challenging, and usually when you write a driver you have no
time to accept the challenge.
|
In effetti, select() è compresa meglio nelle sue relazioni
con read() e write() : se select()
dice che il file è leggibile, allora la prossima read() non
si bloccherà (indipendentemente da O_NONBLOCK ). Questo
significa che occorre dire all'hardware di ritornare qualche dato.
L'interruzione raccoglierà i dati e sveglierà la coda di attesa. Se
l'utente sta selezionando per la scrittura, la situazione è simile: il
driver deve dire se la prossima write() si bloccherà o no.
Se il buffer è pieno si bloccherà, ma non occorre riferire questo evento
all'hardware in quanto write() doverbbe averlo già
notificato (durante il riempimento del buffer). Se il buffer non è
pieno, write() non si bloccherà, quindi
select() ritornerà 1.
|
Actually, select is better understood in its relationships with read and
write: if select() says that the file is readable, the next
read must not block (independently of O_NONBLOCK ), and this
means you have to tell the hardware to return data. The interrupt will
collect data, and awake the queue. If the user is selecting for writing,
the situation is similar: the driver must tell if write()
will block or not. If the buffer is full it will block, but you don't
need to tell the hardware about it, since write() has
already told it (when it filled the buffer). If the buffer is not full,
the write won't block, so you return 1.
|
Questo modo di pensare select() per la scrittura può
sembrare strano, in quanto capita di aver bisogno di scritture sincrone,
e un programmatore si aspetterebbe che un dispositivo sia scrivibile
quando ha già accettato tutto l'output precedente. Sfortunatamente,
questo modo di implementare le cose romperrebbe tutto il macchinario
delle operazioni blocking/non-blocking, e quindi viene fornita al
programmatore esigente una chiamata di sistema extra: il driver dovrebbe
in questo caso offrire una operazione fsync() all'interno
delle sue fops . L'applicazione invocherà allora
fops->fsync attraverso la chiamata di sistema
fsync , e se il driver non supporta tale funzione,
-EINVAL verrà ritornato al programma.
|
This way to think of selecting for write may appear strange, as there
are times when you need synchronous write, and you may expect that a
device is writable when it has already accepted pending input.
Unfortunately, this way of doing things will break the
blocking/nonblocking machinery, and thus an extra call is provided: if
you need synchronous write, the driver must offer (within its
fops ) the fsync() call. The application
invokes fops->fsync through the fsync() system
call, and if the driver doesn't support it, -EINVAL is
returned.
|
ioctl() Passaggio di informazioni di controllo
|
ioctl() Passing Control Information
|
Immaginiamo di voler cambiare la baud-rate della scheda serialie
multiporta che abbiamo costruito. O che dobbiamo dire al frame grabber
di cambiare la risoluzione. O quasiasi altra cosa.... Si potrebbero
impacchettare queate istruzxioni all'interno di sequenze di escape,
come, per esempio, si fa per posizionare il cursore sui terminali ansi,
ma ill metodo comunemente usato è la funzione ioctl() .
|
Imagine that you want to change the baud-rate of your self-built serial
multiport card. Or tell your frame grabber to change the resolution. Or
whatever else... You could wrap these instructions into some escape
sequences, like, for example, the screen positioning in ansi emulation.
But, the normal method for this is making an ioctl() call.
|
Le chiamate ioctl() sono definite in
e hanno la forma
|
ioctl() calls as defined in have
the form
|
ioctl (int file_handle, int command, ...)
dove i puntini indicano un argimento di tipo char * (in
base alla pagina del manuale). Stranamente, il kernel riceve questi
argomenti tipizzati diversamente in fs/ioctl.c , dove appare
la forma:
|
where ... is considered to be one argument of the type
char * (according to the ioctl man page). Strange as things
sometimes are, the kernel receives these arguments in
fs/ioctl.c in the form:
|
int sys_ioctl (unsigned int fd, unsigned int cmd,
unsigned long arg);
Per aumentare la confusione, <linux/ioctl.h> dà le
regole dettagliate riguardo a come devono essere costruiti i comandi che
appaiono come secondo parametro, ma nonostante ciò sono ancora pochi i
driver che seguono queste buone idee -- principalemente perchè i vecchi
driver devono mantenere i vecchi comandi, per non creare grosse
incompatibilità con le applicazioni.
|
To enlarge this confusion, <linux/ioctl.h> gives
detailed rules how the commands in the second parameter should be built,
but nobody in all the drivers is actually following these (good) ideas
yet.
|
Invece di pulire i sorgenti del kernel cerchiamo di capire
l'idea generale delle chiamate ioctl() ; il modo
più corretto per definire i comandi di ioctl è descritto bene in
Documentation/ioctl-number.txt , ma noi ci limiteremo qui
alla forma semplice, anche se è sconsigliata.
|
Anyway, instead of cleaning up the Linux source tree, lets concentrate
on the general idea of ioctl() calls. As the user,
you pass the file handle and a command in the first two arguments and
pass a pointer to a data structure the driver should read, write, or
read and write as the third parameter.
|
Il programma passa al kernel come primi argomenti di una chiamata
ioctl() il file descriptor e un numero di comando, un
puntatore ad una struttura dati che deve essere letta, scritta, o
letta-e-scritta, dal driver viene passato come terzo parametro
(opzionale).
|
Alcuni comandi vengono interpretati dal kernel prima di passare il
controllo al dirver, come FIONBIO , che cambia il valore
del flag blocking/non-blocking per il file. Il resto dei comandi, la
maggior parte, vengono passati alla nostra funzione ioctl()
all'interno del driver. Il prototipo di tale funzione è il seguente:
|
A few commands are interpreted by the kernel itself, for example,
FIONBIO , which changes the blocking/non-blocking flag of
the file. The rest is passed to our own, driver-specific
ioctl() call, and will arrive here in the form:
|
int skel_ioctl (struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg)
Prima di mostrare un breve esempio di implementazione per
skel_ioctl() , diamo la regola per costruire i numeri dei
comandi (ma ricordiamo che questo è il metodo sconsigliato: un
programmatore attento dovrebbe seguire le regole in
ioctl-number.txt ).
|
Before we show a small example of a skel_ioctl()
implementation, the commands you define should obey the following rule:
|
-
Si scelga un numero magico libero usando la lista in
Documentation/ioctl-number.txt , questo numero cosituirà gli
otto bit alti del numero a 16 bit che constituisce il comando.
-
Si numerino sequenzialmente i comandi negli otto bit più bassi.
|
-
Pick up a free MAGIC number from
/usr/src/linux/MAGIC and
make this number the upper eight bits of your 16-bit command word.
-
Enumerate your commands in the lower eight bits.
|
Perchè questo? Immaginiamo che Pierino faccia partire il suo favorito
programma di comunicazione per connettersi alla sua casella postale
favorita. Pierino ha casualmente cambiato la linea seriale usata da
minicom da /dev/ttyS0 a
/dev/skel0 (è stata una mossa abbastanza stupida, in
effetti). La prima operazione effettuata da minicom è
inizializzare la `linea seriale', usando una ioctl() per
passare il comando TCGETA . Sfortunatamente, il nostro
driver che sta dietro /dev/skel0 usa lo stesso numero per
controllare il voltaggio di un esperimento che sta girando da giorni nel
laboratorio...
|
Why this? Imagine ``Silly Billy'' starts his favorite terminal program
minicom to connect to his favorite mailbox. ``Silly Billy''
accidentally changed the serial line minicom uses from
/dev/ttyS0 to /dev/skel0 (he is quite silly).
The next thing minicom does is initialise the ``serial
line'' with an ioctl() using TCGETA as
command. Unfortunately, your device driver being hidden behind
/dev/skel0 uses that number to control the voltage for a
long-term experiment in the lab...
|
Se gli otto bit alti dei comandi di ioctl() sono diversi
da un driver all'altro, ogni ioctl() che si riferisca ad
un device inappropriato fara ritornare il valore -EINVAL ,
proteggendoci da risultati inaspettati come quello descritto sopra.
|
If the upper eight bits in the commands for ioctl() differ
from driver to driver, every ioctl() to an inappropriate
device will result in an -EINVAL return, protecting us from
extremely unexpected results.
|
Per finire questa sezione implementeremo ora una funzione
ioctl() per leggere o modificare il valore attuale del
timeout del nostro driver. Se si vuole usare questa funzione occorre
però introdurre una nuova variabile nel driver dopo la definizione di
SKEL_TIMEOUT :
|
Now, to finish this section, we will implement an ioctl()
call reading or changing the timeout delay in our driver. If you want to
use it, you have to introduce a new variable
|
unsigned long skel_timeout = SKEL_TIMEOUT;
Ogni occorrenza di SKEL_TIMEOUT andrà poi sostiuita con
skel_timeout .
|
right after the definition of SKEL_TIMEOUT and replace
every later occurence of SKEL_TIMEOUT with
skel_timeout .
|
Abbiamo poi scelto il numero magico '4'
(il codice ascii per il numero 4), e abbiamo definito due comandi:
|
We choose the MAGIC '4' (the ASCII
character 4) and define two commands:
|
# define SKEL_GET_TIMEOUT 0x3401
# define SKEL_SET_TIMEOUT 0x3402
Nel nostro programma, queste linee saranno usate per raddoppiare il
valore corrente del timeout:
|
In our user process, these lines will double the time-out value:
|
/* ... */
unsigned long timeout;
if (ioctl (skel_hd, SKEL_GET_TIMEOUT, &timeout) < 0) {
/* an error occured (Silly Billy?) */
/* ... */
}
timeout *= 2;
if (ioctl (skel_hd, SKEL_SET_TIMEOUT, &timeout) < 0) {
/* another error */
/* ... */
}
Nel driver, invece, queste linee saranno quelle che compiono il lavoro:
|
And in our driver, these lines will do the work:
|
int skel_ioctl (struct inode *inode,
struct file *file,
unsigned int cmd,
unsigned long arg) {
switch (cmd) {
case SKEL_GET_TIMEOUT:
put_user_long(skel_timeout, (long*) arg);
return 0;
case SKEL_SET_TIMEOUT:
skel_timeout = get_user_long((long*) arg);
return 0;
default:
return -EINVAL; /* for Silly Billy */
}
}
Il prossimo numero
|
Next Month
|
Nelle prossime sezioni investigheremo i segreti della gestione di
meoria, e entreremo in contatto con il DMA e mmap() ,
fornendo al nostro driver il meglio della gestione di memoria di Linux.
|
In the next sections, we will investigate the secrets of Linux memory
management and get in touch with DMA and mmap() , providing
our driver with the ultimate throughput of Linux memory management.
|
Georg e Alessandro sono linuxers di 27 anni con un gusto per il lato
pratico dell'informatica e una tendenza a dilazionare il sonno.
|
Georg and Alessandro are both 27-year-old Linuxers with a taste for the
practical side of Computer Science and a tendancy to avoid sleep.
|
PLUTO Journal nr.6
|
|
|
|
Articoli
|