X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=fileadv.tex;h=1286c56ac2c3a3f2b13d42e4f1a9473d5fc7f866;hp=75f04719580043d508630eda1a59ef497cc50dba;hb=3d3b2a0c6e47ab411bb55925dd9330a7dbd95f96;hpb=dc6f430983f53479f813a2b7bc5ec6517e4b7e98 diff --git a/fileadv.tex b/fileadv.tex index 75f0471..1286c56 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -71,17 +71,18 @@ eseguire in continuazione delle system call che nella gran parte dei casi falliranno. Per superare questo problema è stato introdotto il concetto di \textit{I/O - multiplexing}, una nuova modalità di operazioni che consenta di tenere sotto + multiplexing}, una nuova modalità di operazioni che consente di tenere sotto controllo più file descriptor in contemporanea, permettendo di bloccare un processo quando le operazioni volute non sono possibili, e di riprenderne -l'esecuzione una volta che almeno una di quelle richieste sia disponibile, in +l'esecuzione una volta che almeno una di quelle richieste sia effettuabile, in modo da poterla eseguire con la sicurezza di non restare bloccati. Dato che, come abbiamo già accennato, per i normali file su disco non si ha mai un accesso bloccante, l'uso più comune delle funzioni che esamineremo nei prossimi paragrafi è per i server di rete, in cui esse vengono utilizzate per tenere sotto controllo dei socket; pertanto ritorneremo su di esse con -ulteriori dettagli e qualche esempio in sez.~\ref{sec:TCP_sock_multiplexing}. +ulteriori dettagli e qualche esempio di utilizzo concreto in +sez.~\ref{sec:TCP_sock_multiplexing}. \subsection{Le funzioni \func{select} e \func{pselect}} @@ -96,7 +97,7 @@ Il primo kernel unix-like ad introdurre una interfaccia per l'\textit{I/O \headdecl{sys/time.h} \headdecl{sys/types.h} \headdecl{unistd.h} - \funcdecl{int select(int n, fd\_set *readfds, fd\_set *writefds, fd\_set + \funcdecl{int select(int ndfs, fd\_set *readfds, fd\_set *writefds, fd\_set *exceptfds, struct timeval *timeout)} Attende che uno dei file descriptor degli insiemi specificati diventi @@ -109,8 +110,8 @@ Il primo kernel unix-like ad introdurre una interfaccia per l'\textit{I/O \item[\errcode{EBADF}] Si è specificato un file descriptor sbagliato in uno degli insiemi. \item[\errcode{EINTR}] La funzione è stata interrotta da un segnale. - \item[\errcode{EINVAL}] Si è specificato per \param{n} un valore negativo o - un valore non valido per \param{timeout}. + \item[\errcode{EINVAL}] Si è specificato per \param{ndfs} un valore negativo + o un valore non valido per \param{timeout}. \end{errlist} ed inoltre \errval{ENOMEM}. } @@ -142,7 +143,7 @@ possono usare delle opportune macro di preprocessore: Inserisce il file descriptor \param{fd} nell'insieme. \funcdecl{void \macro{FD\_CLR}(int fd, fd\_set *set)} - Rimuove il file descriptor \param{fd} nell'insieme. + Rimuove il file descriptor \param{fd} dall'insieme. \funcdecl{int \macro{FD\_ISSET}(int fd, fd\_set *set)} Controlla se il file descriptor \param{fd} è nell'insieme. @@ -158,13 +159,19 @@ massimo, esso indica le dimensioni massime dei numeri usati nei \textit{file 1003.1-2001, è definito in \file{sys/select.h}, ed è pari a 1024.} Si tenga presente che i \textit{file descriptor set} devono sempre essere inizializzati con \macro{FD\_ZERO}; passare a \func{select} un valore non inizializzato può -dar luogo a comportamenti non prevedibili. +dar luogo a comportamenti non prevedibili; allo stesso modo usare +\macro{FD\_SET} o \macro{FD\_CLR} con un file descriptor il cui valore eccede +\const{FD\_SETSIZE} può dare luogo ad un comportamento indefinito. La funzione richiede di specificare tre insiemi distinti di file descriptor; il primo, \param{readfds}, verrà osservato per rilevare la disponibilità di effettuare una lettura,\footnote{per essere precisi la funzione ritornerà in tutti i casi in cui la successiva esecuzione di \func{read} risulti non - bloccante, quindi anche in caso di \textit{end-of-file}.} il secondo, + bloccante, quindi anche in caso di \textit{end-of-file}; inoltre con Linux + possono verificarsi casi particolari, ad esempio quando arrivano dati su un + socket dalla rete che poi risultano corrotti e vengono scartati, può + accadere che \func{select} riporti il relativo file descriptor come + leggibile, ma una successiva \func{read} si blocchi.} il secondo, \param{writefds}, per verificare la possibilità effettuare una scrittura ed il terzo, \param{exceptfds}, per verificare l'esistenza di eccezioni (come i dati urgenti \itindex{out-of-band} su un socket, vedi @@ -172,19 +179,19 @@ sez.~\ref{sec:TCP_urgent_data}). Dato che in genere non si tengono mai sotto controllo fino a \const{FD\_SETSIZE} file contemporaneamente la funzione richiede di -specificare qual è il numero massimo dei file descriptor indicati nei tre +specificare qual è il valore più alto fra i file descriptor indicati nei tre insiemi precedenti. Questo viene fatto per efficienza, per evitare di passare e far controllare al kernel una quantità di memoria superiore a quella -necessaria. Questo limite viene indicato tramite l'argomento \param{n}, che -deve corrispondere al valore massimo aumentato di uno.\footnote{i file - descriptor infatti sono contati a partire da zero, ed il valore indica il - numero di quelli da tenere sotto controllo; dimenticarsi di aumentare di uno - il valore di \param{n} è un errore comune.} Infine l'argomento -\param{timeout}, specifica un tempo massimo di attesa prima che la funzione -ritorni; se impostato a \val{NULL} la funzione attende indefinitamente. Si può -specificare anche un tempo nullo (cioè una struttura \struct{timeval} con i -campi impostati a zero), qualora si voglia semplicemente controllare lo stato -corrente dei file descriptor. +necessaria. Questo limite viene indicato tramite l'argomento \param{ndfs}, che +deve corrispondere al valore massimo aumentato di uno.\footnote{si ricordi che + i file descriptor sono numerati progressivamente a partire da zero, ed il + valore indica il numero più alto fra quelli da tenere sotto controllo; + dimenticarsi di aumentare di uno il valore di \param{ndfs} è un errore + comune.} Infine l'argomento \param{timeout} specifica un tempo massimo di +attesa prima che la funzione ritorni; se impostato a \val{NULL} la funzione +attende indefinitamente. Si può specificare anche un tempo nullo (cioè una +struttura \struct{timeval} con i campi impostati a zero), qualora si voglia +semplicemente controllare lo stato corrente dei file descriptor. La funzione restituisce il numero di file descriptor pronti,\footnote{questo è il comportamento previsto dallo standard, ma la standardizzazione della @@ -199,6 +206,15 @@ contenuto. \itindend{file~descriptor~set} +Una volta ritornata la funzione si potrà controllare quali sono i file +descriptor pronti ed operare su di essi, si tenga presente però che si tratta +solo di un suggerimento, esistono infatti condizioni\footnote{ad esempio + quando su un socket arrivano dei dati che poi vengono scartati perché + corrotti.} in cui \func{select} può riportare in maniera spuria che un file +descriptor è pronto in lettura, quando una successiva lettura si bloccherebbe. +Per questo quando si usa \textit{I/O multiplexing} è sempre raccomandato l'uso +delle funzioni di lettura e scrittura in modalità non bloccante. + In Linux \func{select} modifica anche il valore di \param{timeout}, impostandolo al tempo restante in caso di interruzione prematura; questo è utile quando la funzione viene interrotta da un segnale, in tal caso infatti @@ -213,10 +229,10 @@ rimanente.\footnote{questo pu Uno dei problemi che si presentano con l'uso di \func{select} è che il suo comportamento dipende dal valore del file descriptor che si vuole tenere sotto -controllo. Infatti il kernel riceve con \param{n} un valore massimo per tale -valore, e per capire quali sono i file descriptor da tenere sotto controllo -dovrà effettuare una scansione su tutto l'intervallo, che può anche essere -anche molto ampio anche se i file descriptor sono solo poche unità; tutto ciò +controllo. Infatti il kernel riceve con \param{ndfs} un limite massimo per +tale valore, e per capire quali sono i file descriptor da tenere sotto +controllo dovrà effettuare una scansione su tutto l'intervallo, che può anche +essere molto ampio anche se i file descriptor sono solo poche unità; tutto ciò ha ovviamente delle conseguenze ampiamente negative per le prestazioni. Inoltre c'è anche il problema che il numero massimo dei file che si possono @@ -254,8 +270,8 @@ precedenti, ed inoltre aggiunge a \func{select} una nuova funzione \item[\errcode{EBADF}] Si è specificato un file descriptor sbagliato in uno degli insiemi. \item[\errcode{EINTR}] La funzione è stata interrotta da un segnale. - \item[\errcode{EINVAL}] Si è specificato per \param{n} un valore negativo o - un valore non valido per \param{timeout}. + \item[\errcode{EINVAL}] Si è specificato per \param{ndfs} un valore negativo + o un valore non valido per \param{timeout}. \end{errlist} ed inoltre \errval{ENOMEM}.} \end{prototype} @@ -263,9 +279,13 @@ precedenti, ed inoltre aggiunge a \func{select} una nuova funzione La funzione è sostanzialmente identica a \func{select}, solo che usa una struttura \struct{timespec} (vedi fig.~\ref{fig:sys_timeval_struct}) per indicare con maggiore precisione il timeout e non ne aggiorna il valore in -caso di interruzione. Inoltre prende un argomento aggiuntivo \param{sigmask} -che è il puntatore ad una maschera di segnali (si veda -sez.~\ref{sec:sig_sigmask}). La maschera corrente viene sostituita da questa +caso di interruzione.\footnote{in realtà la system call di Linux aggiorna il + valore al tempo rimanente, ma la funzione fornita dalle \acr{glibc} modifica + questo comportamento passando alla system call una variabile locale, in modo + da mantenere l'aderenza allo standard POSIX che richiede che il valore di + \param{timeout} non sia modificato.} Inoltre prende un argomento aggiuntivo +\param{sigmask} che è il puntatore ad una maschera di segnali (si veda +sez.~\ref{sec:sig_sigmask}). La maschera corrente viene sostituita da questa immediatamente prima di eseguire l'attesa, e ripristinata al ritorno della funzione. @@ -293,28 +313,25 @@ interrotta, e la ricezione del segnale non sar Per questo è stata introdotta \func{pselect} che attraverso l'argomento \param{sigmask} permette di riabilitare la ricezione il segnale contestualmente all'esecuzione della funzione,\footnote{in Linux però, fino al - kernel 2.6.16, non è presente la relativa system call, e la funzione è + kernel 2.6.16, non era presente la relativa system call, e la funzione era implementata nelle \acr{glibc} attraverso \func{select} (vedi \texttt{man select\_tut}) per cui la possibilità di \itindex{race~condition} - \textit{race condition} permane; esiste però una soluzione, chiamata - \itindex{self-pipe trick} \textit{self-pipe trick}, che consiste nell'aprire - una pipe (vedi sez.~\ref{sec:ipc_pipes}) ed usare \func{select} sul capo in - lettura della stessa, e indicare l'arrivo di un segnale scrivendo sul capo - in scrittura all'interno del gestore dello stesso; in questo modo anche se - il segnale va perso prima della chiamata di \func{select} questa lo - riconoscerà comunque dalla presenza di dati sulla pipe.} ribloccandolo non -appena essa ritorna, così che il precedente codice potrebbe essere riscritto -nel seguente modo: + \textit{race condition} permaneva; in tale situazione si può ricorrere ad una + soluzione alternativa, chiamata \itindex{self-pipe trick} \textit{self-pipe + trick}, che consiste nell'aprire una pipe (vedi sez.~\ref{sec:ipc_pipes}) + ed usare \func{select} sul capo in lettura della stessa; si può indicare + l'arrivo di un segnale scrivendo sul capo in scrittura all'interno del + gestore dello stesso; in questo modo anche se il segnale va perso prima + della chiamata di \func{select} questa lo riconoscerà comunque dalla + presenza di dati sulla pipe.} ribloccandolo non appena essa ritorna, così +che il precedente codice potrebbe essere riscritto nel seguente modo: \includecodesnip{listati/pselect_norace.c} in questo caso utilizzando \var{oldmask} durante l'esecuzione di \func{pselect} la ricezione del segnale sarà abilitata, ed in caso di interruzione si potranno eseguire le relative operazioni. -% TODO pselect è stata introdotta nel kernel 2.6.16 (o 15 o 17?) insieme a -% ppoll mettere e verificare, vedi articolo LWN http://lwn.net/Articles/176750/ - -\subsection{La funzione \func{poll}} +\subsection{Le funzioni \func{poll} e \func{ppoll}} \label{sec:file_poll} Nello sviluppo di System V, invece di utilizzare l'interfaccia di @@ -351,17 +368,6 @@ indica un'attesa indefinita, mentre un valore nullo comporta il ritorno immediato (e può essere utilizzato per impiegare \func{poll} in modalità \textsl{non-bloccante}). -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{15cm} - \includestruct{listati/pollfd.h} - \end{minipage} - \normalsize - \caption{La struttura \structd{pollfd}, utilizzata per specificare le - modalità di controllo di un file descriptor alla funzione \func{poll}.} - \label{fig:file_pollfd} -\end{figure} - Per ciascun file da controllare deve essere inizializzata una struttura \struct{pollfd} nel vettore indicato dall'argomento \param{ufds}. La struttura, la cui definizione è riportata in fig.~\ref{fig:file_pollfd}, @@ -375,6 +381,17 @@ tutto indipendenti da quelli in uscita (che vengono restituiti in \var{revents}) non è necessario reinizializzare tutte le volte il valore delle strutture \struct{pollfd} a meno di non voler cambiare qualche condizione. +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includestruct{listati/pollfd.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{pollfd}, utilizzata per specificare le + modalità di controllo di un file descriptor alla funzione \func{poll}.} + \label{fig:file_pollfd} +\end{figure} + Le costanti che definiscono i valori relativi ai bit usati nelle maschere binarie dei campi \var{events} e \var{revents} sono riportati in tab.~\ref{tab:file_pollfd_flags}, insieme al loro significato. Le si sono @@ -438,14 +455,437 @@ valore nullo indica che si indica un errore nella chiamata, il cui codice viene riportato al solito tramite \var{errno}. +L'uso di \func{poll} consente di superare alcuni dei problemi illustrati in +precedenza per \func{select}; anzitutto, dato che in questo caso si usa un +vettore di strutture \struct{pollfd} di dimensione arbitraria, non esiste il +limite introdotto dalle dimensioni massime di un \itindex{file~descriptor~set} +\textit{file descriptor set} e la dimensione dei dati passati al kernel +dipende solo dal numero dei file descriptor che si vogliono controllare, non +dal loro valore.\footnote{anche se usando dei bit un \textit{file descriptor + set} può essere più efficiente di un vettore di strutture \struct{pollfd}, + qualora si debba osservare un solo file descriptor con un valore molto alto + ci si troverà ad utilizzare inutilmente un maggiore quantitativo di + memoria.} + +Inoltre con \func{select} lo stesso \itindex{file~descriptor~set} \textit{file + descriptor set} è usato sia in ingresso che in uscita, e questo significa +che tutte le volte che si vuole ripetere l'operazione occorre reinizializzarlo +da capo. Questa operazione, che può essere molto onerosa se i file descriptor +da tenere sotto osservazione sono molti, non è invece necessaria con +\func{poll}. + +Abbiamo visto in sez.~\ref{sec:file_select} come lo standard POSIX preveda una +variante di \func{select} che consente di gestire correttamente la ricezione +dei segnali nell'attesa su un file descriptor. Con l'introduzione di una +implementazione reale di \func{pselect} nel kernel 2.6.16, è stata aggiunta +anche una analoga funzione che svolga lo stesso ruolo per \func{poll}. + +In questo caso si tratta di una estensione che è specifica di Linux e non è +prevista da nessuno standard; essa può essere utilizzata esclusivamente se si +definisce la macro \macro{\_GNU\_SOURCE} ed ovviamente non deve essere usata +se si ha a cuore la portabilità. La funzione è \funcd{ppoll}, ed il suo +prototipo è: +\begin{prototype}{sys/poll.h} + {int ppoll(struct pollfd *fds, nfds\_t nfds, const struct timespec *timeout, + const sigset\_t *sigmask)} + + La funzione attende un cambiamento di stato su un insieme di file + descriptor. + + \bodydesc{La funzione restituisce il numero di file descriptor con attività + in caso di successo, o 0 se c'è stato un timeout e -1 in caso di errore, + ed in quest'ultimo caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] Si è specificato un file descriptor sbagliato in uno + degli insiemi. + \item[\errcode{EINTR}] La funzione è stata interrotta da un segnale. + \item[\errcode{EINVAL}] Il valore di \param{nfds} eccede il limite + \macro{RLIMIT\_NOFILE}. + \end{errlist} + ed inoltre \errval{EFAULT} e \errval{ENOMEM}.} +\end{prototype} -% TODO accennare a ppoll +La funzione ha lo stesso comportamento di \func{poll}, solo che si può +specificare, con l'argomento \param{sigmask}, il puntatore ad una maschera di +segnali; questa sarà la maschera utilizzata per tutto il tempo che la funzione +resterà in attesa, all'uscita viene ripristinata la maschera originale. L'uso +di questa funzione è cioè equivalente, come illustrato nella pagina di +manuale, all'esecuzione atomica del seguente codice: +\includecodesnip{listati/ppoll_means.c} + +Eccetto per \param{timeout}, che come per \func{pselect} deve essere un +puntatore ad una struttura \struct{timespec}, gli altri argomenti comuni con +\func{poll} hanno lo stesso significato, e la funzione restituisce gli stessi +risultati illustrati in precedenza. + + +\subsection{L'interfaccia di \textit{epoll}} +\label{sec:file_epoll} + +\itindbeg{epoll} + +Nonostante \func{poll} presenti alcuni vantaggi rispetto a \func{select}, +anche questa funzione non è molto efficiente quando deve essere utilizzata con +un gran numero di file descriptor,\footnote{in casi del genere \func{select} + viene scartata a priori, perché può avvenire che il numero di file + descriptor ecceda le dimensioni massime di un \itindex{file~descriptor~set} + \textit{file descriptor set}.} in particolare nel caso in cui solo pochi di +questi diventano attivi. Il problema in questo caso è che il tempo impiegato +da \func{poll} a trasferire i dati da e verso il kernel è proporzionale al +numero di file descriptor osservati, non a quelli che presentano attività. + +Quando ci sono decine di migliaia di file descriptor osservati e migliaia di +eventi al secondo,\footnote{il caso classico è quello di un server web di un + sito con molti accessi.} l'uso di \func{poll} comporta la necessità di +trasferire avanti ed indietro da user space a kernel space la lunga lista +delle strutture \struct{pollfd} migliaia di volte al secondo. A questo poi si +aggiunge il fatto che la maggior parte del tempo di esecuzione sarà impegnato +ad eseguire una scansione su tutti i file descriptor tenuti sotto controllo +per determinare quali di essi (in genere una piccola percentuale) sono +diventati attivi. In una situazione come questa l'uso delle funzioni classiche +dell'interfaccia dell'\textit{I/O multiplexing} viene a costituire un collo di +bottiglia che degrada irrimediabilmente le prestazioni. + +Per risolvere questo tipo di situazioni sono state ideate delle interfacce +specialistiche\footnote{come \texttt{/dev/poll} in Solaris, o \texttt{kqueue} + in BSD.} il cui scopo fondamentale è quello di restituire solamente le +informazioni relative ai file descriptor osservati che presentano una +attività, evitando così le problematiche appena illustrate. In genere queste +prevedono che si registrino una sola volta i file descriptor da tenere sotto +osservazione, e forniscono un meccanismo che notifica quali di questi +presentano attività. + +Le modalità con cui avviene la notifica sono due, la prima è quella classica +(quella usata da \func{poll} e \func{select}) che viene chiamata \textit{level + triggered}.\footnote{la nomenclatura è stata introdotta da Jonathan Lemon in + un articolo su \texttt{kqueue} al BSDCON 2000, e deriva da quella usata + nell'elettronica digitale.} In questa modalità vengono notificati i file +descriptor che sono \textsl{pronti} per l'operazione richiesta, e questo +avviene indipendentemente dalle operazioni che possono essere state fatte su +di essi a partire dalla precedente notifica. Per chiarire meglio il concetto +ricorriamo ad un esempio: se su un file descriptor sono diventati disponibili +in lettura 2000 byte ma dopo la notifica ne sono letti solo 1000 (ed è quindi +possibile eseguire una ulteriore lettura dei restanti 1000), in modalità +\textit{level triggered} questo sarà nuovamente notificato come +\textsl{pronto}. + +La seconda modalità, è detta \textit{edge triggered}, e prevede che invece +vengano notificati solo i file descriptor che hanno subito una transizione da +\textsl{non pronti} a \textsl{pronti}. Questo significa che in modalità +\textit{edge triggered} nel caso del precedente esempio il file descriptor +diventato pronto da cui si sono letti solo 1000 byte non verrà nuovamente +notificato come pronto, nonostante siano ancora disponibili in lettura 1000 +byte. Solo una volta che si saranno esauriti tutti i byte disponibili, e che +il file descriptor sia tornato non essere pronto, si potrà ricevere una +ulteriore notifica qualora ritornasse pronto. + +Nel caso di Linux al momento la sola interfaccia che fornisce questo tipo di +servizio è \textit{epoll},\footnote{l'interfaccia è stata creata da Davide + Libenzi, ed è stata introdotta per la prima volta nel kernel 2.5.44, ma la + sua forma definitiva è stata raggiunta nel kernel 2.5.66.} anche se sono in +discussione altre interfacce con le quali si potranno effettuare lo stesso +tipo di operazioni;\footnote{al momento della stesura di queste note (Giugno + 2007) un'altra interfaccia proposta è quella di \textit{kevent}, che + fornisce un sistema di notifica di eventi generico in grado di fornire le + stesse funzionalità di \textit{epoll}, esiste però una forte discussione + intorno a tutto ciò e niente di definito.} \textit{epoll} è in grado di +operare sia in modalità \textit{level triggered} che \textit{edge triggered}. + +La prima versione \textit{epoll} prevedeva l'apertura di uno speciale file di +dispositivo, \texttt{/dev/epoll}, per ottenere un file descriptor da +utilizzare con le funzioni dell'interfaccia,\footnote{il backporting + dell'interfaccia per il kernel 2.4, non ufficiale, utilizza sempre questo + file.} ma poi si è passati all'uso una apposita \textit{system call}. Il +primo passo per usare l'interfaccia di \textit{epoll} è pertanto quello di +chiamare la funzione \funcd{epoll\_create}, il cui prototipo è: +\begin{prototype}{sys/epoll.h} + {int epoll\_create(int size)} + + Apre un file descriptor per \textit{epoll}. + + \bodydesc{La funzione restituisce un file descriptor in caso di successo, o + $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EINVAL}] si è specificato un valore di \param{size} non + positivo. + \item[\errcode{ENFILE}] si è raggiunto il massimo di file descriptor aperti + nel sistema. + \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel per creare + l'istanza. + \end{errlist} +} +\end{prototype} + +La funzione restituisce un file descriptor speciale,\footnote{esso non è + associato a nessun file su disco, inoltre a differenza dei normali file + descriptor non può essere inviato ad un altro processo attraverso un socket + locale (vedi sez.~\ref{sec:sock_fd_passing}).} detto anche \textit{epoll + descriptor}, che viene associato alla infrastruttura utilizzata dal kernel +per gestire la notifica degli eventi; l'argomento \param{size} serve a dare +l'indicazione del numero di file descriptor che si vorranno tenere sotto +controllo, ma costituisce solo un suggerimento per semplificare l'allocazione +di risorse sufficienti, non un valore massimo. + +Una volta ottenuto un file descriptor per \textit{epoll} il passo successivo è +indicare quali file descriptor mettere sotto osservazione e quali operazioni +controllare, per questo si deve usare la seconda funzione dell'interfaccia, +\funcd{epoll\_ctl}, il cui prototipo è: +\begin{prototype}{sys/epoll.h} + {int epoll\_ctl(int epfd, int op, int fd, struct epoll\_event *event)} + + Esegue le operazioni di controllo di \textit{epoll}. + + \bodydesc{La funzione restituisce $0$ in caso di successo o $-1$ in caso di + errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] il file descriptor \param{epfd} o \param{fd} non sono + validi. + \item[\errcode{EEXIST}] l'operazione richiesta è \const{EPOLL\_CTL\_ADD} ma + \param{fd} è già stato inserito in \param{epfd}. + \item[\errcode{EINVAL}] il file descriptor \param{epfd} non è stato ottenuto + con \func{epoll\_create}, o \param{fd} è lo stesso \param{epfd} o + l'operazione richiesta con \param{op} non è supportata. + \item[\errcode{ENOENT}] l'operazione richiesta è \const{EPOLL\_CTL\_MOD} o + \const{EPOLL\_CTL\_DEL} ma \param{fd} non è inserito in \param{epfd}. + \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel gestire + l'operazione richiesta. + \item[\errcode{EPERM}] il file \param{fd} non supporta \textit{epoll}. + \end{errlist} +} +\end{prototype} + +Il comportamento della funzione viene controllato dal valore dall'argomento +\param{op} che consente di specificare quale operazione deve essere eseguita. +Le costanti che definiscono i valori utilizzabili per \param{op} +sono riportate in tab.~\ref{tab:epoll_ctl_operation}, assieme al significato +delle operazioni cui fanno riferimento. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{8cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{EPOLL\_CTL\_ADD}& aggiunge un nuovo file descriptor da osservare + \param{fd} alla lista dei file descriptor + controllati tramite \param{epfd}, in + \param{event} devono essere specificate le + modalità di osservazione.\\ + \const{EPOLL\_CTL\_MOD}& modifica le modalità di osservazione del file + descriptor \param{fd} secondo il contenuto di + \param{event}.\\ + \const{EPOLL\_CTL\_DEL}& rimuove il file descriptor \param{fd} dalla lista + dei file controllati tramite \param{epfd}.\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{op} che consentono di scegliere quale + operazione di controllo effettuare con la funzione \func{epoll\_ctl}.} + \label{tab:epoll_ctl_operation} +\end{table} + +La funzione prende sempre come primo argomento un file descriptor di +\textit{epoll}, \param{epfd}, che deve essere stato ottenuto in precedenza con +una chiamata a \func{epoll\_create}. L'argomento \param{fd} indica invece il +file descriptor che si vuole tenere sotto controllo, quest'ultimo può essere +un qualunque file descriptor utilizzabile con \func{poll}, ed anche un altro +file descriptor di \textit{epoll}, ma non lo stesso \param{epfd}. + +L'ultimo argomento, \param{event}, deve essere un puntatore ad una struttura +di tipo \struct{epoll\_event}, ed ha significato solo con le operazioni +\const{EPOLL\_CTL\_MOD} e \const{EPOLL\_CTL\_ADD}, per le quali serve ad +indicare quale tipo di evento relativo ad \param{fd} si vuole che sia tenuto +sotto controllo. L'argomento viene ignorato con l'operazione +\const{EPOLL\_CTL\_DEL}.\footnote{fino al kernel 2.6.9 era comunque richiesto + che questo fosse un puntatore valido, anche se poi veniva ignorato, a + partire dal 2.6.9 si può specificare anche anche un valore \texttt{NULL}.} + + + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includestruct{listati/epoll_event.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{epoll\_event}, che consente di specificare + gli eventi associati ad un file descriptor controllato con + \textit{epoll}.} + \label{fig:epoll_event} +\end{figure} + +La struttura \struct{epoll\_event} è l'analoga di \struct{pollfd} e come +quest'ultima serve sia in ingresso (quando usata con \func{epoll\_ctl}) ad +impostare quali eventi osservare, che in uscita (nei risultati ottenuti con +\func{epoll\_wait}) per ricevere le notifiche degli eventi avvenuti. La sua +definizione è riportata in fig.~\ref{fig:epoll_event}. + +Il primo campo, \var{events}, è una maschera binaria in cui ciascun bit +corrisponde o ad un tipo di evento, o una modalità di notifica; detto campo +deve essere specificato come OR aritmetico delle costanti riportate in +tab.~\ref{tab:epoll_events}. Il secondo campo, \var{data}, serve ad indicare a +quale file descriptor si intende fare riferimento, ed in astratto può +contenere un valore qualsiasi che permetta di identificarlo, di norma comunque +si usa come valore lo stesso \param{fd}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{8cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{EPOLLIN} & Il file è pronto per le operazioni di lettura + (analogo di \const{POLLIN}).\\ + \const{EPOLLOUT} & Il file è pronto per le operazioni di scrittura + (analogo di \const{POLLOUT}).\\ + \const{EPOLLRDHUP} & l'altro capo di un socket di tipo + \const{SOCK\_STREAM} (vedi sez.~\ref{sec:sock_type}) + ha chiuso la connessione o il capo in scrittura + della stessa (vedi sez.~\ref{sec:TCP_shutdown}).\\ + \const{EPOLLPRI} & Ci sono \itindex{out-of-band} dati urgenti + disponibili in lettura (analogo di + \const{POLLPRI}); questa condizione viene comunque + riportata in uscita, e non è necessaria impostarla + in ingresso.\\ + \const{EPOLLERR} & Si è verificata una condizione di errore + (analogo di \const{POLLERR}); questa condizione + viene comunque riportata in uscita, e non è + necessaria impostarla in ingresso.\\ + \const{EPOLLHUP} & Si è verificata una condizione di hung-up.\\ + \const{EPOLLET} & Imposta la notifica in modalità \textit{edge + triggered} per il file descriptor associato.\\ + \const{EPOLLONESHOT}& Imposta la modalità \textit{one-shot} per il file + descriptor associato.\footnotemark\\ + \hline + \end{tabular} + \caption{Costanti che identificano i bit del campo \param{events} di + \struct{epoll\_event}.} + \label{tab:epoll_events} +\end{table} + +\footnotetext{questa modalità è disponibile solo a partire dal kernel 2.6.2.} + +Le modalità di utilizzo di \textit{epoll} prevedano che si definisca qual'è +l'insieme dei file descriptor da tenere sotto controllo tramite un certo +\textit{epoll descriptor} \param{epfd} attraverso una serie di chiamate a +\const{EPOLL\_CTL\_ADD}.\footnote{un difetto dell'interfaccia è che queste + chiamate devono essere ripetute per ciascun file descriptor, incorrendo in + una perdita di prestazioni qualora il numero di file descriptor sia molto + grande; per questo è stato proposto di introdurre come estensione una + funzione \func{epoll\_ctlv} che consenta di effettuare con una sola chiamata + le impostazioni per un blocco di file descriptor.} L'uso di +\const{EPOLL\_CTL\_MOD} consente in seguito di modificare le modalità di +osservazione di un file descriptor che sia già stato aggiunto alla lista di +osservazione. + +Le impostazioni di default prevedono che la notifica degli eventi richiesti +sia effettuata in modalità \textit{level triggered}, a meno che sul file +descriptor non si sia impostata la modalità \textit{edge triggered}, +registrandolo con \const{EPOLLET} attivo nel campo \var{events}. Si tenga +presente che è possibile tenere sotto osservazione uno stesso file descriptor +su due \textit{epoll descriptor} diversi, ed entrambi riceveranno le +notifiche, anche se questa pratica è sconsigliata. + +Qualora non si abbia più interesse nell'osservazione di un file descriptor lo +si può rimuovere dalla lista associata a \param{epfd} con +\const{EPOLL\_CTL\_DEL}; si tenga conto inoltre che i file descriptor sotto +osservazione che vengono chiusi sono eliminati dalla lista automaticamente e +non è necessario usare \const{EPOLL\_CTL\_DEL}. + +Infine una particolare modalità di notifica è quella impostata con +\const{EPOLLONESHOT}: a causa dell'implementazione di \textit{epoll} infatti +quando si è in modalità \textit{edge triggered} l'arrivo in rapida successione +di dati in blocchi separati\footnote{questo è tipico con i socket di rete, in + quanto i dati arrivano a pacchetti.} può causare una generazione di eventi +(ad esempio segnalazioni di dati in lettura disponibili) anche se la +condizione è già stata rilevata.\footnote{si avrebbe cioè una rottura della + logica \textit{edge triggered}.} + +Anche se la situazione è facile da gestire, la si può evitare utilizzando +\const{EPOLLONESHOT} per impostare la modalità \textit{one-shot}, in cui la +notifica di un evento viene effettuata una sola volta, dopo di che il file +descriptor osservato, pur restando nella lista di osservazione, viene +automaticamente disattivato,\footnote{la cosa avviene contestualmente al + ritorno di \func{epoll\_wait} a causa dell'evento in questione.} e per +essere riutilizzato dovrà essere riabilitato esplicitamente con una successiva +chiamata con \const{EPOLL\_CTL\_MOD}. + +Una volta impostato l'insieme di file descriptor che si vogliono osservare con +i relativi eventi, la funzione che consente di attendere l'occorrenza di uno +di tali eventi è \funcd{epoll\_wait}, il cui prototipo è: +\begin{prototype}{sys/epoll.h} + {int epoll\_wait(int epfd, struct epoll\_event * events, int maxevents, int + timeout)} + + Attende che uno dei file descriptor osservati sia pronto. + + \bodydesc{La funzione restituisce il numero di file descriptor pronti in + caso di successo o $-1$ in caso di errore, nel qual caso \var{errno} + assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] il file descriptor \param{epfd} non è valido. + \item[\errcode{EFAULT}] il puntatore \param{events} non è valido. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima + della scadenza di \param{timeout}. + \item[\errcode{EINVAL}] il file descriptor \param{epfd} non è stato ottenuto + con \func{epoll\_create}, o \param{maxevents} non è maggiore di zero. + \end{errlist} +} +\end{prototype} + +La funzione si blocca in attesa di un evento per i file descriptor registrati +nella lista di osservazione di \param{epfd} fino ad un tempo massimo +specificato in millisecondi tramite l'argomento \param{timeout}. Gli eventi +registrati vengono riportati in un vettore di strutture \struct{epoll\_event} +(che deve essere stato allocato in precedenza) all'indirizzo indicato +dall'argomento \param{events}, fino ad un numero massimo di eventi impostato +con l'argomento \param{maxevents}. + +La funzione ritorna il numero di eventi rilevati, o un valore nullo qualora +sia scaduto il tempo massimo impostato con \param{timeout}. Per quest'ultimo, +oltre ad un numero di millisecondi, si può utilizzare il valore nullo, che +indica di non attendere e ritornare immediatamente,\footnote{anche in questo + caso il valore di ritorno sarà nullo.} o $-1$, che indica un'attesa +indefinita. L'argomento \param{maxevents} dovrà invece essere sempre un intero +positivo. + +Come accennato la funzione restituisce i suoi risultati nel vettore di +strutture \struct{epoll\_event} puntato da \param{events}; in tal caso nel +campo \param{events} di ciascuna di esse saranno attivi i flag relativi agli +eventi accaduti, mentre nel campo \var{data} sarà restituito il valore che era +stato impostato (per il file descriptor per cui si è verificato l'evento) +quando questo era stato registrato con le operazioni \const{EPOLL\_CTL\_MOD} o +\const{EPOLL\_CTL\_ADD}. + +Si ricordi che le occasioni per cui \func{epoll\_wait} ritorna dipendono da +come si è impostata la modalità di osservazione (se \textit{level triggered} o +\textit{edge triggered}) del singolo file descriptor. L'interfaccia assicura +che se arrivano più eventi fra due chiamate successive ad \func{epoll\_wait} +questi vengano combinati. Inoltre qualora su di esso fossero presenti eventi +non ancora notificati, e si effettuasse una modifica dell'osservazione con +\const{EPOLL\_CTL\_MOD} questi verrebbero riletti alla luce delle modifiche. + +Si tenga presente infine che con l'uso della modalità \textit{edge triggered} +il ritorno di \func{epoll\_wait} indica un file descriptor è pronto e resterà +tale fintanto che non si sono completamente esaurite le operazioni su di esso, +questo può essere rilevato con un errore di \errcode{EAGAIN} in una +\func{read} o una \func{write},\footnote{è opportuno ricordare ancora una + volta che l'uso dell'I/O multiplexing richiede di operare sui file in + modalità non bloccante.} ma anche con il fatto che sono stati restituiti +meno dati di quelli richiesti. + +Come per le precedenti \func{select} e \func{poll}, essendo queste funzioni +utiilizzate prevalentemente con i server di rete, tratteremo degli esempi del +loro più avanti, nella trattazione dei socket, ed in particolare in +sez.~\ref{sec:TCP_sock_multiplexing}. + + +\itindend{epoll} -%\subsection{L'interfaccia di \textit{epoll}} -%\label{sec:file_epoll} -% placeholder ... -% TODO epoll \section{L'accesso \textsl{asincrono} ai file} \label{sec:file_asyncronous_access} @@ -458,46 +898,52 @@ contesto le modalit \textsl{asincrona}, quelle cioè in cui un processo non deve bloccarsi in attesa della disponibilità dell'accesso al file, ma può proseguire nell'esecuzione utilizzando invece un meccanismo di notifica asincrono (di -norma un segnale), per essere avvisato della possibilità di eseguire le +norma un segnale, ma esistono anche altre interfacce, come \itindex{inotify} +\textit{inotify}), per essere avvisato della possibilità di eseguire le operazioni di I/O volute. -\subsection{Operazioni asincrone sui file} +\subsection{Il \textit{Signal driven I/O}} \label{sec:file_asyncronous_operation} -Abbiamo accennato in sez.~\ref{sec:file_open} che è possibile, attraverso l'uso -del flag \const{O\_ASYNC},\footnote{l'uso del flag di \const{O\_ASYNC} e dei - comandi \const{F\_SETOWN} e \const{F\_GETOWN} per \func{fcntl} è specifico - di Linux e BSD.} aprire un file in modalità asincrona, così come è possibile -attivare in un secondo tempo questa modalità impostando questo flag attraverso -l'uso di \func{fcntl} con il comando \const{F\_SETFL} (vedi -sez.~\ref{sec:file_fcntl}). - -In realtà in questo caso non si tratta di eseguire delle operazioni di lettura -o scrittura del file in modo asincrono (tratteremo questo, che più -propriamente è detto \textsl{I/O asincrono} in -sez.~\ref{sec:file_asyncronous_io}), quanto di un meccanismo asincrono di -notifica delle variazione dello stato del file descriptor aperto in questo -modo. - -Quello che succede in questo caso è che il sistema genera un segnale -(normalmente \const{SIGIO}, ma è possibile usarne altri con il comando -\const{F\_SETSIG} di \func{fcntl}) tutte le volte che diventa possibile -leggere o scrivere dal file descriptor che si è posto in questa modalità. Si -può inoltre selezionare, con il comando \const{F\_SETOWN} di \func{fcntl}, +Abbiamo accennato in sez.~\ref{sec:file_open} che è possibile, attraverso +l'uso del flag \const{O\_ASYNC},\footnote{l'uso del flag di \const{O\_ASYNC} e + dei comandi \const{F\_SETOWN} e \const{F\_GETOWN} per \func{fcntl} è + specifico di Linux e BSD.} aprire un file in modalità asincrona, così come è +possibile attivare in un secondo tempo questa modalità impostando questo flag +attraverso l'uso di \func{fcntl} con il comando \const{F\_SETFL} (vedi +sez.~\ref{sec:file_fcntl}). + +In realtà parlare di apertura in modalità asincrona non significa che le +operazioni di lettura o scrittura del file vengono eseguite in modo asincrono +(tratteremo questo, che è ciò che più propriamente viene chiamato \textsl{I/O + asincrono}, in sez.~\ref{sec:file_asyncronous_io}), quanto dell'attivazione +un meccanismo di notifica asincrona delle variazione dello stato del file +descriptor aperto in questo modo. Quello che succede in questo caso è che il +sistema genera un segnale (normalmente \const{SIGIO}, ma è possibile usarne +altri con il comando \const{F\_SETSIG} di \func{fcntl}) tutte le volte che +diventa possibile leggere o scrivere dal file descriptor che si è posto in +questa modalità.\footnote{questa modalità non è utilizzabile con i file + ordinari ma solo con socket, file di terminale o pseudo terminale, e, a + partire dal kernel 2.6, anche per fifo e pipe.} + +Si può inoltre selezionare, con il comando \const{F\_SETOWN} di \func{fcntl}, quale processo (o gruppo di processi) riceverà il segnale. Se pertanto si effettuano le operazioni di I/O in risposta alla ricezione del segnale non ci sarà più la necessità di restare bloccati in attesa della disponibilità di -accesso ai file; per questo motivo Stevens chiama questa modalità -\textit{signal driven I/O}. - -In questo modo si può evitare l'uso delle funzioni \func{poll} o \func{select} -che, quando vengono usate con un numero molto grande di file descriptor, non -hanno buone prestazioni. % aggiungere cenno a epoll quando l'avrò scritta - In tal caso infatti la maggior parte del loro tempo -di esecuzione è impegnato ad eseguire una scansione su tutti i file descriptor -tenuti sotto controllo per determinare quali di essi (in genere una piccola -percentuale) sono diventati attivi. +accesso ai file. + +Per questo motivo Stevens, ed anche le pagine di manuale di +Linux, chiamano questa modalità ``\textit{Signal driven I/O}''. Questa è +ancora un'altra modalità di gestione dell'I/O, alternativa all'uso di +\itindex{epoll} \textit{epoll},\footnote{anche se le prestazioni ottenute con + questa tecnica sono inferiori, il vantaggio è che questa modalità è + utilizzabile anche con kernel che non supportano \textit{epoll}, come quelli + della serie 2.4, ottenendo comunque prestazioni superiori a quelle che si + hanno con \func{poll} e \func{select}.} che consente di evitare l'uso delle +funzioni \func{poll} o \func{select} che, come illustrato in +sez.~\ref{sec:file_epoll}, quando vengono usate con un numero molto grande di +file descriptor, non hanno buone prestazioni. Tuttavia con l'implementazione classica dei segnali questa modalità di I/O presenta notevoli problemi, dato che non è possibile determinare, quando i @@ -505,13 +951,15 @@ file descriptor sono pi segnale. Inoltre dato che i segnali normali non si accodano (si ricordi quanto illustrato in sez.~\ref{sec:sig_notification}), in presenza di più file descriptor attivi contemporaneamente, più segnali emessi nello stesso momento -verrebbero notificati una volta sola. Linux però supporta le estensioni -POSIX.1b dei segnali real-time, che vengono accodati e che permettono di -riconoscere il file descriptor che li ha emessi. In questo caso infatti si può -fare ricorso alle informazioni aggiuntive restituite attraverso la struttura -\struct{siginfo\_t}, utilizzando la forma estesa \var{sa\_sigaction} del -gestore installata con il flag \const{SA\_SIGINFO} (si riveda quanto -illustrato in sez.~\ref{sec:sig_sigaction}). +verrebbero notificati una volta sola. + +Linux però supporta le estensioni POSIX.1b dei segnali real-time, che vengono +accodati e che permettono di riconoscere il file descriptor che li ha emessi. +In questo caso infatti si può fare ricorso alle informazioni aggiuntive +restituite attraverso la struttura \struct{siginfo\_t}, utilizzando la forma +estesa \var{sa\_sigaction} del gestore installata con il flag +\const{SA\_SIGINFO} (si riveda quanto illustrato in +sez.~\ref{sec:sig_sigaction}). Per far questo però occorre utilizzare le funzionalità dei segnali real-time (vedi sez.~\ref{sec:sig_real_time}) impostando esplicitamente con il comando @@ -531,11 +979,18 @@ risposta a seconda del segnale usato, dato che i segnali real-time supportano anche questa funzionalità. In questo modo si può identificare immediatamente un file su cui l'accesso è diventato possibile evitando completamente l'uso di funzioni come \func{poll} e \func{select}, almeno fintanto che non si satura -la coda. Se infatti si eccedono le dimensioni di quest'ultima, il kernel, non -potendo più assicurare il comportamento corretto per un segnale real-time, -invierà al suo posto un solo \const{SIGIO}, su cui si saranno accumulati tutti -i segnali in eccesso, e si dovrà allora determinare con un ciclo quali sono i -file diventati attivi. +la coda. + +Se infatti si eccedono le dimensioni di quest'ultima, il kernel, non potendo +più assicurare il comportamento corretto per un segnale real-time, invierà al +suo posto un solo \const{SIGIO}, su cui si saranno accumulati tutti i segnali +in eccesso, e si dovrà allora determinare con un ciclo quali sono i file +diventati attivi. L'unico modo per essere sicuri che questo non avvenga è di +impostare la lunghezza della coda dei segnali real-time ad una dimensione +identica al valore massimo del numero di file descriptor +utilizzabili.\footnote{vale a dire impostare il contenuto di + \texttt{/proc/sys/kernel/rtsig-max} allo stesso valore di quello di + \texttt{/proc/sys/fs/file-max}.} % TODO fare esempio che usa O_ASYNC @@ -576,11 +1031,11 @@ soluzione disponibile con l'interfaccia tradizionale, che Queste nuove funzionalità sono delle estensioni specifiche, non standardizzate, che sono disponibili soltanto su Linux (anche se altri kernel -supportano meccanismi simili). Esse sono realizzate, e solo a partire dalla -versione 2.4 del kernel, attraverso l'uso di alcuni \textsl{comandi} -aggiuntivi per la funzione \func{fcntl} (vedi sez.~\ref{sec:file_fcntl}), che -divengono disponibili soltanto se si è definita la macro \macro{\_GNU\_SOURCE} -prima di includere \file{fcntl.h}. +supportano meccanismi simili). Alcune di esse sono realizzate, e solo a +partire dalla versione 2.4 del kernel, attraverso l'uso di alcuni +\textsl{comandi} aggiuntivi per la funzione \func{fcntl} (vedi +sez.~\ref{sec:file_fcntl}), che divengono disponibili soltanto se si è +definita la macro \macro{\_GNU\_SOURCE} prima di includere \file{fcntl.h}. \index{file!lease|(} @@ -649,7 +1104,8 @@ su un file, e che un \textit{lease} pu può ottenere un \textit{lease} soltanto per un file appartenente ad un \acr{uid} corrispondente a quello del processo. Soltanto un processo con privilegi di amministratore (cioè con la \itindex{capabilities} capability -\const{CAP\_LEASE}) può acquisire \textit{lease} su qualunque file. +\const{CAP\_LEASE}, vedi sez.~\ref{sec:proc_capabilities}) può acquisire +\textit{lease} su qualunque file. Se su un file è presente un \textit{lease} quando il \textit{lease breaker} esegue una \func{truncate} o una \func{open} che confligge con @@ -693,28 +1149,20 @@ kernel Benché possa risultare utile per sincronizzare l'accesso ad uno stesso file da parte di più processi, l'uso dei \textit{file lease} non consente comunque di -risolvere il problema di rilevare automaticamente quando un file viene -modificato, che è quanto necessario ad esempio ai programma di gestione dei -file dei vari desktop grafici. +risolvere il problema di rilevare automaticamente quando un file o una +directory vengono modificati, che è quanto necessario ad esempio ai programma +di gestione dei file dei vari desktop grafici. Per risolvere questo problema è stata allora creata un'altra interfaccia, chiamata \textit{dnotify}, che consente di richiedere una notifica quando una directory, o di uno qualunque dei file in essa contenuti, viene modificato. Come per i \textit{file lease} la notifica avviene di default attraverso il -segnale \const{SIGIO}, ma questo può essere modificato e si può ottenere nel -gestore il file descriptor che è stato modificato dal contenuto della -struttura \struct{siginfo\_t}. +segnale \const{SIGIO}, ma se ne può utilizzare un altro. Inoltre si potrà +ottenere nel gestore del segnale il file descriptor che è stato modificato +tramite il contenuto della struttura \struct{siginfo\_t}. \index{file!lease|)} -Ci si può registrare per le notifiche dei cambiamenti al contenuto di una -certa directory eseguendo \func{fcntl} su un file descriptor \param{fd} -associato alla stessa con il comando \const{F\_NOTIFY}. In questo caso -l'argomento \param{arg} serve ad indicare per quali classi eventi si vuole -ricevere la notifica, e prende come valore una maschera binaria composta -dall'OR aritmetico di una o più delle costanti riportate in -tab.~\ref{tab:file_notify}. - \begin{table}[htb] \centering \footnotesize @@ -750,24 +1198,228 @@ tab.~\ref{tab:file_notify}. \label{tab:file_notify} \end{table} -A meno di non impostare in maniera esplicita una notifica permanente usando -\const{DN\_MULTISHOT}, la notifica è singola: viene cioè inviata una sola -volta quando si verifica uno qualunque fra gli eventi per i quali la si è -richiesta. Questo significa che un programma deve registrarsi un'altra volta se -desidera essere notificato di ulteriori cambiamenti. Se si eseguono diverse +Ci si può registrare per le notifiche dei cambiamenti al contenuto di una +certa directory eseguendo la funzione \func{fcntl} su un file descriptor +associato alla stessa con il comando \const{F\_NOTIFY}. In questo caso +l'argomento \param{arg} di \func{fcntl} serve ad indicare per quali classi +eventi si vuole ricevere la notifica, e prende come valore una maschera +binaria composta dall'OR aritmetico di una o più delle costanti riportate in +tab.~\ref{tab:file_notify}. + +A meno di non impostare in maniera esplicita una notifica permanente usando il +valore \const{DN\_MULTISHOT}, la notifica è singola: viene cioè inviata una +sola volta quando si verifica uno qualunque fra gli eventi per i quali la si è +richiesta. Questo significa che un programma deve registrarsi un'altra volta +se desidera essere notificato di ulteriori cambiamenti. Se si eseguono diverse chiamate con \const{F\_NOTIFY} e con valori diversi per \param{arg} questi ultimi si \textsl{accumulano}; cioè eventuali nuovi classi di eventi specificate in chiamate successive vengono aggiunte a quelle già impostate nelle precedenti. Se si vuole rimuovere la notifica si deve invece specificare un valore nullo. + +\index{file!inotify|(} + +Il maggiore problema di \textit{dnotify} è quello della scalabilità: si deve +usare un file descriptor per ciascuna directory che si vuole tenere sotto +controllo, il che porta facilmente ad un eccesso di file aperti. Inoltre +quando la directory è su un dispositivo rimovibile, mantenere un file +descriptor aperto comporta l'impossibilità di smontare il dispositivo e +rimuoverlo, complicando la gestione. + +Un secondo problema è che l'interfaccia consente solo di tenere sotto +controllo il contenuto di una directory; la modifica di un file viene +segnalata, ma poi devo verificare quale è. Infine l'uso dei segnali come +interfaccia di notifica comporta tutti i problemi di gestione visti in +sez.~\ref{sec:sig_management} e sez.~\ref{sec:sig_control}, e per questo in +generale quella di \textit{dnotify} viene considerata una interfaccia di +usabilità problematica. + \index{file!dnotify|)} +Per questa serie di motivi, a partire dal kernel 2.6.13, è stata introdotta +una nuova interfaccia per l'osservazione delle modifiche a file o directory, +chiamata \textit{inotify}.\footnote{le corrispondenti funzioni di interfaccia + sono state introdotte nelle glibc 2.4.} Questa è una interfaccia specifica +di Linux (pertanto non deve essere usata se si devono scrivere programmi +portabili), ed è basata sull'uso di una coda di notifica degli eventi +associata ad un singolo file descriptor, risolvendo così il principale +problema di \itindex{dnotify} \textit{dnotify}. La coda viene creata +attraverso la funzione \funcd{inotify\_init}, il cui prototipo è: +\begin{prototype}{sys/inotify.h} + {int inotify\_init(void)} + + Inizializza una istanza di \textit{inotify}. + + \bodydesc{La funzione restituisce un file descriptor in caso di successo, o + $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EMFILE}] si è raggiunto il numero massimo di istanze di + \textit{inotify} consentite all'utente. + \item[\errcode{ENFILE}] si è raggiunto il massimo di file descriptor aperti + nel sistema. + \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel per creare + l'istanza. + \end{errlist} +} +\end{prototype} + +La funzione non prende alcun argomento, e restituisce un file descriptor +associato alla coda, attraverso il quale verranno effettuate le operazioni di +notifica. Si tratta di un file descriptor speciale, che non è associato a +nessun file, ma che viene utilizzato per notificare gli eventi che si sono +posti in osservazione all'applicazione che usa l'interfaccia di +\textit{inotify}. Dato che questo file descriptor non è associato a nessun +file o directory, questo consente di evitare l'inconveniente di non poter +smontare un filesystem i cui file sono tenuti sotto osservazione.\footnote{ed + una delle caratteristiche dell'interfaccia di \textit{inotify} è proprio + quella di notificare il fatto che il filesystem su cui si trova il file o la + directory osservata è stato smontato.} + +Inoltre trattandosi di un file descriptor a tutti gli effetti, esso potrà +essere utilizzato come argomento per le funzioni \func{select} e \func{poll}, +e siccome gli eventi vengono notificati come dati disponibili in lettura sul +file descriptor, dette funzioni ritorneranno tutte le volte che si avrà un +evento di notifica. Così, invece di dover utilizzare i segnali, si potrà +gestire l'osservazione delle modifiche con l'\textit{I/O multiplexing}, +utilizzando secondo le modalità illustrate in +sez.~\ref{sec:file_multiplexing}. + +Infine l'interfaccia di \textit{inotify} consente di mettere sotto +osservazione sia singoli file, che intere directory; in quest'ultimo caso +l'interfaccia restituirà informazioni sia riguardo alla directory che ai file +che essa contiene. Una volta creata la coda di notifica si devono definire +gli eventi da tenere sotto osservazione; questo viene fatto tramite una +\textsl{lista di osservazione} (o \textit{watch list}) associata alla coda. +Per gestire la lista di osservazione l'interfaccia fornisce due funzioni, la +prima di queste è \funcd{inotify\_add\_watch}, il cui prototipo è: +\begin{prototype}{sys/inotify.h} + {int inotify\_add\_watch(int fd, const char *pathname, uint32\_t mask)} + + Aggiunge un evento di osservazione alla lista di osservazione di \param{fd}. + + \bodydesc{La funzione restituisce un valore positivo in caso di successo, o + $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EACCESS}] non si ha accesso in lettura al file indicato. + \item[\errcode{EINVAL}] \param{mask} non contiene eventi legali o \param{fd} + non è un filesystem di \textit{inotify}. + \item[\errcode{ENOSPC}] si è raggiunto il numero massimo di voci di + osservazione o il kernel non ha potuto allocare una risorsa necessaria. + \end{errlist} + ed inoltre \errval{EFAULT}, \errval{ENOMEM} e \errval{EBADF}.} +\end{prototype} + +La funzione consente di creare un \textsl{evento di osservazione} (un +cosiddetto ``\textit{watch}'') nella lista di una coda di notifica, indicata +specificando il file descriptor ad essa associato nell'argomento \param{fd}. +Il file o la directory da porre sotto osservazione viene invece indicato per +nome, che viene passato nell'argomento \param{pathname}. Infine il terzo +argomento, \param{mask}, indica che tipo di eventi devono essere tenuti sotto +osservazione. Questo deve essere specificato come maschera binaria combinando +i valori delle costanti riportate in tab.~\ref{tab:inotify_event_watch}. In +essa si sono marcati con un ``$\bullet$'' gli eventi che, quando specificati +per una directory, vengono osservati anche su tutti i file che essa contiene. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|c|p{10cm}|} + \hline + \textbf{Valore} & & \textbf{Significato} \\ + \hline + \hline + \const{IN\_ACCESS} &$\bullet$& c'è stato accesso al file in + lettura.\\ + \const{IN\_ATTRIB} &$\bullet$& ci sono stati cambiamenti sui dati + dell'inode.\\ + \const{IN\_CLOSE\_WRITE} &$\bullet$& è stato chiuso un file aperto in + scrittura.\\ + \const{IN\_CLOSE\_NOWRITE}&$\bullet$& è stato chiuso un file aperto in + sola lettura.\\ + \const{IN\_CREATE} &$\bullet$& è stato creato un file o una + directory in una directory sotto + osservazione.\\ + \const{IN\_DELETE} &$\bullet$& è stato cancellato un file o una + directory in una directory sotto + osservazione.\\ + \const{IN\_DELETE\_SELF} & & è stato cancellato il file (o la + directory) sotto osservazione.\\ + \const{IN\_MODIFY} &$\bullet$& è stato modificato il file.\\ + \const{IN\_MOVE\_SELF} & & è stato rinominato il file (o la + directory) sotto osservazione.\\ + \const{IN\_MOVED\_FROM} &$\bullet$& un file è stato spostato fuori dalla + directory sotto osservazione.\\ + \const{IN\_MOVED\_TO} &$\bullet$& un file è stato spostato nella + directory sotto osservazione.\\ + \const{IN\_OPEN} &$\bullet$& un file è stato aperto.\\ + \hline + \end{tabular} + \caption{Le costanti che identificano i valori per la maschera binaria + dell'argomento \param{mask} di \func{inotify\_add\_watch}.} + \label{tab:inotify_event_watch} +\end{table} + +Se non esiste nessun \textit{watch} per il file (o la directory) specificata +questo verrà creato per gli eventi specificati dall'argomento \param{mask}, +altrimenti la funzione sovrascriverà le impostazioni precedenti. In caso di +successo la funzione ritorna un intero positivo, detto \textit{watch + descriptor} che identifica univocamente l'evento di osservazione. Questo +valore è importante perché è soltanto con esso che si può rimuovere un evento +di osservazione, usando la seconda funzione dell'interfaccia di gestione, +\funcd{inotify\_rm\_watch}, il cui prototipo è: +\begin{prototype}{sys/inotify.h} + {int inotify\_rm\_watch(int fd, uint32\_t wd)} + + Rimuove un evento di osservazione. + + \bodydesc{La funzione restituisce 0 in caso di successo, o $-1$ in caso di + errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] non si è specificato in \param{fd} un file descriptor + valido. + \item[\errcode{EINVAL}] il valore di \param{wd} non è corretto, o \param{fd} + non è associato ad una coda di notifica. + \end{errlist} +} +\end{prototype} + +Oltre che per la rimozione, il \textit{watch descriptor} viene usato anche per +identificare l'evento a cui si fa riferimento nella lista dei risultati +restituiti da \textit{inotify} + + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includestruct{listati/inotify_event.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{inotify\_event}.} + \label{fig:inotify_event} +\end{figure} + + +Inoltre l'interfaccia di \textit{inotify} permette di conoscere, come avviene +per i file descriptor associati ai socket (si veda al proposito quanto +trattato in sez.~\ref{sec:sock_ioctl_IP}) il numero di byte disponibili in +lettura sul nostro file descriptor, utilizzando su di esso l'operazione +\const{FIONREAD} con \func{ioctl}.\footnote{questa è una delle operazioni + speciali (che abbiamo visto in sez.~\ref{sec:file_ioctl}) che nel caso è + disponibile solo per i socket e per i file descriptor creati con + \func{inotify\_init}.} Questo consente anche di ottenere rapidamente il +numero di file che sono cambiati. + -\index{file!inotify|)} % TODO inserire anche inotify, vedi http://www.linuxjournal.com/article/8478 -\index{file!inotify|(} +% TODO e man inotify +\index{file!inotify|)} + + +% TODO inserire anche eventfd (vedi http://lwn.net/Articles/233462/) +% e le restanti signalfd e timerfd introdotte con il 2.6.22 +% o trovargli un posto migliore \subsection{L'interfaccia POSIX per l'I/O asincrono} @@ -1005,7 +1657,7 @@ operazioni di sincronizzazione dei dati saranno completate. In alcuni casi può essere necessario interrompere le operazioni (in genere quando viene richiesta un'uscita immediata dal programma), per questo lo -standard POSIX.1b prevede una funzioni apposita, \funcd{aio\_cancel}, che +standard POSIX.1b prevede una funzione apposita, \funcd{aio\_cancel}, che permette di cancellare una operazione richiesta in precedenza; il suo prototipo è: \begin{prototype}{aio.h} @@ -1219,10 +1871,10 @@ cosiddetto \textit{memory-mapped I/O}, che, attraverso il meccanismo della \textsl{paginazione} \index{paginazione} usato dalla memoria virtuale (vedi sez.~\ref{sec:proc_mem_gen}), permette di \textsl{mappare} il contenuto di un file in una sezione dello spazio di indirizzi del processo. - + che lo ha allocato \begin{figure}[htb] \centering - \includegraphics[width=12cm]{img/mmap_layout} + \includegraphics[width=10cm]{img/mmap_layout} \caption{Disposizione della memoria di un processo quando si esegue la mappatura in memoria di un file.} \label{fig:file_mmap_layout} @@ -1355,7 +2007,7 @@ tab.~\ref{tab:file_mmap_flag}. \begin{table}[htb] \centering \footnotesize - \begin{tabular}[c]{|l|p{10cm}|} + \begin{tabular}[c]{|l|p{11cm}|} \hline \textbf{Valore} & \textbf{Significato} \\ \hline @@ -1447,8 +2099,11 @@ effettive del file o della sezione che si vuole mappare. \footnotetext[20]{Dato che tutti faranno riferimento alle stesse pagine di memoria.} -\footnotetext[21]{L'uso di questo flag con \const{MAP\_SHARED} è - stato implementato in Linux a partire dai kernel della serie 2.4.x.} + +\footnotetext[21]{L'uso di questo flag con \const{MAP\_SHARED} è stato + implementato in Linux a partire dai kernel della serie 2.4.x; esso consente + di creare segmenti di memoria condivisa e torneremo sul suo utilizzo in + sez.~\ref{sec:ipc_mmap_anonymous}.} \footnotetext{questo flag ed il precedente \const{MAP\_POPULATE} sono stati introdotti nel kernel 2.5.46 insieme alla mappatura non lineare di cui @@ -1830,25 +2485,35 @@ mappatura che gi \itindend{memory~mapping} -\subsection{L'I/O diretto fra file descriptor con \func{sendfile}} -\label{sec:file_sendfile} +%\subsection{L'I/O diretto fra file descriptor} +%\label{sec:file_sendfile_splice} -Uno dei problemi -NdA è da finire, sul perché non è abilitata fra file vedi: +%Uno dei problemi + +%NdA è da finire, sul perché non è abilitata fra file vedi: + +%\href{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html} +%{\texttt{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}} + -\href{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html} -{\texttt{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}} % TODO documentare la funzione sendfile +% TODO documentare le funzioni tee e splice +% http://kerneltrap.org/node/6505 e http://lwn.net/Articles/178199/ e +% http://lwn.net/Articles/179492/ +% e http://en.wikipedia.org/wiki/Splice_(system_call) -% i raw device -%\subsection{I \textit{raw} device} -%\label{sec:file_raw_device} -% -% TODO i raw device +%\subsection{Gestione avanzata del caching dei dati} +%\label{sec:file_fadvise} + +% TODO documentare \func{madvise} +% TODO documentare \func{mincore} +% TODO documentare \func{posix\_fadvise} +% vedi http://insights.oetiker.ch/linux/fadvise.html +% questo tread? http://www.ussg.iu.edu/hypermail/linux/kernel/0703.1/0032.html %\subsection{L'utilizzo delle porte di I/O} %\label{sec:file_io_port} @@ -2743,7 +3408,13 @@ possibilit % LocalWords: flock shared exclusive operation dup inode linked NFS cmd ENOLCK % LocalWords: EDEADLK whence SEEK CUR type pid GETLK SETLK SETLKW all'inode HP % LocalWords: switch bsd lockf mandatory SVr sgid group root mount mand TRUNC -% LocalWords: SVID UX Documentation sendfile +% LocalWords: SVID UX Documentation sendfile dnotify inotify NdA ppoll fds add +% LocalWords: init EMFILE FIONREAD ioctl watch char pathname uint mask ENOSPC +% LocalWords: dell'inode CLOSE NOWRITE MOVE MOVED FROM TO rm wd event page ctl +% LocalWords: attribute Universe epoll Solaris kqueue level triggered Jonathan +% LocalWords: Lemon BSDCON edge Libenzi kevent backporting epfd EEXIST ENOENT +% LocalWords: MOD wait EPOLLIN EPOLLOUT EPOLLRDHUP SOCK EPOLLPRI EPOLLERR one +% LocalWords: EPOLLHUP EPOLLET EPOLLONESHOT shot maxevents %%% Local Variables: