\chapter{La gestione avanzata dei file}
\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}.


\section{Le funzioni di I/O avanzato}
\label{sec:file_advanced_io}

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.


\subsection{La modalità di I/O \textsl{non-bloccante}}
\label{sec:file_noblocking}

Abbiamo visto in \secref{sec:sig_gen_beha}, affrontando la suddivisione fra
\textit{fast} e \textit{slow} system call, che in certi casi le funzioni di
I/O possono bloccarsi indefinitamente.\footnote{si ricordi però che questo può
  accadere solo per le pipe, i socket ed alcuni file di dispositivo; sui file
  normali le funzioni di lettura e scrittura ritornano sempre subito.}  Ad
esempio le operazioni di 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 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}, 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 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. 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{Le funzioni \func{poll} e \func{select}}
\label{sec:file_multiplexing}

Per superare il problema di dover usare il \textit{polling} controllare la
disponibilità di accesso ad un file aperto in modalità non bloccante, sia BSD
che SysV hanno introdotto delle nuove funzioni in grado di sospendere
l'esecuzione di un processo fino a che l'accesso diventi possibile; il primo
ad introdurre questa nuova interfaccia, chiamata usualmente \textit{I/O
  multiplexing}, è stato BSD, con l'introduzione della 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 un certo insieme di file descriptor cambi stato.
  
\bodydesc{La funzione restituisce il numero di file descriptor, anche nullo,
  che hanno cambiato stato in caso di successo 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
  degeli 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 non viene rilevate dell'attività
sull'insieme dei file descriptor specificati (\param{readfds},
\param{writefds} e \param{exceptfds}), per un tempo massimo specificato da
\param{timeout}. 

Per specificare quali file descriptor si intende 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 a seconda del sistema può essere
il limite del 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, questo limite non c'è un massimo,
esso indica le dimensioni in munero di bit utilizzabili per l'insieme.


La funzione richiede di specificare tre insiemi distinti di file descriptor;
il primo, \param{readfds}, verrà osservato per rilevare la disponibilità di
input in lettura, il secondo, \param{writefds} per verificare la possibilità
di scrivere ed il terzo, \param{exceptfds}, per verificare l'esistenza di
eccezioni. I corrispondenti valori dei \textit{file descriptor set} saranno
modificati di conseguenza per mostrare quale dei file descriptor ha cambiato
stato.





Come accennato l'interfaccia di \func{select} è una estensione aggiunta BSD, e
poi entrata a far parte di POSIX; allo stesso tempo System V aveva introdotto
una interfaccia alternativa, 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:

.}
\end{prototype}




\subsection{L'I/O asincrono}
\label{sec:file_asyncronous_io}

Una modalità alternativa all'uso dell'I/O non bloccante è quella di fare
ricorso all'I/O asincrono. Abbiamo accennato in \secref{sec:file_open} che è
possibile, attraverso l'uso del flag \macro{O\_ASYNC}, aprire un file in
modalità asincrona, così come è possibile settare questo flag attraverso l'uso
di \func{fcntl}.

In tal caso il sistema genera un segnale \macro{SIGIO} tutte le volte che sono
presenti dei dati in input su un file aperto in questa modalità.  Uno dei
problemi che si presentavano con le prime implementazioni di questa modalità
di I/O è che essa poteva essere usata in maniera semplice aprendo un solo file
per processo, dato che altrimenti si sarebbe dovuto provvedere ad effettuare
una serie di controlli su tutti i file aperti per distinguere a quale fosse
dovuto l'emissione del segnale.

Tutto questo adesso può essere evitato facendo ricorso alle informazioni
restituite al manipolatore del segnale attraverso la struttura
\var{siginfo\_t} (vedi \figref{fig:sig_siginfo_t}), il cui campo \var{si\_fd}
riporta il file descriptor che ha generato il segnale.



\subsection{File mappati in memoria}
\label{sec:file_memory_map}


\subsection{I/O multiplo}
\label{sec:file_multiple_io}



\section{Il file locking}
\label{sec:file_locking}

In \secref{sec:file_sharing} abbiamo preso in esame le mosalità 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\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 imprevedebile 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
bloccare l'accesso al file da parte di altri processi, così da evitare le
sovrapposizioni, e garantire la atomicità delle operazioni di scrittura.


\subsection{L'\textit{advisory locking}}
\label{sec:file_record_locking}

La prima modalità di file locking che è stata implementata nei sistemi
unix-like è quella che viene usualmente chiamata \textit{advisory locking}, in
quanto è il processo, e non il sistema, che si incarica di verificare se
esiste una condizione di blocco per l'accesso ai file.




\subsection{Il \textit{mandatory locking}}
\label{sec:file_mand_locking}

Il \textit{mandatory locking} è una opzione introdotta inizialmente in SVr4, 






%%% Local Variables: 
%%% mode: latex
%%% TeX-master: "gapil"
%%% End: 
