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}}
\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
\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}.
}
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
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
\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
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
\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}
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 dimesioni massime di un \itindex{file~descriptor~set}
+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
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 è stata creata una nuova
-interfaccia,\footnote{l'interfaccia è stata creata da Davide Libernzi, ed è
- stata introdotta per la prima volta nel kernel 2.5.44, ma la sua forma
- definitiva è stata raggiunta nel kernel 2.5.66.} detta \textit{epoll}, il
-cui concetto fondamentale è quello di restituire solamente le informazioni
-relative ai file descriptor osservati che presentano una attività, evitando
-così tutti le problematiche appena illustrate.
+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, 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 l'argomento \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|l|}
+ \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}.\\
+ \const{EPOLL\_CTL\_MOD}& .\\
+ \const{EPOLL\_CTL\_DEL}& .\\
+ \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}
+\begin{figure}[!htb]
+ \footnotesize \centering
+ \begin{minipage}[c]{15cm}
+ \includestruct{listati/epoll_event.h}
+ \end{minipage}
+ \normalsize
+ \caption{La struttura \structd{epoll\_event}, .}
+ \label{fig:}
+\end{figure}
-\itindend{epoll}
+
+
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|l|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{EPOLLIN}& .\\
+ \const{EPOLLOUT}& .\\
+ \const{EPOLLRDHUP}& .\\
+ \const{EPOLLPRI}& .\\
+ \const{EPOLLERR}& .\\
+ \const{EPOLLHUP}& .\\
+ \const{EPOLLET}& .\\
+ \const{EPOLLONESHOT}& .\\
+ \hline
+ \end{tabular}
+ \caption{Valori del campo \param{events} di \struct{epoll\_event}.}
+ \label{tab:epoll_events}
+\end{table}
+
+
+, da
+specificare con le costanti riportate in tab.,
+La funzione prende come primo argomento un file descriptor di \textit{epoll}
+\param{epfd}, che deve essere stato ottenuto tramite \func{epoll\_create}, e
+consente di impostare le operazioni di osservazione su un altro file
+descriptor \param{fd}
+
+
+
+
+La funzione esegue l'operazione di controllo specificata dall'argomento
+\param{op},
+
+
+\param{fd} alla lista dei file descriptor osservati
+
+sull'\textit{epoll descripter} \param{epfd}
+
+
+La funzione che consente di attendere è \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{fd} è lo stesso \param{epfd} o
+ l'operazione richiesta con \param{op} non è supportata.
+ \end{errlist}
+}
+\end{prototype}
+
+
+\itindend{epoll}
% TODO epoll
-%
+%
\section{L'accesso \textsl{asincrono} ai file}
\label{sec:file_asyncronous_access}
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 rimuovibile, mantenere un file
+quando la directory è su un dispositivo rimovibile, mantenere un file
descriptor aperto comporta l'impossibilità di smontare il dispositivo e
rimuoverlo, complicando la gestione.
% 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{L'utilizzo delle porte di I/O}
%\label{sec:file_io_port}
%
% LocalWords: switch bsd lockf mandatory SVr sgid group root mount mand TRUNC
% 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
-% LocalWords: attribute Universe
+% 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
%%% Local Variables: