Riscritta (meglio) select
[gapil.git] / fileadv.tex
index 21dc922ac683c4fe499ff8d417a41bd0a91bb84c..7b3a6b10b551532e357c3828ad6cb72fbd1c5ebe 100644 (file)
@@ -28,37 +28,158 @@ I/O possono bloccarsi indefinitamente.\footnote{si ricordi per
 esempio le operazioni di lettura possono bloccarsi quando non ci sono dati
 disponibili sul descrittore su cui si sta operando.
 
-Uno dei problemi più comuni che ci si trova ad affrontare, che non può essere
-risolto con le funzioni di base trattate in \capref{cha:file_unix_interface},
-è quello in cui si devono eseguire operazioni che possono bloccarsi su più
-file descriptor: il problema è che mentre si è bloccati su uno di questi file
-su di un'altro potrebbero essere presenti dei dati, così che nel migliore dei
-casi si avrebbe una lettura inutilmente ritardata, e nel peggiore si potrebbe
-addirittura arrivare ad un deadlock.
-
-Abbiamo già accennato in \secref{sec:file_open} che è possibile prevenire
+Questo comportamento causa uno dei problemi più comuni che ci si trova ad
+affrontare nelle operazioni di I/O, che è quello che si verifica quando si
+devono eseguire operazioni che possono bloccarsi su più file descriptor:
+mentre si è bloccati su uno di questi file su di un'altro potrebbero essere
+presenti dei dati, così che nel migliore dei casi si avrebbe una lettura
+ritardata inutilmente, e nel peggiore si potrebbe addirittura arrivare ad un
+deadlock.
+
+Abbiamo già accennato in \secref{sec:file_open} che però è possibile prevenire
 questo tipo di comportamento aprendo un file in modalità
-\textsl{non-bloccante}, specificando il flag \macro{O\_NONBLOCK} alla chiamata
-di \func{open}. In questo caso le funzioni di input/output che altrimenti si
-sarebbero bloccate ritornano immediatamente, restituendo l'errore
-\macro{EAGAIN}.
+\textsl{non-bloccante}, attraverso l'uso del flag \macro{O\_NONBLOCK} nella
+chiamata di \func{open}. In questo caso le funzioni di input/output che
+altrimenti si sarebbero bloccate ritornano immediatamente, restituendo
+l'errore \macro{EAGAIN}.
 
-L'utilizzo di questa modalità di I/O permette allora di risolvere il problema
+L'utilizzo di questa modalità di I/O permette di risolvere il problema
 controllando a turno i vari file descriptor, in un ciclo in cui si ripete
 l'accesso fintanto che esso non viene garantito.  Ovviamente questa tecnica,
 detta \textit{polling}, è estremamente inefficiente: si tiene costantemente
 impiegata la CPU solo per eseguire in continuazione delle system call che
-nella gran parte dei casi falliranno.
+nella gran parte dei casi falliranno. Per evitare questo, come vedremo in
+\secref{sec:file_multiplexing}, è stata introdotta una nuova interfaccia di
+programmazione, che comporta comunque l'uso della modalità di I/O non
+bloccante.
+
 
-Per questo motivo, quando come vedremo in dettaglio in
-\secref{sec:file_multiplexing}, il sistema fornisce delle funzioni apposite
-che permettono di aggirare questo problema, permettendo di attendere fino alla
-disponibilità di un accesso; per poterle usare però è comunque comunque
-necessario utilizzare la modalità di I/O non bloccante all'apertura del file.
 
 \subsection{Le funzioni \func{poll} e \func{select}}
 \label{sec:file_multiplexing}
 
+Per superare il problema di dover usare il \textit{polling} per controllare la
+possibilità di effettuare operazioni su un file aperto in modalità non
+bloccante, sia BSD che System V hanno introdotto delle nuove funzioni in grado
+di sospendere l'esecuzione di un processo in attesa che l'accesso diventi
+possibile.  Il primo ad introdurre questa modalità di operazione, chiamata
+usualmente \textit{I/O multiplexing}, è stato BSD,\footnote{la funzione è
+  apparsa in BSD4.2 e standardizzata in BSD4.4, ma è stata portata su tutti i
+  sistemi che supportano i \textit{socket}, compreso le varianti di System V.}
+con la funzione \func{select}, il cui prototipo è:
+\begin{prototype}{sys/select.h}
+  {int select(int n, fd\_set *readfds, fd\_set *writefds, fd\_set *exceptfds,
+    struct timeval *timeout)}
+  
+  Attende che uno dei file descriptor degli insiemi specificati diventi
+  attivo.
+  
+  \bodydesc{La funzione in caso di successo restituisce il numero di file
+    descriptor (anche nullo) che sono attivi, e -1 in caso di errore, nel qual
+    caso \var{errno} viene settata ai valori:
+  \begin{errlist}
+  \item[\macro{EBADF}] Si è specificato un file descriptor sbagliato in uno
+  degli insiemi.
+  \item[\macro{EINTR}] La funzione è stata interrotta da un segnale.
+  \item[\macro{EINVAL}] Si è specificato per \param{n} un valore negativo.
+  \end{errlist}
+  ed inoltre \macro{ENOMEM}.
+}
+\end{prototype}
+
+La funzione mette il processo in stato di \textit{sleep} (vedi
+\tabref{tab:proc_proc_states}) fintanto che almeno uno dei file descriptor
+degli insiemo specificati (\param{readfds}, \param{writefds} e
+\param{exceptfds}), non diventa attivo, per un tempo massimo specificato da
+\param{timeout}.
+
+Per specificare quali file descriptor si intende \textsl{selezionare}, la
+funzione usa un particolare oggetto, il \textit{file descriptor set},
+identificato dal tipo \type{fd\_set}, che serve ad identificare un insieme di
+file descriptor, (in maniera analoga a come un \textit{signal set}, vedi
+\secref{sec:sig_sigset}, identifica un insieme di segnali). Per la
+manipolazione di questi \textit{file descriptor set} si possono usare delle
+opportune macro di preprocessore:
+\begin{functions}
+  \headdecl{sys/select.h}
+  \funcdecl{FD\_ZERO(fd\_set *set)}
+  Inizializza l'insieme (vuoto).
+
+  \funcdecl{FD\_SET(int fd, fd\_set *set)}
+  Inserisce il file descriptor \param{fd} nell'insieme.
+
+  \funcdecl{FD\_CLR(int fd, fd\_set *set)}
+  Rimuove il file descriptor \param{fd} nell'insieme.
+  
+  \funcdecl{FD\_ISSET(int fd, fd\_set *set)}
+  Controlla se il file descriptor \param{fd} è nell'insieme.
+\end{functions}
+
+In genere un \textit{file descriptor set} può contenere fino ad un massimo di
+\macro{FD\_SETSIZE} file descriptor.  Questo valore in origine corrispondeva
+al limite per il numero massimo di file aperti\footnote{ad esempio in Linux,
+  fino alla serie 2.0.x, c'era un limite di 256 file per processo.}, ma
+quando, come nelle versioni più recenti del kernel, non c'è più un limite
+massimo, esso indica le dimensioni massime dei numeri usati nei \textit{file
+  descriptor set}.
+
+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, il secondo, \param{writefds}, per verificare la
+possibilità effettuare una scrittura ed il terzo, \param{exceptfds}, per
+verificare l'esistenza di condizioni eccezionali (come i messaggi urgenti su
+un \textit{socket}\index{socket}, vedi \secref{sec:xxx_urgent}).
+
+La funzione inoltre richiede anche di specificare, tramite l'argomento
+\param{n}, un valore massimo del numero dei file descriptor usati
+nell'insieme; si può usare il già citato \macro{FD\_SETSIZE}, oppure il numero
+più alto dei file descriptor usati nei tre insiemi, aumentato di uno.
+
+Infine l'argomento \param{timeout}, specifica un tempo massimo di
+attesa\footnote{il tempo è valutato come \textit{elapsed time}.} prima che la
+funzione ritorni; se settato a \macro{NULL} la funzione attende
+indefinitamente. Si può specificare anche un tempo nullo (cioè una \var{struct
+  timeval} con i campi settati a zero), qualora si voglia semplicemente
+controllare lo stato corrente dei file descriptor.
+
+La funzione restituisce il totale dei file descriptor pronti nei tre insiemi,
+il valore zero indica sempre che si è raggiunto un timeout. Ciascuno dei tre
+insiemi viene sovrascritto per indicare quale file descriptor è pronto per le
+operazioni ad esso relative, in modo da poterlo controllare con la macro
+\macro{FD\_ISSET}. In caso di errore la funzione restituisce -1 e gli insiemi
+non vengono toccati.
+
+In Linux \func{select} modifica anche il valore di \param{timeout}, settandolo
+al tempo restante; questo è utile quando la funzione viene interrotta da un
+segnale, in tal caso infatti si ha un errore di \macro{EINTR}, ed occorre
+rilanciare la funzione; in questo modo non è necessario ricalcolare tutte le
+volte il tempo rimanente.\footnote{questo però può causare problemi di
+  portabilità sia quando si trasporta codice scritto su Linux che legge questo
+  valore, sia quando si usano programmi scritti per altri sistemi che non
+  dispongono di questa caratteristica e ricalcolano \param{timeout} tutte le
+  volte. In genere la caratteristica è disponibile nei sistemi che derivano da
+  System V e non disponibile per quelli che derivano da BSD.}
+
+Come accennato l'interfaccia di \func{select} è una estensione di BSD; anche
+System V ha introdotto una sua interfaccia per getire l'\textit{I/O
+  multiplexing}, basata sulla funzione \func{poll}, il cui prototipo è:
+\begin{prototype}{sys/poll.h}
+  {int poll(struct pollfd *ufds, unsigned int nfds, int timeout)}
+
+La funzione attente un cambiamento di stato per uno dei file descriptor
+specificati da \param{ufds}.
+  
+\bodydesc{La funzione restituisce il numero di file descriptor con attività in
+  caso di successo, o 0 se c'è stato un timeout; in caso di errore viene
+  restituito  -1 ed \var{errno} viene settata ai valori:
+  \begin{errlist}
+  \item[\macro{EBADF}] Si è specificato un file descriptor sbagliato in uno
+  degli insiemi.
+  \item[\macro{EINTR}] La funzione è stata interrotta da un segnale.
+  \end{errlist}
+  ed inoltre \macro{EFAULT} e \macro{ENOMEM}.}
+\end{prototype}
+
 
 
 \subsection{L'I/O asincrono}
@@ -97,17 +218,18 @@ riporta il file descriptor che ha generato il segnale.
 \section{Il file locking}
 \label{sec:file_locking}
 
-In \secref{sec:file_sharing} abbiamo preso in esame le mosalità in cui un
+In \secref{sec:file_sharing} abbiamo preso in esame le modalità in cui un
 sistema unix-like gestisce la condivisione dei file da parte di processi
 diversi. In quell'occasione si è visto come, con l'eccezione dei file aperti
 in \textit{append mode}, quando più processi scrivono contemporaneamente sullo
 stesso file non è possibile determinare la sequenza in cui essi opereranno.
 
-Questo causa la possibilità di race condition; in generale le situazioni più
-comuni sono due: l'interazione fra un processo che scrive e altri che leggono,
-in cui questi ultimi possono leggere informazioni scritte solo in maniera
-parziale o incompleta; o quella in cui diversi processi scrivono, mescolando
-in maniera imprevedebile il loro output sul file.
+Questo causa la possibilità di race condition\index{race condition}; in
+generale le situazioni più comuni sono due: l'interazione fra un processo che
+scrive e altri che leggono, in cui questi ultimi possono leggere informazioni
+scritte solo in maniera parziale o incompleta; o quella in cui diversi
+processi scrivono, mescolando in maniera imprevedibile il loro output sul
+file.
 
 In tutti questi casi il \textit{file locking} è la tecnica che permette di
 evitare le race condition, attraverso una serie di funzioni che permettono di