di Alessandro Rubini
Riprodotto con il permesso di Linux Magazine, Edizioni Master.
Quando un'applicazione risponde agli eventi, normalmente riceve notifica di quello che avviene attraverso un descrittore di file. Questo succede per esempio per l'arrivo di pacchetti di rete o di dati su una pipe o una FIFO, come pure per gli eventi di interazione utente che comunicati da un dispositivo (mouse, tastiera) o da un altro applicativo (come X11) tramite un socket di rete. Le chiamate di sistema select e poll, unitamente alla notifica asincrona offerta da fcntl(F_SETOWN), rappresentano un buon meccanismo per la notifica di eventi di questo tipo.
I meccanismi classici di Unix, però, non consentono alle applicazioni di ricevere informazioni su eventi di altro tipo, come l'accrescersi di un file regolare, i cambiamenti nel nome di un file o dei suoi pemessi di accesso, la creazione o la rimozione di file nel sistema.
Questa lacuna viene colmata da dnotify, un meccanismo di notifica per gli eventi relativi alle directory (da cui il nome), che affianca le notifiche di trasferimento dati offerte da poll/select/F_SETOWN.
Il codice presentato è stato verificato con Linux-2.6.10, anche se
dnotify è già presente in Linux-2.4.0, senza modifiche nell'interfaccia
verso lo spazio utente. I programmi di esempio relativi a questo
articolo sono diponibili
come http://www.linux.it/kerneldocs/dnotify/src.tar.gz
.
Il problema affrontato da dnotify è quello della notifica delle modifiche nella struttura del filesystem, ad uso di programmi come i file manager, che dovrebbero aggiornare la presentazione grafica non appena la situazione del sistema viene modificata dall'esterno, senza con questo appesantire il sistema con continue richieste di stato.
Il metodo classico per affrontare questo problema è il più brutale: risvegliare periodicamente il processo e richiedere al sistema una nuova istanza di tutte le informazioni che si tengono sott controllo. In questo modo, il compito viene svolto nel peggior modo possibile: non si garantisce né la velocità di risposta né la leggerezza computazionale; ogni miglioramento in uno dei due parametri porta invariabilmente ad un peggioramento dell'altro.
L'idea base di dnotify è quella di implementare una notifica attiva associata alle directory: ogni evento relativo lle caratteristiche di un file viene riportato ai processi che hanno dichiarato il proprio interesse a quel tipo di evento, relativamente alla directory che contiene il file. Tali eventi includono la creazione e la rimozione di file, la lettura, la modifica dei contenuti o dei permessi di accesso.
La dichiarazione degli eventi cui si è interessati avviene tramite
un comando di fcntl (F_NOTIFY
), eseguito sul descrittore di file
relativo alla directory che si vuole osservare.
Gli eventi cui si è interessati sono specificati tramite
una maschera di bit, passata
come terzo argomento di fcntl. Il Riquadro 1 elenca tali bit,
estratti da <linux/fcntl.h>
; di questi, DN_MULTISHOT
indica
che l'applicazione è interessata anche a più di una
notifica, mentre in sua assenza il kernel riporterà all'applicazione
un solo evento, e fcntl andrà reinvocata prima di ricevere
ulteriori notifiche.
Riquadro 1 - I bit di DN_NOTIFY
|
Un programma applicativo che voglia usare dnotify deve definire la macro
_GNU_SOURCE
prima di includere <fcntl.h>
(si veda il riquadro
2). È anche possibile includere sia <fcntl.h>
sia
<linux/fcntl.h>
(senza definire _GNU_SOURCE
),
ma in questo modo il compilatore segnalerà uno o
due warning, a seconda della versione di glibc in uso.
La notifica di un evento sulla directory avviene tramite l'invio di un
segnale. In mancanza di indicazioni diverse da parte dell'applicazione
verrà inviato SIGIO
(che, se non gestito, provoca l'uscita del
programma). Qualora l'applicazione
volesse informazioni più precise su cosa è successo nella directory
osservata, dovrà usare readdir(3) e stat(2), per confrontare
la nuova situazione con la sua conoscenza pregressa.
Il programma minidn, riportato nel riquadro 3, è un'utilizzo minimale di dnotify. Il programma va invocato redireigneto stdin su una directory; il programma a quel punto attende la notifica di creazione di un file. Per esempio, invocando minidn come segue e poi creando un file in /tmp si ottiene:
Naturalmente, un'applicazione più lunga di 4 righe installerà un
gestore di segnale per poter fare uso della notifica.
Inoltre, invocando il comando
ostro$ ./minidn < /tmp
I/O possible
F_SETSIG
di fcntl, un programma può
richiedere l'invio di un segnale diverso da SIGIO
. In
questo caso il kernel fornisce
informazioni aggiuntive al gestore di segnale, tramite la struttura
struct siginfo
; in particolare, il campo si_code
viene posto a
POLL_MSG
e si_fd
indica il descrittore di file che ha scatenato
l'invio del segnale.
Gli header della libreria C del progetto GNU (glibc) permettono di
attivare o disattivare alcune funzionalità in base ad alcune macro
definite in compilazione. È così possibile preferire la
compatibilità con BSD ("
L'implementazione di
|
|
Il programma watch2.c
, parte dei sorgenti associati a questo
articolo, è un esempio di uso di F_SETSIG
e struct siginfo
per ricevere notifica di eventi su due directory. Il numero 2 è stato
scelto per semplicità ed è facile modificare il programma per
lavorare su un numero a piacere di directory.
Il programma, su ogni directory osservata, apre
un descrittore di file per ognuno degli eventi di dnotify, in modo
da riportare su stdout gli eventi ricevuti, riconoscibili
dal campo si_fd
nella struttura siginfo.
Per esempio:
Il riquadro 4 include una versione ridotta, per ragioni di spazio,
del sorgente di watch2.
ostro$ ./watch2 /tmp $HOME
in /home/rubini: file accessed
in /tmp: file created
in /tmp: file modified
in /home/rubini: file modified
in /home/rubini: file accessed
|
Una tipica applicazione in cui si nota la mancanza
di notifica è tail -f
, usata per osservare i messaggi di log.
Il programma tail distribuito nelle coreutils del progetto GNU
una una procedura di poll sul file osservato: ogni secondo viene
invocata la chiamata di sistema stat per verificare se la
dimensione del file è cambiata.
Il programma follow, anch'esso nell'archivio dei sorgenti, effettua lo stesso lavoro appoggiandosi su dnotify. A differenza di tail, non è in grado di stampare le ultime dieci righe del file osservato, limitandosi a visualizzare gli accrescimenti successivi, ma senza la granularità temporale e le latenze del programma tail.
Il riquadro 5 mostra le righe più significativa del programma follow, depurate della gestione degli errori (presente invece nel sorgente completo).
|
Nel kernel, tutto il codice relativo a dnotify si trova in
fs/dnotify.c, un file di sole 180 righe. Qui si trovano
tre funzioni principali: fcntl_dnotify, che implementa lo specifico
comando di fcntl, e la coppia
__inode_dir_notify/dnotify_parent (la prima delle quali viene
normalmente chiamata attraverso inode_dir_notify, funzione inline
che si trova in <linux/fcntl.h>
).
Queste ultime due funzioni vengono chiamate dalle implementazioni delle chiamate di sistema che si trovano negli altri file della directory fs. Per esempio, fs/read_write.c notifica le scritture e le letture dai file invocando dnotify_parent; la funzione viene chiamata dopo ogni chiamata di sistema read o write eseguita con successo.
L'invio del segnale è delegato alla funzione send_sigio, in fs/fcntl.c. Qui viene compilata la struttura siginfo, passata poi a send_sig_info (kernel/signal.c) che si occupa dell'effettiva consegna del segnale al processo.
Come strutture dati, il meccanismo dnotify risulta
abbastanza leggero.
Quando tramite fcntl viene attivata o disattivata una richiesta di
notifica su una cartella, il sistema accoda una struttura
dnotify_struct
alla lista inode->i_dnotify
e aggiorna
la maschera di bit inode->i_dnotify_mask
; entrambi i campi
fanno parte dell'inode relativo alla directory osservata.
Ogno struttura nella lista
registra la maschera di eventi, la struct file
, il descrittore
di file nel processo chiamante e il proprietario della richiesta di notifica.
Nel momento in cui avviene un evento notificabile, la lista
i_dnotify
viene scandita solo se l'evento è attivo nella
i_dnotify_mask
della directory corrispondente, per evitare
di scandire inutilmente la lista delle notifiche
in risposta ad eventi cui nessuno
ha dichiarato interesse.
Per esempio, nel caso di watch2, la maschera dei bit
associata all'inode indicherà che tutti gli eventi sono
sotto osservazione e la scansione della lista troverà
una corrispondenza. Nel caso di follow,
assumendo che non ci siano altri processi
che osservano la stessa directory, solo gli eventi DN_MODIFY
porteranno alla scansione della lista, composta in questo caso da
un'unica dnotify_struct
.
Una proposta di soluzione al problema delle notifiche relative alle operazioni sui file è il demone fam (o famd), il "file alteration monitor", un pacchetto sviluppato alla Silicon Graphics nel 1989 e ora rilasciato con licenza GPL/LGPL. Fam implementa un servizio di rete e una API, accessibile tramite una libreria inclusa nel pacchetto, per inviare richieste al demone. Il vantaggio del servizio fam rispetto all'approccio "fai da te" è l'universalità dell'interfaccia, che resta indipendente dal meccanismo utilizzato dal kernel per inviare le notifiche.
Nella versione distribuita da SGI, il demone fam può ricevere le
notifiche tramite imon, il meccanismo di notifica usato in IRIX.
In assenza di tale meccanismo, fam lavora in poll, come tail -f
.
È disponibile, comunque, una patch per far sì che fam si appoggi su
dnotify, come pure un'altra per aggiungere il meccanismo imon al
kernel Linux. Mentre la patch al kernel non è stata integrata e
probabilmente non lo sarà nemmeno in futuro,
le distribuzioni GNU/Linux che distribuiscono
fam includono al suo interno il supporto per dnotify.
Nonostante fam sia teoricamente una buona centralizzazione del problema delle notifiche, utile anche per poter migliorare l'efficienza dei meccanismi sottostanti senza ricompilare le applicazioni, in pratica si tratta di un servizio usato abbastanza raramente. Di conseguenza, le applicazioni in ambiente GNU/Linux normalmente non sono preconfigurate per usufruire del servizio, non potendo fare affidamento sulla sua presenza nel sistema ospite.
Il meccanismo dnotify, pur essendo una funzionalità consolidata del kernel Linux che risolve in maniera abbastanza elegante il problema delle notifiche, soffre comunque di alcuni problemi non trascurabili, che hanno portato alla scrittura di un'implementazione alternativa, chiamata inotify (da inode).
I punti "fastidiosi" di dnotify sono in qualche modo tutti legati alla scelta di appoggiarsi sulle directory, le stesse che sono usate durante il normale accesso al filesystem.
Innanzitutto, poiché la directory sotto osservazione deve essere aperta, il filesystem che la ospita non può essere smontato, perché "in uso". Questo impedisce in pratica l'uso di dnotify su filesystem rimuovibili come dischetti, CD, chiavette USB.
Ogni directory osservata va poi aperta; l'osservazione di un intero albero di directory richiede quindi l'apertura di un elevato numero di file, in certi casi inaccettabile.
La notifica tramite invio di segnale dà luogo a corse critiche
rispetto ad altri tipi di notifica, quali poll e select, per
cui occorre scrivere codice abbastanza complesso per evitare potenziali
malfunzionamenti. Per esempio, il codice di watch2 nel riquadro 4
ha una breve finestra temporale in cui un evento può andare perduto, tra il
controllo "if (!sigcount)
" e la chiamata a select che appare
sulla stessa riga.
Il segnale, anche quando siginfo specifichi il descrittore di file che ha scatenato l'evento, non fornisce tutta l'informazione che è invece a disposizione del kernel nel momento esso in cui notifica l'evento (per esempio, l'informazione su quale sia il file che ha scatenato evento); l'applicativo deve riesaminare l'intera directory per sapere cosa è successo. Alcuni eventi (come la lettura o la scrittura da un dispositivo) non risultano neppure ricnoscibili a posteriori, in quanto non lasciano traccia nel filesystem.
Tutti questi problemi vengono risolti da inotify che al momento (Gennaio 2005) non è ancora parte del kernel ufficiale, anche se probabilmente lo sarà a breve. Il meccanismo proposto è necessariamente incompatibile con dnotify nella sua interfaccia verso le applicazioni, in quanto le notifiche vengono consegnate tramite pacchetti informativi su un file speciale, senza che i programmi debbano aprire la directory che stanno osservando. Le due infrastrutture di notifica possono comunque essere attive contemporaneamente nello stesso kernel.
Il meccanismo dnotify è descritto brevemente in Documentation/dnotify.txt.
La pagina principale di fam è
Il codice di inotify (patch per il kernel e strumenti in spazio
utente) si può trovare presso
|