From: Simone Piccardi Date: Fri, 26 Dec 2003 19:20:04 +0000 (+0000) Subject: Riorganizzato il capitolo sui file avanzati, con degli esempi in piu X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=commitdiff_plain;h=0c3c06a023684951f7f1e189d270cf322c0dfe31 Riorganizzato il capitolo sui file avanzati, con degli esempi in piu sull'uso di pselect. Inserita (e commentata) anche la seconda versione della funzione ServEcho utilizzata dal server echo normale. --- diff --git a/fileadv.tex b/fileadv.tex index fb516a3..19f7dab 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -12,22 +12,28 @@ \label{cha:file_advanced} In questo capitolo affronteremo le tematiche relative alla gestione avanzata -dei file, che non sono state trattate in \capref{cha:file_unix_interface}, -dove ci si è limitati ad una panoramica delle funzioni base. In particolare -tratteremo delle funzioni di input/output avanzato e del \textit{file - locking}. +dei file. In particolare tratteremo delle funzioni di input/output avanzato, +che permettono una gestione più sofisticata dell'I/O su file, a partire da +quelle che permettono di gestire l'accesso contemporaneo a più file, per +concludere con la gestione dell'I/O mappato in memoria. Dedicheremo poi la +fine del capitolo alle problematiche del \textit{file locking}. -\section{Le funzioni di I/O avanzato} -\label{sec:file_advanced_io} +\section{L'\textit{I/O multiplexing}} +\label{sec:file_multiplexing} -In questa sezione esamineremo le funzioni che permettono una gestione più -sofisticata dell'I/O su file, a partire da quelle che permettono di gestire -l'accesso contemporaneo a più file, per concludere con la gestione dell'I/O -mappato in memoria. +Uno dei problemi che si presentano quando si deve operare contemporaneamente +su molti file usando le funzioni illustrate in +\capref{cha:file_unix_interface} e \capref{cha:files_std_interface} è che si +può essere bloccati nelle operazioni su un file mentre un altro potrebbe +essere disponibile. L'\textit{I/O multiplexing} nasce risposta a questo +problema. In questa sezione forniremo una introduzione a questa problematica +ed analizzeremo le varie funzioni usate per implementare questa modalità di +I/O. -\subsection{La modalità di I/O \textsl{non-bloccante}} +\subsection{La problematica dell'\textit{I/O multiplexing} e l'uso + dell'\textsl{I/O non-bloccante}} \label{sec:file_noblocking} Abbiamo visto in \secref{sec:sig_gen_beha}, affrontando la suddivisione fra @@ -40,45 +46,54 @@ lettura possono bloccarsi quando non ci sono dati disponibili sul descrittore su cui si sta operando. 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 essi 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 -\textit{deadlock}\index{deadlock}. +affrontare nelle operazioni di I/O, che si verifica quando si deve operare con +più file descriptor eseguendo funzioni che possono bloccarsi senza che sia +possibile prevedere quando questo può avvenire (il caso più classico è quello +di un server in attesa di dati in ingresso da vari client). Quello che può +accadere è di restare bloccati nell'eseguire una operazione su un file +descriptor che non è ``\textsl{pronto}'', quando ce ne potrebbe essere +un'altro disponibile. Questo comporta nel migliore dei casi una operazione +ritardata inutilmente nell'attesa del completamento di quella bloccata, mentre +nel peggiore dei casi (quando la conclusione della operazione bloccata dipende +da quanto si otterrebbe dal file descriptor ``\textsl{disponibile}'') si +potrebbe addirittura arrivare ad un \textit{deadlock}\index{deadlock}. Abbiamo già accennato in \secref{sec:file_open} che è possibile prevenire -questo tipo di comportamento aprendo un file in modalità -\textsl{non-bloccante}, attraverso l'uso del flag \const{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 \errcode{EAGAIN}. +questo tipo di comportamento delle funzioni di I/O aprendo un file in quella +che viene chiamata \textsl{modalità non-bloccante}, attraverso l'uso del flag +\const{O\_NONBLOCK} nella chiamata di \func{open}. In questo caso le funzioni +di input/output eseguite sul file che si sarebbero bloccate, ritornano +immediatamente, restituendo l'errore \errcode{EAGAIN}. 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}\index{polling}, è estremamente inefficiente: si tiene costantemente impiegata la CPU solo per eseguire in continuazione delle system -call che 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. - - - -\subsection{L'I/O multiplexing} -\label{sec:file_multiplexing} - -Per superare il problema di dover usare il \textit{polling}\index{polling} per -controllare la possibilità di effettuare operazioni su un gruppo di file -aperti in modalità non bloccante, sia BSD che System V hanno introdotto delle -nuove funzioni in grado di sospendere l'esecuzione di un processo fin quando -l'accesso ad un dato insieme di file 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}\index{socket}, compreso le varianti di System - V.} con la funzione \funcd{select}, il cui prototipo è: +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 +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 +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 \secref{sec:TCP_sock_multiplexing}. + + +\subsection{Le funzioni \func{select} e \func{pselect}} +\label{sec:file_select} + +Il primo ad introdurre una interfaccia per l'\textit{I/O multiplexing} è stato +BSD,\footnote{la funzione \func{select} è apparsa in BSD4.2 e standardizzata + in BSD4.4, ma è stata portata su tutti i sistemi che supportano i + \textit{socket}\index{socket}, compreso le varianti di System V.} con la +funzione \funcd{select}, il cui prototipo è: \begin{functions} \headdecl{sys/time.h} \headdecl{sys/types.h} @@ -94,9 +109,10 @@ introdurre questa modalit caso \var{errno} assumerà uno dei valori: \begin{errlist} \item[\errcode{EBADF}] Si è specificato un file descriptor sbagliato in uno - degli insiemi. + degli insiemi. \item[\errcode{EINTR}] La funzione è stata interrotta da un segnale. - \item[\errcode{EINVAL}] Si è specificato per \param{n} un valore negativo. + \item[\errcode{EINVAL}] Si è specificato per \param{n} un valore negativo o + un valore non valido per \param{timeout}. \end{errlist} ed inoltre \errval{ENOMEM}. } @@ -111,8 +127,8 @@ degli insiemi specificati (\param{readfds}, \param{writefds} e 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 +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} @@ -146,10 +162,13 @@ dar luogo a comportamenti non prevedibili. 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:TCP_urgent_data}). +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, +\param{writefds}, per verificare la possibilità effettuare una scrittura ed il +terzo, \param{exceptfds}, per verificare l'esistenza di eccezioni (come i +messaggi urgenti su un \textit{socket}\index{socket}, vedi +\secref{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 @@ -204,35 +223,121 @@ numero massimo di 1024 file descriptor per processo, adesso che il numero pu essere arbitario si viene a creare una dipendenza del tutto artificiale dalle dimensioni della struttura \type{fd\_set}, che può necessitare di essere estesa, con ulteriori perdite di prestazioni. -Per questo System V, invece di utilizzare l'interfaccia di \func{select}, che -è una estensione creata nello sviluppo di BSD, ha introdotto una sua -interfaccia per gestire l'\textit{I/O multiplexing}, basata sulla funzione + +Lo standard POSIX è rimasto a lungo senza primitive per l'\textit{I/O + multiplexing}, introdotto solo con le ultime revisioni dello standard (POSIX +1003.1g-2000 e POSIX 1003.1-2001). La scelta è stata quella di seguire +l'interfaccia creata da BSD, ma prevede che tutte le funzioni ad esso relative +vengano dichiarate nell'header \file{sys/select.h}, che sostituisce i +precedenti, ed inoltre aggiunge a \func{select} una nuova funzione +\funcd{pselect},\footnote{il supporto per lo standard POSIX 1003.1-2001, ed + l'header \file{sys/select.h}, compaiono in Linux a partire dalle \acr{glibc} + 2.1. Le \acr{libc4} e \acr{libc5} non contengono questo header, le + \acr{glibc} 2.0 contengono una definizione sbagliata di \func{psignal}, + senza l'argomento \param{sigmask}, la definizione corretta è presente dalle + \acr{glibc} 2.1-2.2.1 se si è definito \macro{\_GNU\_SOURCE} e nelle + \acr{glibc} 2.2.2-2.2.4 se si è definito \macro{\_XOPEN\_SOURCE} con valore + maggiore di 600.} il cui prototipo è: +\begin{prototype}{sys/select.h} + {int pselect(int n, fd\_set *readfds, fd\_set *writefds, fd\_set *exceptfds, + struct timespec *timeout, sigset\_t *sigmask)} + + 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} 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}] Si è specificato per \param{n} un valore negativo o + un valore non valido per \param{timeout}. + \end{errlist} + ed inoltre \errval{ENOMEM}.} +\end{prototype} + +La funzione è sostanzialmente identica a \func{select}, solo che usa una +struttura \struct{timespec} (vedi \figref{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 +\secref{sec:sig_sigmask}). La maschera corrente viene sostituita da questa +immediatamente prima di eseguire l'attesa, e ripristinata al ritorno della +funzione. + +L'uso di \param{sigmask} è stato introdotto allo scopo di prevenire possibili +race condition\index{race condition} quando ci si deve porre in attesa sia di +un segnale che di dati.\footnote{in Linux però non è stata ancora introdotta + la relativa system call, pertanto la funzione è implementata nelle + \acr{glibc} attraverso \func{select} e la possibilità di race condition + permane.} La tecnica classica è quella di utilizzare il gestore per +impostare una variabile globale e controllare questa nel corpo principale del +programma; abbiamo visto in \secref{sec:sig_example} come questo lasci spazio +a possibili race condition, per cui diventa essenziale utilizzare +\func{sigprocmask} per disabilitare la ricezione del segnale prima di eseguire +il controllo e riabilitarlo dopo l'esecuzione delle relative operazioni, onde +evitare l'arrivo di un segnale immediatamente dopo il controllo, che andrebbe +perso. + +Nel nostro caso il problema si pone quando oltre al segnale si devono tenere +sotto controllo anche dei file descriptor con \func{select}, in questo caso si +può fare conto sul fatto che all'arrivo di un segnale essa verrebbe interrotta +e si potrebbero eseguire di conseguenza le operazioni relative al segnale e +alla gestione dati con un ciclo del tipo: +\includecodesnip{listati/select_race.c} +qui però emerge una race condition, perché se il segnale arriva prima della +chiamata a \func{select}, questa non verrà interrotta, e la ricezione del +segnale non sarà rilevata. + +Per questo è stata introdotta \func{pselect}, che attraverso l'argomento +\param{sigmask} permette di riabilitare la ricezione il segnale +contestualmente all'esecuzione della funzione, e ribloccandolo non appena essa +ritorna. In questo modo il precedente codice potrebbe essere essere modificato +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. + + + +\subsection{La funzione \func{poll}} +\label{sec:file_poll} + +System V, invece di utilizzare l'interfaccia di \func{select}, che è una +estensione creata nello sviluppo di BSD, ha introdotto una sua interfaccia per +gestire l'\textit{I/O multiplexing}, basata sulla funzione \funcd{poll},\footnote{la funzione è prevista dallo standard XPG4, ed è stata - introdotta in Linux come system call a partire dal kernel 2.1.23 e dalle - \acr{libc} 5.4.28.} il cui prototipo è: + introdotta in Linux come system call a partire dal kernel 2.1.23 ed inserita + nelle \acr{libc} 5.4.28.} il cui prototipo è: \begin{prototype}{sys/poll.h} {int poll(struct pollfd *ufds, unsigned int nfds, int timeout)} La funzione attende 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} assumerà uno dei valori: + \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} 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} -La funzione tiene sotto controllo un numero \param{ndfs} di file descriptor -specificati attraverso un vettore di puntatori a strutture \struct{pollfd}, la -cui definizione è riportata in \figref{fig:file_pollfd}. Come \func{select} -anche \func{poll} permette di interrompere l'attesa dopo un certo tempo, che -va specificato attraverso \param{timeout} in numero di millisecondi (un valore -negativo indica un'attesa indefinita). +La funzione permette di tenere sotto controllo un certo numero \param{ndfs} di +file descriptor, specificati attraverso un vettore di puntatori a strutture +\struct{pollfd}. Come \func{select} anche \func{poll} permette di +interrompere l'attesa dopo un certo tempo, che va specificato attraverso +l'argomento \param{timeout} in numero di millisecondi: un valore negativo +indica un'attesa indefinita mentre si può usare un valore nullo per eseguire +la funzione in modalità \textsl{non-bloccante}. \begin{figure}[!htb] \footnotesize \centering @@ -246,11 +351,21 @@ negativo indica un'attesa indefinita). \end{figure} Per ciascun file da controllare deve essere opportunamente predisposta una -struttura \struct{pollfd}; nel campo \var{fd} deve essere specificato il file -descriptor, mentre nel campo \var{events} il tipo di evento su cui si vuole -attendere; quest'ultimo deve essere specificato come maschera binaria dei -primi tre valori riportati in \tabref{tab:file_pollfd_flags} (gli altri -vengono utilizzati solo per \var{revents} come valori in uscita). +struttura \struct{pollfd}, la cui definizione è riportata in +\figref{fig:file_pollfd}. La struttura prevede tre campi: il campo \var{fd} +viene utilizzato per specificare il file descriptor relativo al file da +controllare, mentre nel campo \var{events} deve essere specificata una +maschera binaria data in ingresso che indichi il tipo di evento che si vuole +controllare, il kernel restituirà il relativo risultato nel campo +\var{revents}. + +Le costanti che definiscono i valori relativi ai bit usati nelle maschere +binarie dei campi \var{events} e \var{revents} sono riportati in +\tabref{tab:file_pollfd_flags}, insieme al loro significato. Le si sono +suddivise in tre gruppi, nel primo gruppo si sono indicati i bit utilizzati +per controllare l'attività in ingresso, nel secondo quelli per l'attività in +uscita, mentre il terzo gruppo contiene dei valori che vengono utilizzati solo +nel campo \var{revents} per notificare delle condizioni di errore. \begin{table}[htb] \centering @@ -260,22 +375,20 @@ vengono utilizzati solo per \var{revents} come valori in uscita). \textbf{Flag} & \textbf{Significato} \\ \hline \hline - \const{POLLIN} & È possibile la lettura immediata.\\ - \const{POLLPRI} & Sono presenti dati urgenti.\\ + \const{POLLIN} & È possibile la lettura.\\ + \const{POLLRDNORM}& Sono disponibili in lettura dati normali.\\ + \const{POLLRDBAND}& Sono disponibili in lettura dati prioritari. \\ + \const{POLLPRI} & È possibile la lettura di dati urgenti.\\ + \hline \const{POLLOUT} & È possibile la scrittura immediata.\\ + \const{POLLWRNORM}& È possibile la scrittura di dati normali. \\ + \const{POLLWRBAND}& È possibile la scrittura di dati prioritari. \\ \hline \const{POLLERR} & C'è una condizione di errore.\\ \const{POLLHUP} & Si è verificato un hung-up.\\ \const{POLLNVAL} & Il file descriptor non è aperto.\\ \hline - \const{POLLRDNORM}& Sono disponibili in lettura dati normali.\\ - \const{POLLRDBAND}& Sono disponibili in lettura dati ad alta - priorità. \\ - \const{POLLWRNORM}& È possibile la scrittura di dati normali. \\ - \const{POLLWRBAND}& È possibile la scrittura di dati ad - alta priorità. \\ - \const{POLLMSG} & Un segnale \const{SIGPOLL} è arrivato alla - cima dello stream (non usato).\\ + \const{POLLMSG} & Definito per compatobilità con SysV.\\ \hline \end{tabular} \caption{Costanti per l'identificazione dei vari bit dei campi @@ -283,74 +396,44 @@ vengono utilizzati solo per \var{revents} come valori in uscita). \label{tab:file_pollfd_flags} \end{table} -La funzione ritorna, restituendo il numero di file per i quali si è verificata -una delle condizioni di attesa richieste od un errore. Lo stato dei file -all'uscita della funzione viene restituito nel campo \var{revents} della -relativa struttura \struct{pollfd}, che viene impostato alla maschera binaria -dei valori riportati in \tabref{tab:file_pollfd_flags}, ed oltre alle tre -condizioni specificate tramite \var{events} può riportare anche l'occorrere di -una condizione di errore. +Infine il valore \const{POLLMSG} non viene utilizzato ed è definito solo per +compatibilità con l'implementazione di SysV, dove indica segnale +\const{SIGPOLL} è arrivato alla cima dello \textit{stream}. Gli +\textit{stream} sono una interfaccia specifica di SysV non presente in Linux, +e non hanno nulla a che fare con i file \textit{stream} delle librerie +standard del C, è da questi che derivano i nomi delle costanti, in quanto per +essi sono definite tre classi di dati: \textsl{normali}, \textit{prioritari} +ed \textit{urgenti}. Nel caso di Linux la distinzione ha senso solo nel caso +per i dati \textit{out-of-band} dei socket (vedi +\secref{sec:TCP_urgent_data}), ma su questo e su come \func{poll} reagisce +alle varie condizioni dei socket torneremo in \secref{sec:TCP_serv_poll}, dove +vedremo anche un esempio del suo utilizzo. -Lo standard POSIX è rimasto a lungo senza primitive per l'\textit{I/O - multiplexing}, introdotto solo con le ultime revisioni dello standard (POSIX -1003.1g-2000 e POSIX 1003.1-2001). Esso prevede che tutte le funzioni ad esso -relative vengano dichiarate nell'header \file{sys/select.h}, che sostituisce i -precedenti, ed aggiunge a \func{select} una nuova funzione -\funcd{pselect},\footnote{il supporto per lo standard POSIX 1003.1-2001, ed - l'header \file{sys/select.h}, compaiono in Linux a partire dalle \acr{glibc} - 2.1. Le \acr{libc4} e \acr{libc5} non contengono questo header, le - \acr{glibc} 2.0 contengono una definizione sbagliata di \func{psignal}, - senza l'argomento \param{sigmask}, la definizione corretta è presente dalle - \acr{glibc} 2.1-2.2.1 se si è definito \macro{\_GNU\_SOURCE} e nelle - \acr{glibc} 2.2.2-2.2.4 se si è definito \macro{\_XOPEN\_SOURCE} con valore - maggiore di 600.} il cui prototipo è: -\begin{prototype}{sys/select.h} - {int pselect(int n, fd\_set *readfds, fd\_set *writefds, fd\_set *exceptfds, - struct timespec *timeout, sigset\_t *sigmask)} - - 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} 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}] Si è specificato per \param{n} un valore negativo. - \end{errlist} - ed inoltre \errval{ENOMEM}.} -\end{prototype} +In caso di successo funzione ritorna restituendo il numero di file (un valore +positivo) per i quali si è verificata una delle condizioni di attesa richieste +o per i quali si è verificato un errore (nel qual caso vengono utilizzati i +valori di \tabref{tab:file_pollfd_flags} esclusivi di \var{revents}). Un +valore nullo indica che si è raggiunto il timeout, mentre un valore negativo +indica un errore nella chiamata, il cui codice viene riportato al solito +tramite \var{errno}. -La funzione è sostanzialmente identica a \func{select}, solo che usa una -struttura \struct{timespec} 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 \secref{sec:sig_sigmask}). La maschera corrente viene sostituita da -questa immediatamente prima di eseguire l'attesa, e ripristinata al ritorno -della funzione. -L'uso di \param{sigmask} è stato introdotto allo scopo di prevenire possibili -race condition\footnote{in Linux però, non esistendo una system call apposita, - la funzione è implementata nelle \acr{glibc} usando \func{select}, e la - possibilità di una race condition\index{race condition} resta.} quando si -deve eseguire un test su una variabile assegnata da un gestore sulla base -dell'occorrenza di un segnale per decidere se lanciare \func{select}. Fra il -test e l'esecuzione è presente una finestra in cui potrebbe arrivare il -segnale che non sarebbe rilevato; la race condition\index{race condition} -diventa superabile disabilitando il segnale prima del test e riabilitandolo -poi grazie all'uso di \param{sigmask}. - -Dato che l'I/O multiplexing serve a risolvere il problema di dover attendere -la disponibilità di accesso ad un insieme di file, esso viene utilizzato -prevalentemente per programmi in cui l'accesso ad un file descriptor può -essere bloccante. Abbiamo già accennato come questo non avvenga mai per i -normali file su disco; l'uso più comune di queste funzioni infatti è nei -server di rete, in cui esse vengono utilizzate per tenere sotto controllo vari -socket; pertanto ritorneremo su di esse con maggiori dettagli e con qualche -esempio in \secref{sec:TCP_sock_multiplexing}. +%\subsection{L'interfaccia di \textit{epoll}} +%\label{sec:file_epoll} +% placeholder ... + + + + +\section{Altre modalità e funzioni di I/O avanzato} +\label{sec:file_advanced_io} +Benché l'\textit{I/O multiplexing} sia stata la prima, e sia tutt'ora una fra +le più diffuse modalità di gestire l'I/O in situazioni complesse che +coivolgono molti file, esistono altre modalità di gestione delle stesse +problematiche, oltre che differenti interfacce per la gestione di altre +problematiche avanzate riguardanti l'I/O su file, tratteremo tutto ciò in +questa sezione. \subsection{L'I/O asincrono} diff --git a/listati/ServEcho_second.c b/listati/ServEcho_second.c new file mode 100644 index 0000000..c20c75e --- /dev/null +++ b/listati/ServEcho_second.c @@ -0,0 +1,27 @@ +void ServEcho(int sockfd) { + char buffer[MAXLINE]; + int nread, nwrite; + char debug[MAXLINE+20]; + /* main loop, reading 0 char means client close connection */ + while ( (nread = read(sockfd, buffer, MAXLINE)) != 0) { + if (nread < 0) { + PrintErr("Errore in lettura"); + return; + } + nwrite = FullWrite(sockfd, buffer, nread); + if (nwrite) { + PrintErr("Errore in scrittura"); + return; + } + if (debugging) { + buffer[nread] = 0; + snprintf(debug, MAXLINE+20, "Letti %d byte, %s", nread, buffer); + if (demonize) { /* daemon mode */ + syslog(LOG_DEBUG, debug); + } else { + printf("%s", debug); + } + } + } + return; +} diff --git a/tcpsock.tex b/tcpsock.tex index 35c5ef2..5a95a98 100644 --- a/tcpsock.tex +++ b/tcpsock.tex @@ -1649,7 +1649,7 @@ compito del client leggere la risposta del server e stamparla sullo standard output. -\subsection{Il client: prima versione} +\subsection{Il client \textit{echo}: prima versione} \label{sec:TCP_echo_client} Il codice della prima versione del client per il servizio \textit{echo}, @@ -1731,7 +1731,7 @@ programma, usato per illustriamo immediatamente. -\subsection{Il server: prima versione} +\subsection{Il server \textit{echo}: prima versione} \label{sec:TCPsimp_server_main} La prima versione del server, contenuta nel file \file{TCP\_echod\_first.c}, è @@ -2179,7 +2179,33 @@ ricevuto da \var{accept} viene convertito in una stringa che poi (\texttt{\small 34--39}) viene opportunamente stampata o sullo schermo o nei log. +Infine come ulteriore miglioria si è perfezionata la funzione \code{ServEcho}, +sia per tenere conto della nuova funzionalità di debugging, che per effettuare +un controllo in caso di errore; il codice della nuova versione è mostrato in +\figref{fig:TCP_ServEcho_second}. +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{15.6cm} + \includecodesample{listati/ServEcho_second.c} + \end{minipage} + \normalsize + \caption{Codice della seconda versione della funzione \code{ServEcho} per la + gestione del servizio \textit{echo}.} + \label{fig:TCP_ServEcho_second} +\end{figure} + +Rispetto alla precedente versione di \figref{fig:TCP_ServEcho_first} in questo +caso si è provveduto a controllare (\texttt{\small 7--10}) il valore di +ritorno di \func{read} per rilevare un eventuale errore, in modo da stampare +(\texttt{\small 8}) un messaggio di errore e ritornare (\texttt{\small 9}) +concludendo la connessione. + +Inoltre qualora sia stata attivata la funzionalità di debug (avvalorando +\var{debugging} tramite l'apposita opzione \texttt{-d}) si provvederà a +stampare (tenendo conto della modalità di invocazione del server, se +interattiva o in forma di demone) il numero di byte e la stringa letta dal +client (\texttt{\small 16--24}). \section{I vari scenari critici} diff --git a/tcpsockadv.tex b/tcpsockadv.tex index 7351738..28b2318 100644 --- a/tcpsockadv.tex +++ b/tcpsockadv.tex @@ -1,4 +1,4 @@ - %% tcpsockadv.tex +%% tcpsockadv.tex %% %% Copyright (C) 2003 Simone Piccardi. Permission is granted to %% copy, distribute and/or modify this document under the terms of the GNU Free @@ -658,10 +658,38 @@ presenta un file descriptor aperto, e lo imposta (\texttt{\small 44}) come nuovo massimo, per poi tornare (\texttt{\small 44}) al ciclo principale con un \code{break}, e rieseguire \func{select}. -Se infine si sono letti dei dati (ultimo caso rimasto) si potrà invocare -(\texttt{\small 49}) \func{FullWrite} per riscriverli indietro sul socket in -questione, avendo cura di uscire con un messaggio in caso di errore -(\texttt{\small 50--53}). +Se infine si sono effettivamente letti dei dati dal socket (ultimo caso +rimasto) si potrà invocare immediatamente (\texttt{\small 49}) +\func{FullWrite} per riscriverli indietro sul socket stesso, avendo cura di +uscire con un messaggio in caso di errore (\texttt{\small 50--53}). Si noti +che nel ciclo si esegue una sola lettura, contrariamente a quanto fatto con la +precedente versione (si riveda il codice di \secref{fig:TCP_ServEcho_second}) +in cui si continuava a leggere fintanto che non si riceveva un +\textit{end-of-file}, questo perché usando l'\textit{I/O multiplexing} non si +vuole essere bloccati in lettura. L'uso di \func{select} ci permette di +trattare automaticamente anche il caso in cui la \func{read} non è stata in +grado di leggere tutti i dati presenti sul socket, dato che alla iterazione +successiva \func{select} ritornerà immediatamente segnalando l'ulteriore +disponibilità. + +Il nostro server comunque soffre di una vulnerabilità per un attacco di tipo +\textit{Denial of Service}. Il problema è che in caso di blocco di una +qualunque delle funzioni di I/O, non avendo usato processi separati, tutto il +server si ferma e non risponde più a nessuna richiesta. Abbiamo scongiurato +questa evenienza per l'I/O in ingresso con l'uso di \func{select}, ma non vale +altrettanto per l'I/O in uscita. Il problema pertanto può sorgere qualora una +delle chiamate a \func{write} effettuate da \func{FullWrite} si blocchi. Con +il funzionamento normale questo non accade in quanto il server si limita a +scrivere quanto riceve in ingresso, ma qualora venga utilizzato un client +malevolo che esegua solo scritture e non legga mai indietro l'\textsl{eco} del +server, si potrebbe giungere alla saturazione del buffer di scrittura, ed al +conseguente blocco del server su di una \func{write}. + +Le possibili soluzioni in questo caso sono quelle di ritornare ad eseguire il +ciclo di risposta alle richieste all'interno di processi separati, utilizzare +un timeout per le operazioni di scrittura, o eseguire queste ultime in +modalità non bloccante, cocludendo le operazioni qualora non vadano a buon +fine.