\label{cha:file_advanced}
In questo capitolo affronteremo le tematiche relative alla gestione avanzata
-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}.
+dei file. Inizieremo con la trattazione delle problematiche del \textit{file
+ locking} e poi prenderemo in esame le varie funzionalità avanzate che
+permettono una gestione più sofisticata dell'I/O su file, a partire da quelle
+che consentono di gestire l'accesso contemporaneo a più file esaminando le
+varie modalità alternative di gestire l'I/O per concludere con la gestione dei
+file mappati in memoria e le altre funzioni avanzate che consentono un
+controllo più dettagliato delle modalità di I/O.
-\section{L'\textit{I/O multiplexing}}
-\label{sec:file_multiplexing}
-
-Uno dei problemi che si presentano quando si deve operare contemporaneamente
-su molti file usando le funzioni illustrate in
-cap.~\ref{cha:file_unix_interface} e cap.~\ref{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.
+\section{Il \textit{file locking}}
+\label{sec:file_locking}
+\index{file!locking|(}
-\subsection{La problematica dell'\textit{I/O multiplexing}}
-\label{sec:file_noblocking}
+In sez.~\ref{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 \itindex{append~mode} \textit{append mode}, quando più processi scrivono
+contemporaneamente sullo stesso file non è possibile determinare la sequenza
+in cui essi opereranno.
-Abbiamo visto in sez.~\ref{sec:sig_gen_beha}, affrontando la suddivisione fra
-\textit{fast} e \textit{slow} system call,\index{system~call~lente} 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\index{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 causa la possibilità di una \itindex{race~condition} \textit{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.
-Questo comportamento causa uno dei problemi più comuni che ci si trova ad
-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 \itindex{deadlock} \textit{deadlock}.
+In tutti questi casi il \textit{file locking} è la tecnica che permette di
+evitare le \itindex{race~condition} \textit{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 lettura o scrittura.
-Abbiamo già accennato in sez.~\ref{sec:file_open} che è possibile prevenire
-questo tipo di comportamento delle funzioni di I/O aprendo un file in
-\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 \itindex{polling} \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 superare questo problema è stato introdotto il concetto di \textit{I/O
- 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 effettuabile, in
-modo da poterla eseguire con la sicurezza di non restare bloccati.
+\subsection{L'\textit{advisory locking}}
+\label{sec:file_record_locking}
-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 di utilizzo concreto in
-sez.~\ref{sec:TCP_sock_multiplexing}.
+La prima modalità di \textit{file locking} che è stata implementata nei
+sistemi unix-like è quella che viene usualmente chiamata \textit{advisory
+ locking},\footnote{Stevens in \cite{APUE} fa riferimento a questo argomento
+ come al \textit{record locking}, dizione utilizzata anche dal manuale delle
+ \acr{glibc}; nelle pagine di manuale si parla di \textit{discrectionary file
+ lock} per \func{fcntl} e di \textit{advisory locking} per \func{flock},
+ mentre questo nome viene usato da Stevens per riferirsi al \textit{file
+ locking} POSIX. Dato che la dizione \textit{record locking} è quantomeno
+ ambigua, in quanto in un sistema Unix non esiste niente che possa fare
+ riferimento al concetto di \textit{record}, alla fine si è scelto di
+ mantenere il nome \textit{advisory locking}.} in quanto sono i singoli
+processi, e non il sistema, che si incaricano di asserire e verificare se
+esistono delle condizioni di blocco per l'accesso ai file.
+Questo significa che le funzioni \func{read} o \func{write} vengono eseguite
+comunque e non risentono affatto della presenza di un eventuale \textit{lock};
+pertanto è sempre compito dei vari processi che intendono usare il
+\textit{file locking}, controllare esplicitamente lo stato dei file condivisi
+prima di accedervi, utilizzando le relative funzioni.
-\subsection{Le funzioni \func{select} e \func{pselect}}
-\label{sec:file_select}
+In generale si distinguono due tipologie di \textit{file lock};\footnote{di
+ seguito ci riferiremo sempre ai blocchi di accesso ai file con la
+ nomenclatura inglese di \textit{file lock}, o più brevemente con
+ \textit{lock}, per evitare confusioni linguistiche con il blocco di un
+ processo (cioè la condizione in cui il processo viene posto in stato di
+ \textit{sleep}).} la prima è il cosiddetto \textit{shared lock}, detto anche
+\textit{read lock} in quanto serve a bloccare l'accesso in scrittura su un
+file affinché il suo contenuto non venga modificato mentre lo si legge. Si
+parla appunto di \textsl{blocco condiviso} in quanto più processi possono
+richiedere contemporaneamente uno \textit{shared lock} su un file per
+proteggere il loro accesso in lettura.
-Il primo kernel unix-like 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 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}
- \headdecl{unistd.h}
- \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
- 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{ndfs} un valore negativo
- o un valore non valido per \param{timeout}.
- \end{errlist}
- ed inoltre \errval{ENOMEM}.
-}
-\end{functions}
+La seconda tipologia è il cosiddetto \textit{exclusive lock}, detto anche
+\textit{write lock} in quanto serve a bloccare l'accesso su un file (sia in
+lettura che in scrittura) da parte di altri processi mentre lo si sta
+scrivendo. Si parla di \textsl{blocco esclusivo} appunto perché un solo
+processo alla volta può richiedere un \textit{exclusive lock} su un file per
+proteggere il suo accesso in scrittura.
-La funzione mette il processo in stato di \textit{sleep} (vedi
-tab.~\ref{tab:proc_proc_states}) fintanto che almeno uno dei file descriptor
-degli insiemi specificati (\param{readfds}, \param{writefds} e
-\param{exceptfds}), non diventa attivo, per un tempo massimo specificato da
-\param{timeout}.
+In Linux sono disponibili due interfacce per utilizzare l'\textit{advisory
+ locking}, la prima è quella derivata da BSD, che è basata sulla funzione
+\func{flock}, la seconda è quella standardizzata da POSIX.1 (derivata da
+System V), che è basata sulla funzione \func{fcntl}. I \textit{file lock}
+sono implementati in maniera completamente indipendente nelle due
+interfacce,\footnote{in realtà con Linux questo avviene solo dalla serie 2.0
+ dei kernel.} che pertanto possono coesistere senza interferenze.
-\itindbeg{file~descriptor~set}
+Entrambe le interfacce prevedono la stessa procedura di funzionamento: si
+inizia sempre con il richiedere l'opportuno \textit{file lock} (un
+\textit{exclusive lock} per una scrittura, uno \textit{shared lock} per una
+lettura) prima di eseguire l'accesso ad un file. Se il blocco viene acquisito
+il processo prosegue l'esecuzione, altrimenti (a meno di non aver richiesto un
+comportamento non bloccante) viene posto in stato di sleep. Una volta finite
+le operazioni sul file si deve provvedere a rimuovere il blocco. La situazione
+delle varie possibilità è riassunta in tab.~\ref{tab:file_file_lock}, dove si
+sono riportati, per le varie tipologie di blocco presenti su un file, il
+risultato che si ha in corrispondenza alle due tipologie di \textit{file lock}
+menzionate, nel successo della richiesta.
-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 \itindex{signal~set}
-\textit{signal set} (vedi sez.~\ref{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/time.h}
- \headdecl{sys/types.h}
- \headdecl{unistd.h}
- \funcdecl{void \macro{FD\_ZERO}(fd\_set *set)}
- Inizializza l'insieme (vuoto).
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|c|c|c|}
+ \hline
+ \textbf{Richiesta} & \multicolumn{3}{|c|}{\textbf{Stato del file}}\\
+ \cline{2-4}
+ &Nessun \textit{lock}&\textit{Read lock}&\textit{Write lock}\\
+ \hline
+ \hline
+ \textit{Read lock} & SI & SI & NO \\
+ \textit{Write lock}& SI & NO & NO \\
+ \hline
+ \end{tabular}
+ \caption{Tipologie di \textit{file locking}.}
+ \label{tab:file_file_lock}
+\end{table}
- \funcdecl{void \macro{FD\_SET}(int fd, fd\_set *set)}
- Inserisce il file descriptor \param{fd} nell'insieme.
+Si tenga presente infine che il controllo di accesso e la gestione dei
+permessi viene effettuata quando si apre un file, l'unico controllo residuo
+che si può avere riguardo il \textit{file locking} è che il tipo di blocco che
+si vuole ottenere su un file deve essere compatibile con le modalità di
+apertura dello stesso (in lettura per un \textit{read lock} e in scrittura per
+un \textit{write lock}).
- \funcdecl{void \macro{FD\_CLR}(int fd, fd\_set *set)}
- 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.
-\end{functions}
+%% Si ricordi che
+%% la condizione per acquisire uno \textit{shared lock} è che il file non abbia
+%% già un \textit{exclusive lock} attivo, mentre per acquisire un
+%% \textit{exclusive lock} non deve essere presente nessun tipo di blocco.
-In genere un \textit{file descriptor set} può contenere fino ad un massimo di
-\const{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 da
-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}.\footnote{il suo valore, secondo lo standard POSIX
- 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; 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}; 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
-sez.~\ref{sec:TCP_urgent_data}).
+\subsection{La funzione \func{flock}}
+\label{sec:file_flock}
-Dato che in genere non si tengono mai sotto controllo fino a
-\const{FD\_SETSIZE} file contemporaneamente la funzione richiede di
-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{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 prima interfaccia per il \textit{file locking}, quella derivata da BSD,
+permette di eseguire un blocco solo su un intero file; la funzione usata per
+richiedere e rimuovere un \textit{file lock} è \funcd{flock}, ed il suo
+prototipo è:
+\begin{prototype}{sys/file.h}{int flock(int fd, int operation)}
+
+ Applica o rimuove un \textit{file lock} sul file \param{fd}.
+
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ errore, nel qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EWOULDBLOCK}] il file ha già un blocco attivo, e si è
+ specificato \const{LOCK\_NB}.
+ \end{errlist}
+ }
+\end{prototype}
-La funzione restituisce il numero di file descriptor pronti,\footnote{questo è
- il comportamento previsto dallo standard, ma la standardizzazione della
- funzione è recente, ed esistono ancora alcune versioni di Unix che non si
- comportano in questo modo.} e ciascun insieme viene sovrascritto per
-indicare quali sono i file descriptor pronti per le operazioni ad esso
-relative, in modo da poterli controllare con \macro{FD\_ISSET}. Se invece si
-ha un timeout viene restituito un valore nullo e gli insiemi non vengono
-modificati. In caso di errore la funzione restituisce -1, ed i valori dei tre
-insiemi sono indefiniti e non si può fare nessun affidamento sul loro
-contenuto.
+La funzione può essere usata per acquisire o rilasciare un \textit{file lock}
+a seconda di quanto specificato tramite il valore dell'argomento
+\param{operation}; questo viene interpretato come maschera binaria, e deve
+essere passato costruendo il valore con un OR aritmetico delle costanti
+riportate in tab.~\ref{tab:file_flock_operation}.
-\itindend{file~descriptor~set}
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|p{6cm}|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{LOCK\_SH} & Richiede uno \textit{shared lock} sul file.\\
+ \const{LOCK\_EX} & Richiede un \textit{esclusive lock} sul file.\\
+ \const{LOCK\_UN} & Rilascia il \textit{file lock}.\\
+ \const{LOCK\_NB} & Impedisce che la funzione si blocchi nella
+ richiesta di un \textit{file lock}.\\
+ \hline
+ \end{tabular}
+ \caption{Valori dell'argomento \param{operation} di \func{flock}.}
+ \label{tab:file_flock_operation}
+\end{table}
-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
-si ha un errore di \errcode{EINTR}, ed occorre rilanciare la funzione; in
-questo modo non è necessario ricalcolare tutte le volte il tempo
-rimanente.\footnote{questo 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.}
-
-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{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
-tenere sotto controllo, la funzione è nata quando il kernel consentiva un
-numero massimo di 1024 file descriptor per processo, adesso che il numero può
-essere arbitrario 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.
-
-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{ndfs} 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 fig.~\ref{fig:sys_timespec_struct}) per
-indicare con maggiore precisione il timeout e non ne aggiorna il valore in
-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.
+I primi due valori, \const{LOCK\_SH} e \const{LOCK\_EX} permettono di
+richiedere un \textit{file lock}, ed ovviamente devono essere usati in maniera
+alternativa. Se si specifica anche \const{LOCK\_NB} la funzione non si
+bloccherà qualora il \textit{file lock} non possa essere acquisito, ma
+ritornerà subito con un errore di \errcode{EWOULDBLOCK}. Per rilasciare un
+\textit{file lock} si dovrà invece usare \const{LOCK\_UN}.
+
+Si tenga presente che non esite una modalità per eseguire atomicamente un
+cambiamento del tipo di blocco (da \textit{shared lock} a \textit{esclusive
+ lock}), il blocco deve essere prima rilasciato e poi richiesto, ed è sempre
+possibile che nel frattempo abbia successo un'altra richiesta pendente,
+facendo fallire la riacquisizione.
+
+Si tenga presente infine che \func{flock} non è supportata per i file
+mantenuti su NFS, in questo caso, se si ha la necessità di utilizzare il
+\textit{file locking}, occorre usare l'interfaccia del \textit{file locking}
+POSIX basata su \func{fcntl} che è in grado di funzionare anche attraverso
+NFS, a condizione ovviamente che sia il client che il server supportino questa
+funzionalità.
+
+La semantica del \textit{file locking} di BSD inoltre è diversa da quella del
+\textit{file locking} POSIX, in particolare per quanto riguarda il
+comportamento dei \textit{file lock} nei confronti delle due funzioni
+\func{dup} e \func{fork}. Per capire queste differenze occorre descrivere con
+maggiore dettaglio come viene realizzato dal kernel il \textit{file locking}
+per entrambe le interfacce.
-L'uso di \param{sigmask} è stato introdotto allo scopo di prevenire possibili
-\textit{race condition} \itindex{race~condition} quando ci si deve porre in
-attesa sia di un segnale che di dati. La tecnica classica è quella di
-utilizzare il gestore per impostare una variabile globale e controllare questa
-nel corpo principale del programma; abbiamo visto in
-sez.~\ref{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.
+In fig.~\ref{fig:file_flock_struct} si è riportato uno schema essenziale
+dell'implementazione del \textit{file locking} in stile BSD su Linux. Il punto
+fondamentale da capire è che un \textit{file lock}, qualunque sia
+l'interfaccia che si usa, anche se richiesto attraverso un file descriptor,
+agisce sempre su un file; perciò le informazioni relative agli eventuali
+\textit{file lock} sono mantenute a livello di inode\index{inode},\footnote{in
+ particolare, come accennato in fig.~\ref{fig:file_flock_struct}, i
+ \textit{file lock} sono mantenuti in una \itindex{linked~list}
+ \textit{linked list} di strutture \struct{file\_lock}. La lista è
+ referenziata dall'indirizzo di partenza mantenuto dal campo \var{i\_flock}
+ della struttura \struct{inode} (per le definizioni esatte si faccia
+ riferimento al file \file{fs.h} nei sorgenti del kernel). Un bit del campo
+ \var{fl\_flags} di specifica se si tratta di un lock in semantica BSD
+ (\const{FL\_FLOCK}) o POSIX (\const{FL\_POSIX}).} dato che questo è l'unico
+riferimento in comune che possono avere due processi diversi che aprono lo
+stesso file.
-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 \itindex{race~condition} \textit{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.
+\begin{figure}[htb]
+ \centering
+ \includegraphics[width=15cm]{img/file_flock}
+ \caption{Schema dell'architettura del \textit{file locking}, nel caso
+ particolare del suo utilizzo da parte dalla funzione \func{flock}.}
+ \label{fig:file_flock_struct}
+\end{figure}
-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 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} 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.
+La richiesta di un \textit{file lock} prevede una scansione della lista per
+determinare se l'acquisizione è possibile, ed in caso positivo l'aggiunta di
+un nuovo elemento.\footnote{cioè una nuova struttura \struct{file\_lock}.}
+Nel caso dei blocchi creati con \func{flock} la semantica della funzione
+prevede che sia \func{dup} che \func{fork} non creino ulteriori istanze di un
+\textit{file lock} quanto piuttosto degli ulteriori riferimenti allo
+stesso. Questo viene realizzato dal kernel secondo lo schema di
+fig.~\ref{fig:file_flock_struct}, associando ad ogni nuovo \textit{file lock}
+un puntatore\footnote{il puntatore è mantenuto nel campo \var{fl\_file} di
+ \struct{file\_lock}, e viene utilizzato solo per i \textit{file lock} creati
+ con la semantica BSD.} alla voce nella \itindex{file~table} \textit{file
+ table} da cui si è richiesto il blocco, che così ne identifica il titolare.
+
+Questa struttura prevede che, quando si richiede la rimozione di un
+\textit{file lock}, il kernel acconsenta solo se la richiesta proviene da un
+file descriptor che fa riferimento ad una voce nella \itindex{file~table}
+\textit{file table} corrispondente a quella registrata nel blocco. Allora se
+ricordiamo quanto visto in sez.~\ref{sec:file_dup} e
+sez.~\ref{sec:file_sharing}, e cioè che i file descriptor duplicati e quelli
+ereditati in un processo figlio puntano sempre alla stessa voce nella
+\itindex{file~table} \textit{file table}, si può capire immediatamente quali
+sono le conseguenze nei confronti delle funzioni \func{dup} e \func{fork}.
+
+Sarà così possibile rimuovere un \textit{file lock} attraverso uno qualunque
+dei file descriptor che fanno riferimento alla stessa voce nella
+\itindex{file~table} \textit{file table}, anche se questo è diverso da quello
+con cui lo si è creato,\footnote{attenzione, questo non vale se il file
+ descriptor fa riferimento allo stesso file, ma attraverso una voce diversa
+ della \itindex{file~table} \textit{file table}, come accade tutte le volte
+ che si apre più volte lo stesso file.} o se si esegue la rimozione in un
+processo figlio; inoltre una volta tolto un \textit{file lock}, la rimozione
+avrà effetto su tutti i file descriptor che condividono la stessa voce nella
+\itindex{file~table} \textit{file table}, e quindi, nel caso di file
+descriptor ereditati attraverso una \func{fork}, anche su processi diversi.
+Infine, per evitare che la terminazione imprevista di un processo lasci attivi
+dei \textit{file lock}, quando un file viene chiuso il kernel provvede anche a
+rimuovere tutti i blocchi ad esso associati. Anche in questo caso occorre
+tenere presente cosa succede quando si hanno file descriptor duplicati; in tal
+caso infatti il file non verrà effettivamente chiuso (ed il blocco rimosso)
+fintanto che non viene rilasciata la relativa voce nella \itindex{file~table}
+\textit{file table}; e questo avverrà solo quando tutti i file descriptor che
+fanno riferimento alla stessa voce sono stati chiusi. Quindi, nel caso ci
+siano duplicati o processi figli che mantengono ancora aperto un file
+descriptor, il \textit{file lock} non viene rilasciato.
+
-\subsection{Le funzioni \func{poll} e \func{ppoll}}
-\label{sec:file_poll}
+\subsection{Il \textit{file locking} POSIX}
+\label{sec:file_posix_lock}
-Nello sviluppo di System V, invece di utilizzare l'interfaccia di
-\func{select}, che è una estensione tipica di BSD, è stata introdotta un'altra
-interfaccia, 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 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 seconda interfaccia per l'\textit{advisory locking} disponibile in Linux è
+quella standardizzata da POSIX, basata sulla funzione \func{fcntl}. Abbiamo
+già trattato questa funzione nelle sue molteplici possibilità di utilizzo in
+sez.~\ref{sec:file_fcntl}. Quando la si impiega per il \textit{file locking}
+essa viene usata solo secondo il seguente prototipo:
+\begin{prototype}{fcntl.h}{int fcntl(int fd, int cmd, struct flock *lock)}
- La funzione attende un cambiamento di stato su un insieme di file
- descriptor.
+ Applica o rimuove un \textit{file lock} sul file \param{fd}.
- \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}.}
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ errore, nel qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EACCES}] l'operazione è proibita per la presenza di
+ \textit{file lock} da parte di altri processi.
+ \item[\errcode{ENOLCK}] il sistema non ha le risorse per il blocco: ci
+ sono troppi segmenti di \textit{lock} aperti, si è esaurita la tabella
+ dei \textit{file lock}, o il protocollo per il blocco remoto è fallito.
+ \item[\errcode{EDEADLK}] si è richiesto un \textit{lock} su una regione
+ bloccata da un altro processo che è a sua volta in attesa dello sblocco
+ di un \textit{lock} mantenuto dal processo corrente; si avrebbe pertanto
+ un \itindex{deadlock} \textit{deadlock}. Non è garantito che il sistema
+ riconosca sempre questa situazione.
+ \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima
+ di poter acquisire un \textit{file lock}.
+ \end{errlist}
+ ed inoltre \errval{EBADF}, \errval{EFAULT}.
+ }
\end{prototype}
-La funzione permette di tenere sotto controllo contemporaneamente \param{ndfs}
-file descriptor, specificati attraverso il puntatore \param{ufds} ad un
-vettore di strutture \struct{pollfd}. Come con \func{select} si può
-interrompere l'attesa dopo un certo tempo, questo deve essere specificato con
-l'argomento \param{timeout} in numero di millisecondi: un valore negativo
-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}).
-
-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},
-prevede tre campi: in \var{fd} deve essere indicato il numero del file
-descriptor da controllare, in \var{events} deve essere specificata una
-maschera binaria di flag che indichino il tipo di evento che si vuole
-controllare, mentre in \var{revents} il kernel restituirà il relativo
-risultato. Usando un valore negativo per \param{fd} la corrispondente
-struttura sarà ignorata da \func{poll}. Dato che i dati in ingresso sono del
-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.
+Al contrario di quanto avviene con l'interfaccia basata su \func{flock} con
+\func{fcntl} è possibile bloccare anche delle singole sezioni di un file, fino
+al singolo byte. Inoltre la funzione permette di ottenere alcune informazioni
+relative agli eventuali blocchi preesistenti. Per poter fare tutto questo la
+funzione utilizza come terzo argomento una apposita struttura \struct{flock}
+(la cui definizione è riportata in fig.~\ref{fig:struct_flock}) nella quale
+inserire tutti i dati relativi ad un determinato blocco. Si tenga presente poi
+che un \textit{file lock} fa sempre riferimento ad una regione, per cui si
+potrà avere un conflitto anche se c'è soltanto una sovrapposizione parziale
+con un'altra regione bloccata.
-\begin{figure}[!htb]
+\begin{figure}[!bht]
\footnotesize \centering
\begin{minipage}[c]{15cm}
- \includestruct{listati/pollfd.h}
+ \includestruct{listati/flock.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}
+ \caption{La struttura \structd{flock}, usata da \func{fcntl} per il
+ \textit{file locking}.}
+ \label{fig:struct_flock}
\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
-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.
+
+I primi tre campi della struttura, \var{l\_whence}, \var{l\_start} e
+\var{l\_len}, servono a specificare la sezione del file a cui fa riferimento
+il blocco: \var{l\_start} specifica il byte di partenza, \var{l\_len} la
+lunghezza della sezione e infine \var{l\_whence} imposta il riferimento da cui
+contare \var{l\_start}. Il valore di \var{l\_whence} segue la stessa semantica
+dell'omonimo argomento di \func{lseek}, coi tre possibili valori
+\const{SEEK\_SET}, \const{SEEK\_CUR} e \const{SEEK\_END}, (si vedano le
+relative descrizioni in sez.~\ref{sec:file_lseek}).
+
+Si tenga presente che un \textit{file lock} può essere richiesto anche per una
+regione al di là della corrente fine del file, così che una eventuale
+estensione dello stesso resti coperta dal blocco. Inoltre se si specifica un
+valore nullo per \var{l\_len} il blocco si considera esteso fino alla
+dimensione massima del file; in questo modo è possibile bloccare una qualunque
+regione a partire da un certo punto fino alla fine del file, coprendo
+automaticamente quanto eventualmente aggiunto in coda allo stesso.
\begin{table}[htb]
\centering
\footnotesize
\begin{tabular}[c]{|l|l|}
\hline
- \textbf{Flag} & \textbf{Significato} \\
- \hline
- \hline
- \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 \itindex{out-of-band} 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.\\
+ \textbf{Valore} & \textbf{Significato} \\
\hline
- \const{POLLERR} & C'è una condizione di errore.\\
- \const{POLLHUP} & Si è verificato un hung-up.\\
- \const{POLLNVAL} & Il file descriptor non è aperto.\\
\hline
- \const{POLLMSG} & Definito per compatibilità con SysV.\\
+ \const{F\_RDLCK} & Richiede un blocco condiviso (\textit{read lock}).\\
+ \const{F\_WRLCK} & Richiede un blocco esclusivo (\textit{write lock}).\\
+ \const{F\_UNLCK} & Richiede l'eliminazione di un \textit{file lock}.\\
\hline
\end{tabular}
- \caption{Costanti per l'identificazione dei vari bit dei campi
- \var{events} e \var{revents} di \struct{pollfd}.}
- \label{tab:file_pollfd_flags}
+ \caption{Valori possibili per il campo \var{l\_type} di \struct{flock}.}
+ \label{tab:file_flock_type}
\end{table}
-Il valore \const{POLLMSG} non viene utilizzato ed è definito solo per
-compatibilità con l'implementazione di SysV che usa gli
-\textit{stream};\footnote{essi 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 di alcune
-costanti, in quanto per essi sono definite tre classi di dati:
-\textsl{normali}, \textit{prioritari} ed \textit{urgenti}. In Linux la
-distinzione ha senso solo per i dati urgenti \itindex{out-of-band} dei socket
-(vedi sez.~\ref{sec:TCP_urgent_data}), ma su questo e su come \func{poll}
-reagisce alle varie condizioni dei socket torneremo in
-sez.~\ref{sec:TCP_serv_poll}, dove vedremo anche un esempio del suo utilizzo.
-Si tenga conto comunque che le costanti relative ai diversi tipi di dati (come
-\const{POLLRDNORM} e \const{POLLRDBAND}) sono utilizzabili soltanto qualora si
-sia definita la macro \macro{\_XOPEN\_SOURCE}.\footnote{e ci si ricordi di
- farlo sempre in testa al file, definirla soltanto prima di includere
- \file{sys/poll.h} non è sufficiente.}
-
-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 tab.~\ref{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}.
+Il tipo di \textit{file lock} richiesto viene specificato dal campo
+\var{l\_type}, esso può assumere i tre valori definiti dalle costanti
+riportate in tab.~\ref{tab:file_flock_type}, che permettono di richiedere
+rispettivamente uno \textit{shared lock}, un \textit{esclusive lock}, e la
+rimozione di un blocco precedentemente acquisito. Infine il campo \var{l\_pid}
+viene usato solo in caso di lettura, quando si chiama \func{fcntl} con
+\const{F\_GETLK}, e riporta il \acr{pid} del processo che detiene il
+\textit{file lock}.
-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.}
+Oltre a quanto richiesto tramite i campi di \struct{flock}, l'operazione
+effettivamente svolta dalla funzione è stabilita dal valore dall'argomento
+\param{cmd} che, come già riportato in sez.~\ref{sec:file_fcntl}, specifica
+l'azione da compiere; i valori relativi al \textit{file locking} sono tre:
+\begin{basedescript}{\desclabelwidth{2.0cm}}
+\item[\const{F\_GETLK}] verifica se il \textit{file lock} specificato dalla
+ struttura puntata da \param{lock} può essere acquisito: in caso negativo
+ sovrascrive la struttura \param{flock} con i valori relativi al blocco già
+ esistente che ne blocca l'acquisizione, altrimenti si limita a impostarne il
+ campo \var{l\_type} con il valore \const{F\_UNLCK}.
+\item[\const{F\_SETLK}] se il campo \var{l\_type} della struttura puntata da
+ \param{lock} è \const{F\_RDLCK} o \const{F\_WRLCK} richiede il
+ corrispondente \textit{file lock}, se è \const{F\_UNLCK} lo rilascia. Nel
+ caso la richiesta non possa essere soddisfatta a causa di un blocco
+ preesistente la funzione ritorna immediatamente con un errore di
+ \errcode{EACCES} o di \errcode{EAGAIN}.
+\item[\const{F\_SETLKW}] è identica a \const{F\_SETLK}, ma se la richiesta di
+ non può essere soddisfatta per la presenza di un altro blocco, mette il
+ processo in stato di attesa fintanto che il blocco precedente non viene
+ rilasciato. Se l'attesa viene interrotta da un segnale la funzione ritorna
+ con un errore di \errcode{EINTR}.
+\end{basedescript}
-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}.
+Si noti che per quanto detto il comando \const{F\_GETLK} non serve a rilevare
+una presenza generica di blocco su un file, perché se ne esistono altri
+compatibili con quello richiesto, la funzione ritorna comunque impostando
+\var{l\_type} a \const{F\_UNLCK}. Inoltre a seconda del valore di
+\var{l\_type} si potrà controllare o l'esistenza di un qualunque tipo di
+blocco (se è \const{F\_WRLCK}) o di \textit{write lock} (se è
+\const{F\_RDLCK}). Si consideri poi che può esserci più di un blocco che
+impedisce l'acquisizione di quello richiesto (basta che le regioni si
+sovrappongano), ma la funzione ne riporterà sempre soltanto uno, impostando
+\var{l\_whence} a \const{SEEK\_SET} ed i valori \var{l\_start} e \var{l\_len}
+per indicare quale è la regione bloccata.
-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}.
+Infine si tenga presente che effettuare un controllo con il comando
+\const{F\_GETLK} e poi tentare l'acquisizione con \const{F\_SETLK} non è una
+operazione atomica (un altro processo potrebbe acquisire un blocco fra le due
+chiamate) per cui si deve sempre verificare il codice di ritorno di
+\func{fcntl}\footnote{controllare il codice di ritorno delle funzioni invocate
+ è comunque una buona norma di programmazione, che permette di evitare un
+ sacco di errori difficili da tracciare proprio perché non vengono rilevati.}
+quando la si invoca con \const{F\_SETLK}, per controllare che il blocco sia
+stato effettivamente acquisito.
-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}
+\begin{figure}[htb]
+ \centering \includegraphics[width=9cm]{img/file_lock_dead}
+ \caption{Schema di una situazione di \itindex{deadlock} \textit{deadlock}.}
+ \label{fig:file_flock_dead}
+\end{figure}
-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}
+Non operando a livello di interi file, il \textit{file locking} POSIX
+introduce un'ulteriore complicazione; consideriamo la situazione illustrata in
+fig.~\ref{fig:file_flock_dead}, in cui il processo A blocca la regione 1 e il
+processo B la regione 2. Supponiamo che successivamente il processo A richieda
+un lock sulla regione 2 che non può essere acquisito per il preesistente lock
+del processo 2; il processo 1 si bloccherà fintanto che il processo 2 non
+rilasci il blocco. Ma cosa accade se il processo 2 nel frattempo tenta a sua
+volta di ottenere un lock sulla regione A? Questa è una tipica situazione che
+porta ad un \itindex{deadlock} \textit{deadlock}, dato che a quel punto anche
+il processo 2 si bloccherebbe, e niente potrebbe sbloccare l'altro processo.
+Per questo motivo il kernel si incarica di rilevare situazioni di questo tipo,
+ed impedirle restituendo un errore di \errcode{EDEADLK} alla funzione che
+cerca di acquisire un blocco che porterebbe ad un \itindex{deadlock}
+\textit{deadlock}.
-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.
+Per capire meglio il funzionamento del \textit{file locking} in semantica
+POSIX (che differisce alquanto rispetto da quello di BSD, visto
+sez.~\ref{sec:file_flock}) esaminiamo più in dettaglio come viene gestito dal
+kernel. Lo schema delle strutture utilizzate è riportato in
+fig.~\ref{fig:file_posix_lock}; come si vede esso è molto simile all'analogo
+di fig.~\ref{fig:file_flock_struct}:\footnote{in questo caso nella figura si
+ sono evidenziati solo i campi di \struct{file\_lock} significativi per la
+ semantica POSIX, in particolare adesso ciascuna struttura contiene, oltre al
+ \acr{pid} del processo in \var{fl\_pid}, la sezione di file che viene
+ bloccata grazie ai campi \var{fl\_start} e \var{fl\_end}. La struttura è
+ comunque la stessa, solo che in questo caso nel campo \var{fl\_flags} è
+ impostato il bit \const{FL\_POSIX} ed il campo \var{fl\_file} non viene
+ usato.} il blocco è sempre associato \index{inode} all'inode, solo che in
+questo caso la titolarità non viene identificata con il riferimento ad una
+voce nella \itindex{file~table} \textit{file table}, ma con il valore del
+\acr{pid} del processo.
+\begin{figure}[!bht]
+ \centering \includegraphics[width=13cm]{img/file_posix_lock}
+ \caption{Schema dell'architettura del \textit{file locking}, nel caso
+ particolare del suo utilizzo secondo l'interfaccia standard POSIX.}
+ \label{fig:file_posix_lock}
+\end{figure}
-\subsection{L'interfaccia di \textit{epoll}}
-\label{sec:file_epoll}
+Quando si richiede un \textit{file lock} il kernel effettua una scansione di
+tutti i blocchi presenti sul file\footnote{scandisce cioè la
+ \itindex{linked~list} \textit{linked list} delle strutture
+ \struct{file\_lock}, scartando automaticamente quelle per cui
+ \var{fl\_flags} non è \const{FL\_POSIX}, così che le due interfacce restano
+ ben separate.} per verificare se la regione richiesta non si sovrappone ad
+una già bloccata, in caso affermativo decide in base al tipo di blocco, in
+caso negativo il nuovo blocco viene comunque acquisito ed aggiunto alla lista.
-\itindbeg{epoll}
+Nel caso di rimozione invece questa viene effettuata controllando che il
+\acr{pid} del processo richiedente corrisponda a quello contenuto nel blocco.
+Questa diversa modalità ha delle conseguenze precise riguardo il comportamento
+dei \textit{file lock} POSIX. La prima conseguenza è che un \textit{file lock}
+POSIX non viene mai ereditato attraverso una \func{fork}, dato che il processo
+figlio avrà un \acr{pid} diverso, mentre passa indenne attraverso una
+\func{exec} in quanto il \acr{pid} resta lo stesso. Questo comporta che, al
+contrario di quanto avveniva con la semantica BSD, quando un processo termina
+tutti i \textit{file lock} da esso detenuti vengono immediatamente rilasciati.
-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à.
+La seconda conseguenza è che qualunque file descriptor che faccia riferimento
+allo stesso file (che sia stato ottenuto con una \func{dup} o con una
+\func{open} in questo caso non fa differenza) può essere usato per rimuovere
+un blocco, dato che quello che conta è solo il \acr{pid} del processo. Da
+questo deriva una ulteriore sottile differenza di comportamento: dato che alla
+chiusura di un file i blocchi ad esso associati vengono rimossi, nella
+semantica POSIX basterà chiudere un file descriptor qualunque per cancellare
+tutti i blocchi relativi al file cui esso faceva riferimento, anche se questi
+fossero stati creati usando altri file descriptor che restano aperti.
+
+Dato che il controllo sull'accesso ai blocchi viene eseguito sulla base del
+\acr{pid} del processo, possiamo anche prendere in considerazione un altro
+degli aspetti meno chiari di questa interfaccia e cioè cosa succede quando si
+richiedono dei blocchi su regioni che si sovrappongono fra loro all'interno
+stesso processo. Siccome il controllo, come nel caso della rimozione, si basa
+solo sul \acr{pid} del processo che chiama la funzione, queste richieste
+avranno sempre successo.
-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.
+Nel caso della semantica BSD, essendo i lock relativi a tutto un file e non
+accumulandosi,\footnote{questa ultima caratteristica è vera in generale, se
+ cioè si richiede più volte lo stesso \textit{file lock}, o più blocchi sulla
+ stessa sezione di file, le richieste non si cumulano e basta una sola
+ richiesta di rilascio per cancellare il blocco.} la cosa non ha alcun
+effetto; la funzione ritorna con successo, senza che il kernel debba
+modificare la lista dei \textit{file lock}. In questo caso invece si possono
+avere una serie di situazioni diverse: ad esempio è possibile rimuovere con
+una sola chiamata più \textit{file lock} distinti (indicando in una regione
+che si sovrapponga completamente a quelle di questi ultimi), o rimuovere solo
+una parte di un blocco preesistente (indicando una regione contenuta in quella
+di un altro blocco), creando un buco, o coprire con un nuovo blocco altri
+\textit{file lock} già ottenuti, e così via, a secondo di come si
+sovrappongono le regioni richieste e del tipo di operazione richiesta. Il
+comportamento seguito in questo caso che la funzione ha successo ed esegue
+l'operazione richiesta sulla regione indicata; è compito del kernel
+preoccuparsi di accorpare o dividere le voci nella lista dei \textit{file
+ lock} per far si che le regioni bloccate da essa risultanti siano coerenti
+con quanto necessario a soddisfare l'operazione richiesta.
-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à.
+\begin{figure}[!htb]
+ \footnotesize \centering
+ \begin{minipage}[c]{15cm}
+ \includecodesample{listati/Flock.c}
+ \end{minipage}
+ \normalsize
+ \caption{Sezione principale del codice del programma \file{Flock.c}.}
+ \label{fig:file_flock_code}
+\end{figure}
-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}.
+Per fare qualche esempio sul \textit{file locking} si è scritto un programma che
+permette di bloccare una sezione di un file usando la semantica POSIX, o un
+intero file usando la semantica BSD; in fig.~\ref{fig:file_flock_code} è
+riportata il corpo principale del codice del programma, (il testo completo è
+allegato nella directory dei sorgenti).
-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.
+La sezione relativa alla gestione delle opzioni al solito si è omessa, come la
+funzione che stampa le istruzioni per l'uso del programma, essa si cura di
+impostare le variabili \var{type}, \var{start} e \var{len}; queste ultime due
+vengono inizializzate al valore numerico fornito rispettivamente tramite gli
+switch \code{-s} e \cmd{-l}, mentre il valore della prima viene impostato con
+le opzioni \cmd{-w} e \cmd{-r} si richiede rispettivamente o un \textit{write
+ lock} o \textit{read lock} (i due valori sono esclusivi, la variabile
+assumerà quello che si è specificato per ultimo). Oltre a queste tre vengono
+pure impostate la variabile \var{bsd}, che abilita la semantica omonima quando
+si invoca l'opzione \cmd{-f} (il valore preimpostato è nullo, ad indicare la
+semantica POSIX), e la variabile \var{cmd} che specifica la modalità di
+richiesta del \textit{file lock} (bloccante o meno), a seconda dell'opzione
+\cmd{-b}.
-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}.
+Il programma inizia col controllare (\texttt{\small 11--14}) che venga passato
+un argomento (il file da bloccare), che sia stato scelto (\texttt{\small
+ 15--18}) il tipo di blocco, dopo di che apre (\texttt{\small 19}) il file,
+uscendo (\texttt{\small 20--23}) in caso di errore. A questo punto il
+comportamento dipende dalla semantica scelta; nel caso sia BSD occorre
+reimpostare il valore di \var{cmd} per l'uso con \func{flock}; infatti il
+valore preimpostato fa riferimento alla semantica POSIX e vale rispettivamente
+\const{F\_SETLKW} o \const{F\_SETLK} a seconda che si sia impostato o meno la
+modalità bloccante.
-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}
+Nel caso si sia scelta la semantica BSD (\texttt{\small 25--34}) prima si
+controlla (\texttt{\small 27--31}) il valore di \var{cmd} per determinare se
+si vuole effettuare una chiamata bloccante o meno, reimpostandone il valore
+opportunamente, dopo di che a seconda del tipo di blocco al valore viene
+aggiunta la relativa opzione (con un OR aritmetico, dato che \func{flock}
+vuole un argomento \param{operation} in forma di maschera binaria. Nel caso
+invece che si sia scelta la semantica POSIX le operazioni sono molto più
+immediate, si prepara (\texttt{\small 36--40}) la struttura per il lock, e lo
+esegue (\texttt{\small 41}).
-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.
+In entrambi i casi dopo aver richiesto il blocco viene controllato il
+risultato uscendo (\texttt{\small 44--46}) in caso di errore, o stampando un
+messaggio (\texttt{\small 47--49}) in caso di successo. Infine il programma si
+pone in attesa (\texttt{\small 50}) finché un segnale (ad esempio un \cmd{C-c}
+dato da tastiera) non lo interrompa; in questo caso il programma termina, e
+tutti i blocchi vengono rilasciati.
-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)}
+Con il programma possiamo fare varie verifiche sul funzionamento del
+\textit{file locking}; cominciamo con l'eseguire un \textit{read lock} su un
+file, ad esempio usando all'interno di un terminale il seguente comando:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -r Flock.c
+Lock acquired
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+il programma segnalerà di aver acquisito un blocco e si bloccherà; in questo
+caso si è usato il \textit{file locking} POSIX e non avendo specificato niente
+riguardo alla sezione che si vuole bloccare sono stati usati i valori
+preimpostati che bloccano tutto il file. A questo punto se proviamo ad
+eseguire lo stesso comando in un altro terminale, e avremo lo stesso
+risultato. Se invece proviamo ad eseguire un \textit{write lock} avremo:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -w Flock.c
+Failed lock: Resource temporarily unavailable
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+come ci aspettiamo il programma terminerà segnalando l'indisponibilità del
+blocco, dato che il file è bloccato dal precedente \textit{read lock}. Si noti
+che il risultato è lo stesso anche se si richiede il blocco su una sola parte
+del file con il comando:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -w -s0 -l10 Flock.c
+Failed lock: Resource temporarily unavailable
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+se invece blocchiamo una regione con:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -r -s0 -l10 Flock.c
+Lock acquired
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+una volta che riproviamo ad acquisire il \textit{write lock} i risultati
+dipenderanno dalla regione richiesta; ad esempio nel caso in cui le due
+regioni si sovrappongono avremo che:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -w -s5 -l15 Flock.c
+Failed lock: Resource temporarily unavailable
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+ed il blocco viene rifiutato, ma se invece si richiede una regione distinta
+avremo che:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -w -s11 -l15 Flock.c
+Lock acquired
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+ed il blocco viene acquisito. Se a questo punto si prova ad eseguire un
+\textit{read lock} che comprende la nuova regione bloccata in scrittura:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -r -s10 -l20 Flock.c
+Failed lock: Resource temporarily unavailable
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+come ci aspettiamo questo non sarà consentito.
+
+Il programma di norma esegue il tentativo di acquisire il lock in modalità non
+bloccante, se però usiamo l'opzione \cmd{-b} possiamo impostare la modalità
+bloccante, riproviamo allora a ripetere le prove precedenti con questa
+opzione:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -r -b -s0 -l10 Flock.c Lock acquired
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+il primo comando acquisisce subito un \textit{read lock}, e quindi non cambia
+nulla, ma se proviamo adesso a richiedere un \textit{write lock} che non potrà
+essere acquisito otterremo:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -w -s0 -l10 Flock.c
+\end{verbatim}%$
+\end{minipage}\vspace{1mm}
+\par\noindent
+il programma cioè si bloccherà nella chiamata a \func{fcntl}; se a questo
+punto rilasciamo il precedente blocco (terminando il primo comando un
+\texttt{C-c} sul terminale) potremo verificare che sull'altro terminale il
+blocco viene acquisito, con la comparsa di una nuova riga:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[piccardi@gont sources]$ ./flock -w -s0 -l10 Flock.c
+Lock acquired
+\end{verbatim}%$
+\end{minipage}\vspace{3mm}
+\par\noindent
+
+Un'altra cosa che si può controllare con il nostro programma è l'interazione
+fra i due tipi di blocco; se ripartiamo dal primo comando con cui si è
+ottenuto un blocco in lettura sull'intero file, possiamo verificare cosa
+succede quando si cerca di ottenere un blocco in scrittura con la semantica
+BSD:
+
+\vspace{1mm}
+\begin{minipage}[c]{12cm}
+\begin{verbatim}
+[root@gont sources]# ./flock -f -w Flock.c
+Lock acquired
+\end{verbatim}
+\end{minipage}\vspace{1mm}
+\par\noindent
+che ci mostra come i due tipi di blocco siano assolutamente indipendenti; per
+questo motivo occorre sempre tenere presente quale fra le due semantiche
+disponibili stanno usando i programmi con cui si interagisce, dato che i
+blocchi applicati con l'altra non avrebbero nessun effetto.
+
+
+
+\subsection{La funzione \func{lockf}}
+\label{sec:file_lockf}
+
+Abbiamo visto come l'interfaccia POSIX per il \textit{file locking} sia molto
+più potente e flessibile di quella di BSD, questo comporta anche una maggiore
+complessità per via delle varie opzioni da passare a \func{fcntl}. Per questo
+motivo è disponibile anche una interfaccia semplificata (ripresa da System V)
+che utilizza la funzione \funcd{lockf}, il cui prototipo è:
+\begin{prototype}{sys/file.h}{int lockf(int fd, int cmd, off\_t len)}
- Esegue le operazioni di controllo di \textit{epoll}.
+ Applica, controlla o rimuove un \textit{file lock} sul file \param{fd}.
- \bodydesc{La funzione restituisce $0$ in caso di successo o $-1$ in caso di
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -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}
-}
+ \begin{errlist}
+ \item[\errcode{EWOULDBLOCK}] non è possibile acquisire il lock, e si è
+ selezionato \const{LOCK\_NB}, oppure l'operazione è proibita perché il
+ file è mappato in memoria.
+ \item[\errcode{ENOLCK}] il sistema non ha le risorse per il blocco: ci
+ sono troppi segmenti di \textit{lock} aperti, si è esaurita la tabella
+ dei \textit{file lock}.
+ \end{errlist}
+ ed inoltre \errval{EBADF}, \errval{EINVAL}.
+ }
\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.
+Il comportamento della funzione dipende dal valore dell'argomento \param{cmd},
+che specifica quale azione eseguire; i valori possibili sono riportati in
+tab.~\ref{tab:file_lockf_type}.
\begin{table}[htb]
\centering
\footnotesize
- \begin{tabular}[c]{|l|p{8cm}|}
+ \begin{tabular}[c]{|l|p{7cm}|}
\hline
- \textbf{Valore} & \textbf{Significato} \\
+ \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}.\\
+ \const{LOCK\_SH}& Richiede uno \textit{shared lock}. Più processi possono
+ mantenere un blocco condiviso sullo stesso file.\\
+ \const{LOCK\_EX}& Richiede un \textit{exclusive lock}. Un solo processo
+ alla volta può mantenere un blocco esclusivo su un file.\\
+ \const{LOCK\_UN}& Sblocca il file.\\
+ \const{LOCK\_NB}& Non blocca la funzione quando il blocco non è disponibile,
+ si specifica sempre insieme ad una delle altre operazioni
+ con un OR aritmetico dei valori.\\
\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}
+ \caption{Valori possibili per l'argomento \param{cmd} di \func{lockf}.}
+ \label{tab:file_lockf_type}
\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}.
+Qualora il blocco non possa essere acquisito, a meno di non aver specificato
+\const{LOCK\_NB}, la funzione si blocca fino alla disponibilità dello stesso.
+Dato che la funzione è implementata utilizzando \func{fcntl} la semantica
+delle operazioni è la stessa di quest'ultima (pertanto la funzione non è
+affatto equivalente a \func{flock}).
-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 un valore \texttt{NULL}.}
+\subsection{Il \textit{mandatory locking}}
+\label{sec:file_mand_locking}
-\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}
+\itindbeg{mandatory~locking|(}
-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 \textit{mandatory locking} è una opzione introdotta inizialmente in SVr4,
+per introdurre un \textit{file locking} che, come dice il nome, fosse
+effettivo indipendentemente dai controlli eseguiti da un processo. Con il
+\textit{mandatory locking} infatti è possibile far eseguire il blocco del file
+direttamente al sistema, così che, anche qualora non si predisponessero le
+opportune verifiche nei processi, questo verrebbe comunque rispettato.
-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}.
+Per poter utilizzare il \textit{mandatory locking} è stato introdotto un
+utilizzo particolare del bit \itindex{sgid~bit} \acr{sgid}. Se si ricorda
+quanto esposto in sez.~\ref{sec:file_special_perm}), esso viene di norma
+utilizzato per cambiare il group-ID effettivo con cui viene eseguito un
+programma, ed è pertanto sempre associato alla presenza del permesso di
+esecuzione per il gruppo. Impostando questo bit su un file senza permesso di
+esecuzione in un sistema che supporta il \textit{mandatory locking}, fa sì che
+quest'ultimo venga attivato per il file in questione. In questo modo una
+combinazione dei permessi originariamente non contemplata, in quanto senza
+significato, diventa l'indicazione della presenza o meno del \textit{mandatory
+ locking}.\footnote{un lettore attento potrebbe ricordare quanto detto in
+ sez.~\ref{sec:file_perm_management} e cioè che il bit \acr{sgid} viene
+ cancellato (come misura di sicurezza) quando di scrive su un file, questo
+ non vale quando esso viene utilizzato per attivare il \textit{mandatory
+ locking}.}
-\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}
+L'uso del \textit{mandatory locking} presenta vari aspetti delicati, dato che
+neanche l'amministratore può passare sopra ad un \textit{file lock}; pertanto
+un processo che blocchi un file cruciale può renderlo completamente
+inaccessibile, rendendo completamente inutilizzabile il sistema\footnote{il
+ problema si potrebbe risolvere rimuovendo il bit \itindex{sgid~bit}
+ \acr{sgid}, ma non è detto che sia così facile fare questa operazione con un
+ sistema bloccato.} inoltre con il \textit{mandatory locking} si può
+bloccare completamente un server NFS richiedendo una lettura su un file su cui
+è attivo un blocco. Per questo motivo l'abilitazione del \textit{mandatory
+ locking} è di norma disabilitata, e deve essere attivata filesystem per
+filesystem in fase di montaggio (specificando l'apposita opzione di
+\func{mount} riportata in tab.~\ref{tab:sys_mount_flags}, o con l'opzione
+\code{-o mand} per il comando omonimo).
-\footnotetext{questa modalità è disponibile solo a partire dal kernel 2.6.2.}
+Si tenga presente inoltre che il \textit{mandatory locking} funziona solo
+sull'interfaccia POSIX di \func{fcntl}. Questo ha due conseguenze: che non si
+ha nessun effetto sui \textit{file lock} richiesti con l'interfaccia di
+\func{flock}, e che la granularità del blocco è quella del singolo byte, come
+per \func{fcntl}.
-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.
+La sintassi di acquisizione dei blocchi è esattamente la stessa vista in
+precedenza per \func{fcntl} e \func{lockf}, la differenza è che in caso di
+\textit{mandatory lock} attivato non è più necessario controllare la
+disponibilità di accesso al file, ma si potranno usare direttamente le
+ordinarie funzioni di lettura e scrittura e sarà compito del kernel gestire
+direttamente il \textit{file locking}.
+
+Questo significa che in caso di \textit{read lock} la lettura dal file potrà
+avvenire normalmente con \func{read}, mentre una \func{write} si bloccherà
+fino al rilascio del blocco, a meno di non aver aperto il file con
+\const{O\_NONBLOCK}, nel qual caso essa ritornerà immediatamente con un errore
+di \errcode{EAGAIN}.
+
+Se invece si è acquisito un \textit{write lock} tutti i tentativi di leggere o
+scrivere sulla regione del file bloccata fermeranno il processo fino al
+rilascio del blocco, a meno che il file non sia stato aperto con
+\const{O\_NONBLOCK}, nel qual caso di nuovo si otterrà un ritorno immediato
+con l'errore di \errcode{EAGAIN}.
-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.
+Infine occorre ricordare che le funzioni di lettura e scrittura non sono le
+sole ad operare sui contenuti di un file, e che sia \func{creat} che
+\func{open} (quando chiamata con \const{O\_TRUNC}) effettuano dei cambiamenti,
+così come \func{truncate}, riducendone le dimensioni (a zero nei primi due
+casi, a quanto specificato nel secondo). Queste operazioni sono assimilate a
+degli accessi in scrittura e pertanto non potranno essere eseguite (fallendo
+con un errore di \errcode{EAGAIN}) su un file su cui sia presente un qualunque
+blocco (le prime due sempre, la terza solo nel caso che la riduzione delle
+dimensioni del file vada a sovrapporsi ad una regione bloccata).
-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}.
+L'ultimo aspetto della interazione del \textit{mandatory locking} con le
+funzioni di accesso ai file è quello relativo ai file mappati in memoria (che
+abbiamo trattato in sez.~\ref{sec:file_memory_map}); anche in tal caso
+infatti, quando si esegue la mappatura con l'opzione \const{MAP\_SHARED}, si
+ha un accesso al contenuto del file. Lo standard SVID prevede che sia
+impossibile eseguire il memory mapping di un file su cui sono presenti dei
+blocchi\footnote{alcuni sistemi, come HP-UX, sono ancora più restrittivi e lo
+ impediscono anche in caso di \textit{advisory locking}, anche se questo
+ comportamento non ha molto senso, dato che comunque qualunque accesso
+ diretto al file è consentito.} in Linux è stata però fatta la scelta
+implementativa\footnote{per i dettagli si possono leggere le note relative
+ all'implementazione, mantenute insieme ai sorgenti del kernel nel file
+ \file{Documentation/mandatory.txt}.} di seguire questo comportamento
+soltanto quando si chiama \func{mmap} con l'opzione \const{MAP\_SHARED} (nel
+qual caso la funzione fallisce con il solito \errcode{EAGAIN}) che comporta la
+possibilità di modificare il file.
-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}.}
+\index{file!locking|)}
-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}.
+\itindend{mandatory~locking|(}
-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}.
+\section{L'\textit{I/O multiplexing}}
+\label{sec:file_multiplexing}
-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 il valore $-1$, che indica
-un'attesa indefinita. L'argomento \param{maxevents} dovrà invece essere sempre
-un intero positivo.
+Uno dei problemi che si presentano quando si deve operare contemporaneamente
+su molti file usando le funzioni illustrate in
+cap.~\ref{cha:file_unix_interface} e cap.~\ref{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.
-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}, in questo modo il campo \var{data} consente di
-identificare il file descriptor.\footnote{ed è per questo che, come accennato,
- è consuetudine usare per \var{data} il valore del file descriptor stesso.}
-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 un file descriptor 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.
+\subsection{La problematica dell'\textit{I/O multiplexing}}
+\label{sec:file_noblocking}
-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.
-Questa condizione viene generalmente rilevata dall'occorrere di un errore di
-\errcode{EAGAIN} al ritorno di 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 questa non è la
-sola modalità possibile, ad esempio la condizione può essere riconosciuta
-anche con il fatto che sono stati restituiti meno dati di quelli richiesti.
+Abbiamo visto in sez.~\ref{sec:sig_gen_beha}, affrontando la suddivisione fra
+\textit{fast} e \textit{slow} system call,\index{system~call~lente} 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\index{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.
-Come le precedenti \func{select} e \func{poll}, le funzioni dell'interfaccia
-di \textit{epoll} vengono utilizzate prevalentemente con i server di rete,
-quando si devono tenere sotto osservazione un gran numero di socket; per
-questo motivo rimandiamo di nuovo la trattazione di un esempio concreto a
-quando avremo esaminato in dettaglio le caratteristiche dei socket, in
-particolare si potrà trovare un programma che utilizza questa interfaccia in
+Questo comportamento causa uno dei problemi più comuni che ci si trova ad
+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 \itindex{deadlock} \textit{deadlock}.
+
+Abbiamo già accennato in sez.~\ref{sec:file_open} che è possibile prevenire
+questo tipo di comportamento delle funzioni di I/O aprendo un file in
+\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 \itindex{polling} \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 superare questo problema è stato introdotto il concetto di \textit{I/O
+ 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 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 di utilizzo concreto in
sez.~\ref{sec:TCP_sock_multiplexing}.
-\itindend{epoll}
+\subsection{Le funzioni \func{select} e \func{pselect}}
+\label{sec:file_select}
+Il primo kernel unix-like 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 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}
+ \headdecl{unistd.h}
+ \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
+ 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{ndfs} un valore negativo
+ o un valore non valido per \param{timeout}.
+ \end{errlist}
+ ed inoltre \errval{ENOMEM}.
+}
+\end{functions}
+La funzione mette il processo in stato di \textit{sleep} (vedi
+tab.~\ref{tab:proc_proc_states}) fintanto che almeno uno dei file descriptor
+degli insiemi specificati (\param{readfds}, \param{writefds} e
+\param{exceptfds}), non diventa attivo, per un tempo massimo specificato da
+\param{timeout}.
-\section{L'accesso \textsl{asincrono} ai file}
-\label{sec:file_asyncronous_access}
+\itindbeg{file~descriptor~set}
-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 in cui si
-debba operare su più file contemporaneamente, esistono altre modalità di
-gestione delle stesse problematiche. In particolare sono importanti in questo
-contesto le modalità di accesso ai file eseguibili in maniera
-\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, ma esistono anche altre interfacce, come \itindex{inotify}
-\textit{inotify}), per essere avvisato della possibilità di eseguire le
-operazioni di I/O volute.
+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 \itindex{signal~set} \textit{signal set} (vedi
+sez.~\ref{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/time.h}
+ \headdecl{sys/types.h}
+ \headdecl{unistd.h}
+ \funcdecl{void \macro{FD\_ZERO}(fd\_set *set)}
+ Inizializza l'insieme (vuoto).
+ \funcdecl{void \macro{FD\_SET}(int fd, fd\_set *set)}
+ Inserisce il file descriptor \param{fd} nell'insieme.
-\subsection{Il \textit{Signal driven I/O}}
-\label{sec:file_asyncronous_operation}
+ \funcdecl{void \macro{FD\_CLR}(int fd, fd\_set *set)}
+ 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.
+\end{functions}
-\itindbeg{signal~driven~I/O}
+In genere un \textit{file descriptor set} può contenere fino ad un massimo di
+\const{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 da
+quando, come nelle versioni più recenti del kernel, questo limite è stato
+rimosso, esso indica le dimensioni massime dei numeri usati nei \textit{file
+ descriptor set}.\footnote{il suo valore, secondo lo standard POSIX
+ 1003.1-2001, è definito in \file{sys/select.h}, ed è pari a 1024.}
-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}).
+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; 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.
-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.}
+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}; 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à di 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
+sez.~\ref{sec:TCP_urgent_data}).
-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.
+Dato che in genere non si tengono mai sotto controllo fino a
+\const{FD\_SETSIZE} file contemporaneamente la funzione richiede di
+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{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.}
-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.
+Infine l'argomento \param{timeout}, espresso con una struttura di tipo
+\struct{timeval} (vedi fig.~\ref{fig:sys_timeval_struct}) 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.
-Tuttavia con l'implementazione classica dei segnali questa modalità di I/O
-presenta notevoli problemi, dato che non è possibile determinare, quando i
-file descriptor sono più di uno, qual è quello responsabile dell'emissione del
-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.
+La funzione restituisce il numero di file descriptor pronti,\footnote{questo è
+ il comportamento previsto dallo standard, ma la standardizzazione della
+ funzione è recente, ed esistono ancora alcune versioni di Unix che non si
+ comportano in questo modo.} e ciascun insieme viene sovrascritto per
+indicare quali sono i file descriptor pronti per le operazioni ad esso
+relative, in modo da poterli controllare con \macro{FD\_ISSET}. Se invece si
+ha un timeout viene restituito un valore nullo e gli insiemi non vengono
+modificati. In caso di errore la funzione restituisce -1, ed i valori dei tre
+insiemi sono indefiniti e non si può fare nessun affidamento sul loro
+contenuto.
-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}).
+\itindend{file~descriptor~set}
-Per far questo però occorre utilizzare le funzionalità dei segnali real-time
-(vedi sez.~\ref{sec:sig_real_time}) impostando esplicitamente con il comando
-\const{F\_SETSIG} di \func{fcntl} un segnale real-time da inviare in caso di
-I/O asincrono (il segnale predefinito è \const{SIGIO}). In questo caso il
-gestore, tutte le volte che riceverà \const{SI\_SIGIO} come valore del campo
-\var{si\_code}\footnote{il valore resta \const{SI\_SIGIO} qualunque sia il
- segnale che si è associato all'I/O, ed indica appunto che il segnale è stato
- generato a causa di attività di I/O.} di \struct{siginfo\_t}, troverà nel
-campo \var{si\_fd} il valore del file descriptor che ha generato il segnale.
+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.
-Un secondo vantaggio dell'uso dei segnali real-time è che essendo questi
-ultimi dotati di una coda di consegna ogni segnale sarà associato ad uno solo
-file descriptor; inoltre sarà possibile stabilire delle priorità nella
-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.
+In Linux \func{select} modifica anche il valore di \param{timeout},
+impostandolo al tempo restante, quando la funzione viene interrotta da un
+segnale. In tal caso infatti si ha un errore di \errcode{EINTR}, ed occorre
+rilanciare la funzione; in questo modo non è necessario ricalcolare tutte le
+volte il tempo rimanente. Questo può causare problemi di portabilità sia
+quando si usa 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.\footnote{in
+ genere questa caratteristica è disponibile nei sistemi che derivano da
+ System V e non è disponibile per quelli che derivano da BSD; lo standard
+ POSIX.1-2001 non permette questo comportamento.}
-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
- \procfile{/proc/sys/kernel/rtsig-max} allo stesso valore del contenuto di
- \procfile{/proc/sys/fs/file-max}.}
+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{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.
-% TODO fare esempio che usa O_ASYNC
+Inoltre c'è anche il problema che il numero massimo dei file che si possono
+tenere sotto controllo, la funzione è nata quando il kernel consentiva un
+numero massimo di 1024 file descriptor per processo, adesso che il numero può
+essere arbitrario 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.
-\itindend{signal~driven~I/O}
+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{ndfs} 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 fig.~\ref{fig:sys_timespec_struct}) per
+indicare con maggiore precisione il timeout e non ne aggiorna il valore in
+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.
+L'uso di \param{sigmask} è stato introdotto allo scopo di prevenire possibili
+\textit{race condition} \itindex{race~condition} quando ci si deve porre in
+attesa sia di un segnale che di dati. La tecnica classica è quella di
+utilizzare il gestore per impostare una variabile globale e controllare questa
+nel corpo principale del programma; abbiamo visto in
+sez.~\ref{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.
-\subsection{I meccanismi di notifica asincrona.}
-\label{sec:file_asyncronous_lease}
+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 \itindex{race~condition} \textit{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.
-Una delle domande più frequenti nella programmazione in ambiente unix-like è
-quella di come fare a sapere quando un file viene modificato. La
-risposta\footnote{o meglio la non risposta, tanto che questa nelle Unix FAQ
- \cite{UnixFAQ} viene anche chiamata una \textit{Frequently Unanswered
- Question}.} è che nell'architettura classica di Unix questo non è
-possibile. Al contrario di altri sistemi operativi infatti un kernel unix-like
-classico non prevedeva alcun meccanismo per cui un processo possa essere
-\textsl{notificato} di eventuali modifiche avvenute su un file. Questo è il
-motivo per cui i demoni devono essere \textsl{avvisati} in qualche
-modo\footnote{in genere questo vien fatto inviandogli un segnale di
- \const{SIGHUP} che, per una convenzione adottata dalla gran parte di detti
- programmi, causa la rilettura della configurazione.} se il loro file di
-configurazione è stato modificato, perché possano rileggerlo e riconoscere le
-modifiche.
+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 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} 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.
-Questa scelta è stata fatta perché provvedere un simile meccanismo a livello
-generico per qualunque file comporterebbe un notevole aumento di complessità
-dell'architettura della gestione dei file, il tutto per fornire una
-funzionalità che serve soltanto in alcuni casi particolari. Dato che
-all'origine di Unix i soli programmi che potevano avere una tale esigenza
-erano i demoni, attenendosi a uno dei criteri base della progettazione, che
-era di far fare al kernel solo le operazioni strettamente necessarie e
-lasciare tutto il resto a processi in user space, non era stata prevista
-nessuna funzionalità di notifica.
-Visto però il crescente interesse nei confronti di una funzionalità di questo
-tipo, che è molto richiesta specialmente nello sviluppo dei programmi ad
-interfaccia grafica, quando si deve presentare all'utente lo stato del
-filesystem, sono state successivamente introdotte delle estensioni che
-permettessero la creazione di meccanismi di notifica più efficienti dell'unica
-soluzione disponibile con l'interfaccia tradizionale, che è quella del
-\itindex{polling} \textit{polling}.
+\subsection{Le funzioni \func{poll} e \func{ppoll}}
+\label{sec:file_poll}
-Queste nuove funzionalità sono delle estensioni specifiche, non
-standardizzate, che sono disponibili soltanto su Linux (anche se altri kernel
-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}.
+Nello sviluppo di System V, invece di utilizzare l'interfaccia di
+\func{select}, che è una estensione tipica di BSD, è stata introdotta un'altra
+interfaccia, 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 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 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}
-\index{file!lease|(}
+La funzione permette di tenere sotto controllo contemporaneamente \param{ndfs}
+file descriptor, specificati attraverso il puntatore \param{ufds} ad un
+vettore di strutture \struct{pollfd}. Come con \func{select} si può
+interrompere l'attesa dopo un certo tempo, questo deve essere specificato con
+l'argomento \param{timeout} in numero di millisecondi: un valore negativo
+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}).
-La prima di queste funzionalità è quella del cosiddetto \textit{file lease};
-questo è un meccanismo che consente ad un processo, detto \textit{lease
- holder}, di essere notificato quando un altro processo, chiamato a sua volta
-\textit{lease breaker}, cerca di eseguire una \func{open} o una
-\func{truncate} sul file del quale l'\textit{holder} detiene il
-\textit{lease}.
-La notifica avviene in maniera analoga a come illustrato in precedenza per
-l'uso di \const{O\_ASYNC}: di default viene inviato al \textit{lease holder}
-il segnale \const{SIGIO}, ma questo segnale può essere modificato usando il
-comando \const{F\_SETSIG} di \func{fcntl}.\footnote{anche in questo caso si
- può rispecificare lo stesso \const{SIGIO}.} Se si è fatto questo\footnote{è
- in genere è opportuno farlo, come in precedenza, per utilizzare segnali
- real-time.} e si è installato il gestore del segnale con \const{SA\_SIGINFO}
-si riceverà nel campo \var{si\_fd} della struttura \struct{siginfo\_t} il
-valore del file descriptor del file sul quale è stato compiuto l'accesso; in
-questo modo un processo può mantenere anche più di un \textit{file lease}.
+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},
+prevede tre campi: in \var{fd} deve essere indicato il numero del file
+descriptor da controllare, in \var{events} deve essere specificata una
+maschera binaria di flag che indichino il tipo di evento che si vuole
+controllare, mentre in \var{revents} il kernel restituirà il relativo
+risultato. Usando un valore negativo per \param{fd} la corrispondente
+struttura sarà ignorata da \func{poll}. Dato che i dati in ingresso sono del
+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.
-Esistono due tipi di \textit{file lease}: di lettura (\textit{read lease}) e
-di scrittura (\textit{write lease}). Nel primo caso la notifica avviene quando
-un altro processo esegue l'apertura del file in scrittura o usa
-\func{truncate} per troncarlo. Nel secondo caso la notifica avviene anche se
-il file viene aperto il lettura; in quest'ultimo caso però il \textit{lease}
-può essere ottenuto solo se nessun altro processo ha aperto lo stesso file.
+\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}
-Come accennato in sez.~\ref{sec:file_fcntl} il comando di \func{fcntl} che
-consente di acquisire un \textit{file lease} è \const{F\_SETLEASE}, che viene
-utilizzato anche per rilasciarlo. In tal caso il file descriptor \param{fd}
-passato a \func{fcntl} servirà come riferimento per il file su cui si vuole
-operare, mentre per indicare il tipo di operazione (acquisizione o rilascio)
-occorrerà specificare come valore dell'argomento \param{arg} di \func{fcntl}
-uno dei tre valori di tab.~\ref{tab:file_lease_fctnl}.
+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
+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
\footnotesize
\begin{tabular}[c]{|l|l|}
\hline
- \textbf{Valore} & \textbf{Significato} \\
+ \textbf{Flag} & \textbf{Significato} \\
\hline
\hline
- \const{F\_RDLCK} & Richiede un \textit{read lease}.\\
- \const{F\_WRLCK} & Richiede un \textit{write lease}.\\
- \const{F\_UNLCK} & Rilascia un \textit{file lease}.\\
+ \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 \itindex{out-of-band} 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{POLLRDHUP} & Si è avuta una \textsl{half-close} su un
+ socket.\footnotemark\\
+ \const{POLLNVAL} & Il file descriptor non è aperto.\\
+ \hline
+ \const{POLLMSG} & Definito per compatibilità con SysV.\\
\hline
\end{tabular}
- \caption{Costanti per i tre possibili valori dell'argomento \param{arg} di
- \func{fcntl} quando usata con i comandi \const{F\_SETLEASE} e
- \const{F\_GETLEASE}.}
- \label{tab:file_lease_fctnl}
+ \caption{Costanti per l'identificazione dei vari bit dei campi
+ \var{events} e \var{revents} di \struct{pollfd}.}
+ \label{tab:file_pollfd_flags}
\end{table}
-Se invece si vuole conoscere lo stato di eventuali \textit{file lease}
-occorrerà chiamare \func{fcntl} sul relativo file descriptor \param{fd} con il
-comando \const{F\_GETLEASE}, e si otterrà indietro nell'argomento \param{arg}
-uno dei valori di tab.~\ref{tab:file_lease_fctnl}, che indicheranno la
-presenza del rispettivo tipo di \textit{lease}, o, nel caso di
-\const{F\_UNLCK}, l'assenza di qualunque \textit{file lease}.
+\footnotetext{si tratta di una estensione specifica di Linux, disponibile a
+ partire dal kernel 2.6.17 definendo la marco \macro{\_GNU\_SOURCE}, che
+ consente di riconoscere la chiusura in scrittura dell'altro capo di un
+ socket, situazione che si viene chiamata appunto \itindex{half-close}
+ \textit{half-close} (\textsl{mezza chiusura}) su cui torneremo con maggiori
+ dettagli in sez.~\ref{sec:TCP_shutdown}.}
-Si tenga presente che un processo può mantenere solo un tipo di \textit{lease}
-su un file, e che un \textit{lease} può essere ottenuto solo su file di dati
-(pipe e dispositivi sono quindi esclusi). Inoltre un processo non privilegiato
-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}, vedi sez.~\ref{sec:proc_capabilities}) può acquisire
-\textit{lease} su qualunque file.
+Il valore \const{POLLMSG} non viene utilizzato ed è definito solo per
+compatibilità con l'implementazione di SysV che usa gli
+\textit{stream};\footnote{essi 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 di alcune
+costanti, in quanto per essi sono definite tre classi di dati:
+\textsl{normali}, \textit{prioritari} ed \textit{urgenti}. In Linux la
+distinzione ha senso solo per i dati urgenti \itindex{out-of-band} dei socket
+(vedi sez.~\ref{sec:TCP_urgent_data}), ma su questo e su come \func{poll}
+reagisce alle varie condizioni dei socket torneremo in
+sez.~\ref{sec:TCP_serv_poll}, dove vedremo anche un esempio del suo utilizzo.
-Se su un file è presente un \textit{lease} quando il \textit{lease breaker}
-esegue una \func{truncate} o una \func{open} che confligge con
-esso,\footnote{in realtà \func{truncate} confligge sempre, mentre \func{open},
- se eseguita in sola lettura, non confligge se si tratta di un \textit{read
- lease}.} la funzione si blocca\footnote{a meno di non avere aperto il file
- con \const{O\_NONBLOCK}, nel qual caso \func{open} fallirebbe con un errore
- di \errcode{EWOULDBLOCK}.} e viene eseguita la notifica al \textit{lease
- holder}, così che questo possa completare le sue operazioni sul file e
-rilasciare il \textit{lease}. In sostanza con un \textit{read lease} si
-rilevano i tentativi di accedere al file per modificarne i dati da parte di un
-altro processo, mentre con un \textit{write lease} si rilevano anche i
-tentativi di accesso in lettura. Si noti comunque che le operazioni di
-notifica avvengono solo in fase di apertura del file e non sulle singole
-operazioni di lettura e scrittura.
+Si tenga conto comunque che le costanti relative ai diversi tipi di dati
+normali e prioritari, vale a dire \const{POLLRDNORM}, \const{POLLWRNORM},
+\const{POLLRDBAND} e \const{POLLWRBAND} fanno riferimento alle implementazioni
+in stile SysV (in particolare le ultime due non vengono usate su Linux), e
+sono utilizzabili soltanto qualora si sia definita la macro
+\macro{\_XOPEN\_SOURCE}.\footnote{e ci si ricordi di farlo sempre in testa al
+ file, definirla soltanto prima di includere \file{sys/poll.h} non è
+ sufficiente.}
-L'utilizzo dei \textit{file lease} consente al \textit{lease holder} di
-assicurare la consistenza di un file, a seconda dei due casi, prima che un
-altro processo inizi con le sue operazioni di scrittura o di lettura su di
-esso. In genere un \textit{lease holder} che riceve una notifica deve
-provvedere a completare le necessarie operazioni (ad esempio scaricare
-eventuali buffer), per poi rilasciare il \textit{lease} così che il
-\textit{lease breaker} possa eseguire le sue operazioni. Questo si fa con il
-comando \const{F\_SETLEASE}, o rimuovendo il \textit{lease} con
-\const{F\_UNLCK}, o, nel caso di \textit{write lease} che confligge con una
-operazione di lettura, declassando il \textit{lease} a lettura con
-\const{F\_RDLCK}.
+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 tab.~\ref{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}.
-Se il \textit{lease holder} non provvede a rilasciare il \textit{lease} entro
-il numero di secondi specificato dal parametro di sistema mantenuto in
-\procfile{/proc/sys/fs/lease-break-time} sarà il kernel stesso a rimuoverlo (o
-declassarlo) automaticamente.\footnote{questa è una misura di sicurezza per
- evitare che un processo blocchi indefinitamente l'accesso ad un file
- acquisendo un \textit{lease}.} Una volta che un \textit{lease} è stato
-rilasciato o declassato (che questo sia fatto dal \textit{lease holder} o dal
-kernel è lo stesso) le chiamate a \func{open} o \func{truncate} eseguite dal
-\textit{lease breaker} rimaste bloccate proseguono automaticamente.
+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}.
-\index{file!dnotify|(}
+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}.
-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 o una
-directory vengono modificati, che è quanto necessario ad esempio ai programma
-di gestione dei file dei vari desktop grafici.
+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}
-Per risolvere questo problema a partire dal kernel 2.4 è stata allora creata
-un'altra interfaccia,\footnote{si ricordi che anche questa è una interfaccia
- specifica di Linux che deve essere evitata se si vogliono scrivere programmi
- portabili, e che le funzionalità illustrate sono disponibili soltanto se è
- stata definita la macro \macro{\_GNU\_SOURCE}.} chiamata \textit{dnotify},
-che consente di richiedere una notifica quando una directory, o 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 se ne
-può utilizzare un altro.\footnote{e di nuovo, per le ragioni già esposte in
- precedenza, è opportuno che si utilizzino dei segnali real-time.} Inoltre,
-come in precedenza, si potrà ottenere nel gestore del segnale il file
-descriptor che è stato modificato tramite il contenuto della struttura
-\struct{siginfo\_t}.
+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}
-\index{file!lease|)}
+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. Come nel caso di \func{pselect} la system
+call che implementa \func{ppoll} restituisce, se la funzione viene interrotta
+da un segnale, il tempo mancante in \param{timeout}, e come per \func{pselect}
+la funzione di libreria fornita dalle \acr{glibc} maschera questo
+comportamento non modificando mai il valore di \param{timeout}.\footnote{anche
+ se in questo caso non esiste nessuno standard che richiede questo
+ comportamento.}
-\begin{table}[htb]
- \centering
- \footnotesize
- \begin{tabular}[c]{|l|p{8cm}|}
- \hline
- \textbf{Valore} & \textbf{Significato} \\
- \hline
- \hline
- \const{DN\_ACCESS} & Un file è stato acceduto, con l'esecuzione di una fra
- \func{read}, \func{pread}, \func{readv}.\\
- \const{DN\_MODIFY} & Un file è stato modificato, con l'esecuzione di una
- fra \func{write}, \func{pwrite}, \func{writev},
- \func{truncate}, \func{ftruncate}.\\
- \const{DN\_CREATE} & È stato creato un file nella directory, con
- l'esecuzione di una fra \func{open}, \func{creat},
- \func{mknod}, \func{mkdir}, \func{link},
- \func{symlink}, \func{rename} (da un'altra
- directory).\\
- \const{DN\_DELETE} & È stato cancellato un file dalla directory con
- l'esecuzione di una fra \func{unlink}, \func{rename}
- (su un'altra directory), \func{rmdir}.\\
- \const{DN\_RENAME} & È stato rinominato un file all'interno della
- directory (con \func{rename}).\\
- \const{DN\_ATTRIB} & È stato modificato un attributo di un file con
- l'esecuzione di una fra \func{chown}, \func{chmod},
- \func{utime}.\\
- \const{DN\_MULTISHOT}& Richiede una notifica permanente di tutti gli
- eventi.\\
- \hline
- \end{tabular}
- \caption{Le costanti che identificano le varie classi di eventi per i quali
- si richiede la notifica con il comando \const{F\_NOTIFY} di \func{fcntl}.}
- \label{tab:file_notify}
-\end{table}
-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}.
+\subsection{L'interfaccia di \textit{epoll}}
+\label{sec:file_epoll}
-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.
+\itindbeg{epoll}
-\index{file!inotify|(}
+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à.
-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 avere un eccesso di file aperti. Inoltre
-quando la directory che si controlla è all'interno di un dispositivo
-rimovibile, mantenere il relativo file descriptor aperto comporta
-l'impossibilità di smontare il dispositivo e di rimuoverlo, il che in genere
-complica notevolmente la gestione dell'uso di questi dispositivi.
+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.
-Un altro problema è che l'interfaccia di \textit{dnotify} consente solo di
-tenere sotto controllo il contenuto di una directory; la modifica di un file
-viene segnalata, ma poi è necessario verificare di quale file si tratta
-(operazione che può essere molto onerosa quando una directory contiene un gran
-numero di file). 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_adv_control}. Per tutta questa serie di motivi in generale
-quella di \textit{dnotify} viene considerata una interfaccia di usabilità
-problematica.
+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à.
-\index{file!dnotify|)}
+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}.
-Per risolvere i problemi appena illustrati è stata introdotta una nuova
-interfaccia per l'osservazione delle modifiche a file o directory, chiamata
-\textit{inotify}.\footnote{l'interfaccia è disponibile a partire dal kernel
- 2.6.13, le relative funzioni sono state introdotte nelle glibc 2.4.} Anche
-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, il che permette
-di risolvere 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)}
+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)}
- Inizializza una istanza di \textit{inotify}.
+ 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{EMFILE}] si è raggiunto il numero massimo di istanze di
- \textit{inotify} consentite all'utente.
+ \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
}
\end{prototype}
-La funzione non prende alcun argomento; inizializza una istanza di
-\textit{inotify} e restituisce un file descriptor attraverso il quale verranno
-effettuate le operazioni di notifica;\footnote{per evitare abusi delle risorse
- di sistema è previsto che un utente possa utilizzare un numero limitato di
- istanze di \textit{inotify}; il valore di default del limite è di 128, ma
- questo valore può essere cambiato con \func{sysctl} o usando il file
- \procfile{/proc/sys/fs/inotify/max\_user\_instances}.} si tratta di un file
-descriptor speciale che non è associato a nessun file su disco, e che viene
-utilizzato solo per notificare gli eventi che sono stati posti in
-osservazione. Dato che questo file descriptor non è associato a nessun file o
-directory reale, l'inconveniente di non poter smontare un filesystem i cui
-file sono tenuti sotto osservazione viene completamente
-eliminato.\footnote{anzi, una delle capacità 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
-con l'interfaccia di \textit{epoll};\footnote{ed a partire dal kernel 2.6.25 è
- stato introdotto anche il supporto per il \itindex{signal~driven~I/O}
- \texttt{signal-driven I/O} trattato in
- sez.~\ref{sec:file_asyncronous_operation}.} siccome gli eventi vengono
-notificati come dati disponibili in lettura, dette funzioni ritorneranno tutte
-le volte che si avrà un evento di notifica. Così, invece di dover utilizzare i
-segnali,\footnote{considerati una pessima scelta dal punto di vista
- dell'interfaccia utente.} si potrà gestire l'osservazione degli eventi con
-una qualunque delle modalità di \textit{I/O multiplexing} illustrate in
-sez.~\ref{sec:file_multiplexing}. Qualora si voglia cessare l'osservazione,
-sarà sufficiente chiudere il file descriptor e tutte le risorse allocate
-saranno automaticamente rilasciate.
-
-Infine l'interfaccia di \textit{inotify} consente di mettere sotto
-osservazione, oltre che una directory, anche singoli file. Una volta creata
-la coda di notifica si devono definire gli eventi da tenere sotto
-osservazione; questo viene fatto attraverso una \textsl{lista di osservazione}
-(o \textit{watch list}) che è 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}.
+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.
- \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:
+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{EACCESS}] non si ha accesso in lettura al file indicato.
- \item[\errcode{EINVAL}] \param{mask} non contiene eventi legali o \param{fd}
- non è un file descriptor 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{osservatore}'' (il cosiddetto
-``\textit{watch}'') nella lista di osservazione di una coda di notifica, che
-deve essere indicata specificando il file descriptor ad essa associato
-nell'argomento \param{fd}.\footnote{questo ovviamente dovrà essere un file
- descriptor creato con \func{inotify\_init}.} Il file o la directory da
-porre sotto osservazione vengono invece indicati per nome, da passare
-nell'argomento \param{pathname}. Infine il terzo argomento, \param{mask},
-indica che tipo di eventi devono essere tenuti sotto osservazione e le
-modalità della stessa. L'operazione può essere ripetuta per tutti i file e le
-directory che si vogliono tenere sotto osservazione,\footnote{anche in questo
- caso c'è un limite massimo che di default è pari a 8192, ed anche questo
- valore può essere cambiato con \func{sysctl} o usando il file
- \procfile{/proc/sys/fs/inotify/max\_user\_watches}.} e si utilizzerà sempre
-un solo file descriptor.
-
-Il tipo di evento che si vuole osservare deve essere specificato
-nell'argomento \param{mask} come maschera binaria, combinando i valori delle
-costanti riportate in tab.~\ref{tab:inotify_event_watch} che identificano i
-singoli bit della maschera ed il relativo significato. 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. Nella seconda
-parte della tabella si sono poi indicate alcune combinazioni predefinite dei
-flag della prima parte.
-
-\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 (o sugli attributi
- estesi, vedi
- sez.~\ref{sec:file_xattr}).\\
- \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
- \const{IN\_CLOSE} & & Combinazione di
- \const{IN\_CLOSE\_WRITE} e
- \const{IN\_CLOSE\_NOWRITE}.\\
- \const{IN\_MOVE} & & Combinazione di
- \const{IN\_MOVED\_FROM} e
- \const{IN\_MOVED\_TO}.\\
- \const{IN\_ALL\_EVENTS} & & Combinazione di tutti i flag
- possibili.\\
- \hline
- \end{tabular}
- \caption{Le costanti che identificano i bit della maschera binaria
- dell'argomento \param{mask} di \func{inotify\_add\_watch} che indicano il
- tipo di evento da tenere sotto osservazione.}
- \label{tab:inotify_event_watch}
-\end{table}
+ \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}
-Oltre ai flag di tab.~\ref{tab:inotify_event_watch}, che indicano il tipo di
-evento da osservare e che vengono utilizzati anche in uscita per indicare il
-tipo di evento avvenuto, \func{inotify\_add\_watch} supporta ulteriori
-flag,\footnote{i flag \const{IN\_DONT\_FOLLOW}, \const{IN\_MASK\_ADD} e
- \const{IN\_ONLYDIR} sono stati introdotti a partire dalle glibc 2.5, se si
- usa la versione 2.4 è necessario definirli a mano.} riportati in
-tab.~\ref{tab:inotify_add_watch_flag}, che indicano le modalità di
-osservazione (da passare sempre nell'argomento \param{mask}) e che al
-contrario dei precedenti non vengono mai impostati nei risultati in uscita.
+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{10cm}|}
+ \begin{tabular}[c]{|l|p{8cm}|}
\hline
\textbf{Valore} & \textbf{Significato} \\
\hline
\hline
- \const{IN\_DONT\_FOLLOW}& Non dereferenzia \param{pathname} se questo è un
- link simbolico.\\
- \const{IN\_MASK\_ADD} & Aggiunge a quelli già impostati i flag indicati
- nell'argomento \param{mask}, invece di
- sovrascriverli.\\
- \const{IN\_ONESHOT} & Esegue l'osservazione su \param{pathname} per una
- sola volta, rimuovendolo poi dalla \textit{watch
- list}.\\
- \const{IN\_ONLYDIR} & Se \param{pathname} è una directory riporta
- soltanto gli eventi ad essa relativi e non
- quelli per i file che contiene.\\
+ \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{Le costanti che identificano i bit della maschera binaria
- dell'argomento \param{mask} di \func{inotify\_add\_watch} che indicano le
- modalità di osservazione.}
- \label{tab:inotify_add_watch_flag}
+ \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}
-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, a meno che
-non si sia usato il flag \const{IN\_MASK\_ADD}, nel qual caso gli eventi
-specificati saranno aggiunti a quelli già presenti.
-
-Come accennato quando si tiene sotto osservazione una directory vengono
-restituite le informazioni sia riguardo alla directory stessa che ai file che
-essa contiene; questo comportamento può essere disabilitato utilizzando il
-flag \const{IN\_ONLYDIR}, che richiede di riportare soltanto gli eventi
-relativi alla directory stessa. Si tenga presente inoltre che quando si
-osserva una directory vengono riportati solo gli eventi sui file che essa
-contiene direttamente, non quelli relativi a file contenuti in eventuali
-sottodirectory; se si vogliono osservare anche questi sarà necessario creare
-ulteriori \textit{watch} per ciascuna sottodirectory.
-
-Infine usando il flag \const{IN\_ONESHOT} è possibile richiedere una notifica
-singola;\footnote{questa funzionalità però è disponibile soltanto a partire dal
- kernel 2.6.16.} una volta verificatosi uno qualunque fra gli eventi
-richiesti con \func{inotify\_add\_watch} l'\textsl{osservatore} verrà
-automaticamente rimosso dalla lista di osservazione e nessun ulteriore evento
-sarà più notificato.
-
-In caso di successo \func{inotify\_add\_watch} ritorna un intero positivo,
-detto \textit{watch descriptor}, che identifica univocamente un
-\textsl{osservatore} su una coda di notifica; esso viene usato per farvi
-riferimento sia riguardo i risultati restituiti da \textit{inotify}, che per
-la eventuale rimozione dello stesso.
-
-La seconda funzione per la gestione delle code di notifica, che permette di
-rimuovere un \textsl{osservatore}, è \funcd{inotify\_rm\_watch}, ed il suo
-prototipo è:
-\begin{prototype}{sys/inotify.h}
- {int inotify\_rm\_watch(int fd, uint32\_t wd)}
+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}.
- Rimuove un \textsl{osservatore} da una coda di notifica.
-
- \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}
+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 un valore \texttt{NULL}.}
-La funzione rimuove dalla coda di notifica identificata dall'argomento
-\param{fd} l'osservatore identificato dal \textit{watch descriptor}
-\param{wd};\footnote{ovviamente deve essere usato per questo argomento un
- valore ritornato da \func{inotify\_add\_watch}, altrimenti si avrà un errore
- di \errval{EINVAL}.} in caso di successo della rimozione, contemporaneamente
-alla cancellazione dell'osservatore, sulla coda di notifica verrà generato un
-evento di tipo \const{IN\_IGNORED} (vedi
-tab.~\ref{tab:inotify_read_event_flag}). Si tenga presente che se un file
-viene cancellato o un filesystem viene smontato i relativi osservatori vengono
-rimossi automaticamente e non è necessario utilizzare
-\func{inotify\_rm\_watch}.
-Come accennato l'interfaccia di \textit{inotify} prevede che gli eventi siano
-notificati come dati presenti in lettura sul file descriptor associato alla
-coda di notifica. Una applicazione pertanto dovrà leggere i dati da detto file
-con una \func{read}, che ritornerà sul buffer i dati presenti nella forma di
-una o più strutture di tipo \struct{inotify\_event} (la cui definizione è
-riportata in fig.~\ref{fig:inotify_event}). Qualora non siano presenti dati la
-\func{read} si bloccherà (a meno di non aver impostato il file descriptor in
-modalità non bloccante) fino all'arrivo di almeno un evento.
\begin{figure}[!htb]
\footnotesize \centering
\begin{minipage}[c]{15cm}
- \includestruct{listati/inotify_event.h}
+ \includestruct{listati/epoll_event.h}
\end{minipage}
\normalsize
- \caption{La struttura \structd{inotify\_event} usata dall'interfaccia di
- \textit{inotify} per riportare gli eventi.}
- \label{fig:inotify_event}
+ \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}
-Una ulteriore caratteristica dell'interfaccia di \textit{inotify} è che essa
-permette di ottenere con \func{ioctl}, come per i file descriptor associati ai
-socket (si veda sez.~\ref{sec:sock_ioctl_IP}) il numero di byte disponibili in
-lettura sul file descriptor, utilizzando su di esso l'operazione
-\const{FIONREAD}.\footnote{questa è una delle operazioni speciali per i file
- (vedi sez.~\ref{sec:file_ioctl}), che è disponibile solo per i socket e per
- i file descriptor creati con \func{inotify\_init}.} Si può così utilizzare
-questa operazione, oltre che per predisporre una operazione di lettura con un
-buffer di dimensioni adeguate, anche per ottenere rapidamente il numero di
-file che sono cambiati.
+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}.
-Una volta effettuata la lettura con \func{read} a ciascun evento sarà
-associata una struttura \struct{inotify\_event} contenente i rispettivi dati.
-Per identificare a quale file o directory l'evento corrisponde viene
-restituito nel campo \var{wd} il \textit{watch descriptor} con cui il relativo
-osservatore è stato registrato. Il campo \var{mask} contiene invece una
-maschera di bit che identifica il tipo di evento verificatosi; in essa
-compariranno sia i bit elencati nella prima parte di
-tab.~\ref{tab:inotify_event_watch}, che gli eventuali valori
-aggiuntivi\footnote{questi compaiono solo nel campo \var{mask} di
- \struct{inotify\_event}, e non utilizzabili in fase di registrazione
- dell'osservatore.} di tab.~\ref{tab:inotify_read_event_flag}.
+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{10cm}|}
+ \begin{tabular}[c]{|l|p{8cm}|}
\hline
\textbf{Valore} & \textbf{Significato} \\
\hline
\hline
- \const{IN\_IGNORED} & L'osservatore è stato rimosso, sia in maniera
- esplicita con l'uso di \func{inotify\_rm\_watch},
- che in maniera implicita per la rimozione
- dell'oggetto osservato o per lo smontaggio del
- filesystem su cui questo si trova.\\
- \const{IN\_ISDIR} & L'evento avvenuto fa riferimento ad una directory
- (consente così di distinguere, quando si pone
- sotto osservazione una directory, fra gli eventi
- relativi ad essa e quelli relativi ai file che
- essa contiene).\\
- \const{IN\_Q\_OVERFLOW}& Si sono eccedute le dimensioni della coda degli
- eventi (\textit{overflow} della coda); in questo
- caso il valore di \var{wd} è $-1$.\footnotemark\\
- \const{IN\_UNMOUNT} & Il filesystem contenente l'oggetto posto sotto
- osservazione è stato smontato.\\
+ \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{Le costanti che identificano i bit aggiuntivi usati nella maschera
- binaria del campo \var{mask} di \struct{inotify\_event}.}
- \label{tab:inotify_read_event_flag}
+ \caption{Costanti che identificano i bit del campo \param{events} di
+ \struct{epoll\_event}.}
+ \label{tab:epoll_events}
\end{table}
-\footnotetext{la coda di notifica ha una dimensione massima specificata dal
- parametro di sistema \procfile{/proc/sys/fs/inotify/max\_queued\_events} che
- indica il numero massimo di eventi che possono essere mantenuti sulla
- stessa; quando detto valore viene ecceduto gli ulteriori eventi vengono
- scartati, ma viene comunque generato un evento di tipo
- \const{IN\_Q\_OVERFLOW}.}
+\footnotetext{questa modalità è disponibile solo a partire dal kernel 2.6.2.}
-Il campo \var{cookie} contiene invece un intero univoco che permette di
-identificare eventi correlati (per i quali avrà lo stesso valore), al momento
-viene utilizzato soltanto per rilevare lo spostamento di un file, consentendo
-così all'applicazione di collegare la corrispondente coppia di eventi
-\const{IN\_MOVED\_TO} e \const{IN\_MOVED\_FROM}.
+Le modalità di utilizzo di \textit{epoll} prevedono 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.
-Infine due campi \var{name} e \var{len} sono utilizzati soltanto quando
-l'evento è relativo ad un file presente in una directory posta sotto
-osservazione, in tal caso essi contengono rispettivamente il nome del file
-(come pathname relativo alla directory osservata) e la relativa dimensione in
-byte. Il campo \var{name} viene sempre restituito come stringa terminata da
-NUL, con uno o più zeri di terminazione, a seconda di eventuali necessità di
-allineamento del risultato, ed il valore di \var{len} corrisponde al totale
-della dimensione di \var{name}, zeri aggiuntivi compresi. La stringa con il
-nome del file viene restituita nella lettura subito dopo la struttura
-\struct{inotify\_event}; questo significa che le dimensioni di ciascun evento
-di \textit{inotify} saranno pari a \code{sizeof(\struct{inotify\_event}) +
- len}.
+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.
-Vediamo allora un esempio dell'uso dell'interfaccia di \textit{inotify} con un
-semplice programma che permette di mettere sotto osservazione uno o più file e
-directory. Il programma si chiama \texttt{inotify\_monitor.c} ed il codice
-completo è disponibile coi sorgenti allegati alla guida, il corpo principale
-del programma, che non contiene la sezione di gestione delle opzioni e le
-funzioni di ausilio è riportato in fig.~\ref{fig:inotify_monitor_example}.
+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}.
-\begin{figure}[!htbp]
- \footnotesize \centering
- \begin{minipage}[c]{15cm}
- \includecodesample{listati/inotify_monitor.c}
- \end{minipage}
- \normalsize
- \caption{Esempio di codice che usa l'interfaccia di \textit{inotify}.}
- \label{fig:inotify_monitor_example}
-\end{figure}
+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}.}
-Una volta completata la scansione delle opzioni il corpo principale del
-programma inizia controllando (\texttt{\small 11--15}) che sia rimasto almeno
-un argomento che indichi quale file o directory mettere sotto osservazione (e
-qualora questo non avvenga esce stampando la pagina di aiuto); dopo di che
-passa (\texttt{\small 16--20}) all'inizializzazione di \textit{inotify}
-ottenendo con \func{inotify\_init} il relativo file descriptor (oppure usce in
-caso di errore).
+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}.
-Il passo successivo è aggiungere (\texttt{\small 21--30}) alla coda di
-notifica gli opportuni osservatori per ciascuno dei file o directory indicati
-all'invocazione del comando; questo viene fatto eseguendo un ciclo
-(\texttt{\small 22--29}) fintanto che la variabile \var{i}, inizializzata a
-zero (\texttt{\small 21}) all'inizio del ciclo, è minore del numero totale di
-argomenti rimasti. All'interno del ciclo si invoca (\texttt{\small 23})
-\func{inotify\_add\_watch} per ciascuno degli argomenti, usando la maschera
-degli eventi data dalla variabile \var{mask} (il cui valore viene impostato
-nella scansione delle opzioni), in caso di errore si esce dal programma
-altrimenti si incrementa l'indice (\texttt{\small 29}).
+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}
-Completa l'inizializzazione di \textit{inotify} inizia il ciclo principale
-(\texttt{\small 32--56}) del programma, nel quale si resta in attesa degli
-eventi che si intendono osservare. Questo viene fatto eseguendo all'inizio del
-ciclo (\texttt{\small 33}) una \func{read} che si bloccherà fintanto che non
-si saranno verificati eventi.
+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}.
-Dato che l'interfaccia di \textit{inotify} può riportare anche più eventi in
-una sola lettura, si è avuto cura di passare alla \func{read} un buffer di
-dimensioni adeguate, inizializzato in (\texttt{\small 7}) ad un valore di
-approssimativamente 512 eventi.\footnote{si ricordi che la quantità di dati
- restituita da \textit{inotify} è variabile a causa della diversa lunghezza
- del nome del file restituito insieme a \struct{inotify\_event}.} In caso di
-errore di lettura (\texttt{\small 35--40}) il programma esce con un messaggio
-di errore (\texttt{\small 37--39}), a meno che non si tratti di una
-interruzione della system call, nel qual caso (\texttt{\small 36}) si ripete la
-lettura.
+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 il valore $-1$, che indica
+un'attesa indefinita. L'argomento \param{maxevents} dovrà invece essere sempre
+un intero positivo.
-Se la lettura è andata a buon fine invece si esegue un ciclo (\texttt{\small
- 43--52}) per leggere tutti gli eventi restituiti, al solito si inizializza
-l'indice \var{i} a zero (\texttt{\small 42}) e si ripetono le operazioni
-(\texttt{\small 43}) fintanto che esso non supera il numero di byte restituiti
-in lettura. Per ciascun evento all'interno del ciclo si assegna\footnote{si
- noti come si sia eseguito un opportuno \textit{casting} del puntatore.} alla
-variabile \var{event} l'indirizzo nel buffer della corrispondente struttura
-\struct{inotify\_event} (\texttt{\small 44}), e poi si stampano il numero di
-\textit{watch descriptor} (\texttt{\small 45}) ed il file a cui questo fa
-riferimento (\texttt{\small 46}), ricavato dagli argomenti passati a riga di
-comando sfruttando il fatto che i \textit{watch descriptor} vengono assegnati
-in ordine progressivo crescente a partire da 1.
+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}, in questo modo il campo \var{data} consente di
+identificare il file descriptor.\footnote{ed è per questo che, come accennato,
+ è consuetudine usare per \var{data} il valore del file descriptor stesso.}
-Qualora sia presente il riferimento ad un nome di file associato all'evento lo
-si stampa (\texttt{\small 47--49}); si noti come in questo caso si sia
-utilizzato il valore del campo \var{event->len} e non al fatto che
-\var{event->name} riporti o meno un puntatore nullo.\footnote{l'interfaccia
- infatti, qualora il nome non sia presente, non avvalora il campo
- \var{event->name}, che si troverà a contenere quello che era precedentemente
- presente nella rispettiva locazione di memoria, nel caso più comune il
- puntatore al nome di un file osservato in precedenza.} Si utilizza poi
-(\texttt{\small 50}) la funzione \code{printevent}, che interpreta il valore
-del campo \var{event->mask} per stampare il tipo di eventi
-accaduti.\footnote{per il relativo codice, che non riportiamo in quanto non
- essenziale alla comprensione dell'esempio, si possono utilizzare direttamente
- i sorgenti allegati alla guida.} Infine (\texttt{\small 51}) si provvede ad
-aggiornare l'indice \var{i} per farlo puntare all'evento successivo.
+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 un file descriptor 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.
-Se adesso usiamo il programma per mettere sotto osservazione una directory, e
-da un altro terminale eseguiamo il comando \texttt{ls} otterremo qualcosa del
-tipo di:
-\begin{verbatim}
-piccardi@gethen:~/gapil/sources$ ./inotify_monitor -a /home/piccardi/gapil/
-Watch descriptor 1
-Observed event on /home/piccardi/gapil/
-IN_OPEN,
-Watch descriptor 1
-Observed event on /home/piccardi/gapil/
-IN_CLOSE_NOWRITE,
-\end{verbatim}
+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.
+Questa condizione viene generalmente rilevata dall'occorrere di un errore di
+\errcode{EAGAIN} al ritorno di una \func{read} o una \func{write},\footnote{è
+ opportuno ricordare ancora una volta che l'uso dell'\textit{I/O multiplexing}
+ richiede di operare sui file in modalità non bloccante.} ma questa non è la
+sola modalità possibile, ad esempio la condizione può essere riconosciuta
+anche con il fatto che sono stati restituiti meno dati di quelli richiesti.
-I lettori più accorti si saranno resi conto che nel ciclo di lettura degli
-eventi appena illustrato non viene trattato il caso particolare in cui la
-funzione \func{read} restituisce in \var{nread} un valore nullo. Lo si è fatto
-perché con \textit{inotify} il ritorno di una \func{read} con un valore nullo
-avviene soltanto, come forma di avviso, quando si sia eseguita la funzione
-specificando un buffer di dimensione insufficiente a contenere anche un solo
-evento. Nel nostro caso le dimensioni erano senz'altro sufficienti, per cui
-tale evenienza non si verificherà mai.
+Come le precedenti \func{select} e \func{poll}, le funzioni dell'interfaccia
+di \textit{epoll} vengono utilizzate prevalentemente con i server di rete,
+quando si devono tenere sotto osservazione un gran numero di socket; per
+questo motivo rimandiamo di nuovo la trattazione di un esempio concreto a
+quando avremo esaminato in dettaglio le caratteristiche dei socket, in
+particolare si potrà trovare un programma che utilizza questa interfaccia in
+sez.~\ref{sec:TCP_sock_multiplexing}.
-Ci si potrà però chiedere cosa succede se il buffer è sufficiente per un
-evento, ma non per tutti gli eventi verificatisi. Come si potrà notare nel
-codice illustrato in precedenza non si è presa nessuna precauzione per
-verificare che non ci fossero stati troncamenti dei dati. Anche in questo caso
-il comportamento scelto è corretto, perché l'interfaccia di \textit{inotify}
-garantisce automaticamente, anche quando ne sono presenti in numero maggiore,
-di restituire soltanto il numero di eventi che possono rientrare completamente
-nelle dimensioni del buffer specificato.\footnote{si avrà cioè, facendo
- riferimento sempre al codice di fig.~\ref{fig:inotify_monitor_example}, che
- \var{read} sarà in genere minore delle dimensioni di \var{buffer} ed uguale
- soltanto qualora gli eventi corrispondano esattamente alle dimensioni di
- quest'ultimo.} Se gli eventi sono di più saranno restituiti solo quelli che
-entrano interamente nel buffer e gli altri saranno restituiti alla successiva
-chiamata di \func{read}.
+\itindend{epoll}
-Infine un'ultima caratteristica dell'interfaccia di \textit{inotify} è che gli
-eventi restituiti nella lettura formano una sequenza ordinata, è cioè
-garantito che se si esegue uno spostamento di un file gli eventi vengano
-generati nella sequenza corretta. L'interfaccia garantisce anche che se si
-verificano più eventi consecutivi identici (vale a dire con gli stessi valori
-dei campi \var{wd}, \var{mask}, \var{cookie}, e \var{name}) questi vengono
-raggruppati in un solo evento.
-\index{file!inotify|)}
-% TODO trattare fanotify, vedi http://lwn.net/Articles/339399/ e
-% http://lwn.net/Articles/343346/
+\section{L'accesso \textsl{asincrono} ai file}
+\label{sec:file_asyncronous_access}
+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 in cui si
+debba operare su più file contemporaneamente, esistono altre modalità di
+gestione delle stesse problematiche. In particolare sono importanti in questo
+contesto le modalità di accesso ai file eseguibili in maniera
+\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, ma esistono anche altre interfacce, come \itindex{inotify}
+\textit{inotify}), per essere avvisato della possibilità di eseguire le
+operazioni di I/O volute.
-\subsection{L'interfaccia POSIX per l'I/O asincrono}
-\label{sec:file_asyncronous_io}
-Una modalità alternativa all'uso dell'\textit{I/O multiplexing} per gestione
-dell'I/O simultaneo su molti file è costituita dal cosiddetto \textsl{I/O
- asincrono}. Il concetto base dell'\textsl{I/O asincrono} è che le funzioni
-di I/O non attendono il completamento delle operazioni prima di ritornare,
-così che il processo non viene bloccato. In questo modo diventa ad esempio
-possibile effettuare una richiesta preventiva di dati, in modo da poter
-effettuare in contemporanea le operazioni di calcolo e quelle di I/O.
+\subsection{Il \textit{Signal driven I/O}}
+\label{sec:file_asyncronous_operation}
-Benché la modalità di apertura asincrona di un file possa risultare utile in
-varie occasioni (in particolar modo con i socket e gli altri file per i quali
-le funzioni di I/O sono \index{system~call~lente} system call lente), essa è
-comunque limitata alla notifica della disponibilità del file descriptor per le
-operazioni di I/O, e non ad uno svolgimento asincrono delle medesime. Lo
-standard POSIX.1b definisce una interfaccia apposita per l'I/O asincrono vero
-e proprio, che prevede un insieme di funzioni dedicate per la lettura e la
-scrittura dei file, completamente separate rispetto a quelle usate
-normalmente.
+\itindbeg{signal~driven~I/O}
-In generale questa interfaccia è completamente astratta e può essere
-implementata sia direttamente nel kernel, che in user space attraverso l'uso
-di \itindex{thread} \textit{thread}. Per le versioni del kernel meno recenti
-esiste una implementazione di questa interfaccia fornita delle \acr{glibc},
-che è realizzata completamente in user space, ed è accessibile linkando i
-programmi con la libreria \file{librt}. Nelle versioni più recenti (a partire
-dalla 2.5.32) è stato introdotto direttamente nel kernel un nuovo layer per
-l'I/O asincrono.
+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}).
-Lo standard prevede che tutte le operazioni di I/O asincrono siano controllate
-attraverso l'uso di una apposita struttura \struct{aiocb} (il cui nome sta per
-\textit{asyncronous I/O control block}), che viene passata come argomento a
-tutte le funzioni dell'interfaccia. La sua definizione, come effettuata in
-\file{aio.h}, è riportata in fig.~\ref{fig:file_aiocb}. Nello steso file è
-definita la macro \macro{\_POSIX\_ASYNCHRONOUS\_IO}, che dichiara la
-disponibilità dell'interfaccia per l'I/O asincrono.
+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.}
-\begin{figure}[!htb]
- \footnotesize \centering
- \begin{minipage}[c]{15cm}
- \includestruct{listati/aiocb.h}
- \end{minipage}
- \normalsize
- \caption{La struttura \structd{aiocb}, usata per il controllo dell'I/O
- asincrono.}
- \label{fig:file_aiocb}
-\end{figure}
+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.
-Le operazioni di I/O asincrono possono essere effettuate solo su un file già
-aperto; il file deve inoltre supportare la funzione \func{lseek}, pertanto
-terminali e pipe sono esclusi. Non c'è limite al numero di operazioni
-contemporanee effettuabili su un singolo file. Ogni operazione deve
-inizializzare opportunamente un \textit{control block}. Il file descriptor su
-cui operare deve essere specificato tramite il campo \var{aio\_fildes}; dato
-che più operazioni possono essere eseguita in maniera asincrona, il concetto
-di posizione corrente sul file viene a mancare; pertanto si deve sempre
-specificare nel campo \var{aio\_offset} la posizione sul file da cui i dati
-saranno letti o scritti. Nel campo \var{aio\_buf} deve essere specificato
-l'indirizzo del buffer usato per l'I/O, ed in \var{aio\_nbytes} la lunghezza
-del blocco di dati da trasferire.
+Per questo motivo Stevens, ed anche le pagine di manuale di Linux, chiamano
+questa modalità ``\textit{Signal driven I/O}''. Si tratta di 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.
-Il campo \var{aio\_reqprio} permette di impostare la priorità delle operazioni
-di I/O.\footnote{in generale perché ciò sia possibile occorre che la
- piattaforma supporti questa caratteristica, questo viene indicato definendo
- le macro \macro{\_POSIX\_PRIORITIZED\_IO}, e
- \macro{\_POSIX\_PRIORITY\_SCHEDULING}.} La priorità viene impostata a
-partire da quella del processo chiamante (vedi sez.~\ref{sec:proc_priority}),
-cui viene sottratto il valore di questo campo. Il campo
-\var{aio\_lio\_opcode} è usato solo dalla funzione \func{lio\_listio}, che,
-come vedremo, permette di eseguire con una sola chiamata una serie di
-operazioni, usando un vettore di \textit{control block}. Tramite questo campo
-si specifica quale è la natura di ciascuna di esse.
+Tuttavia con l'implementazione classica dei segnali questa modalità di I/O
+presenta notevoli problemi, dato che non è possibile determinare, quando i
+file descriptor sono più di uno, qual è quello responsabile dell'emissione del
+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.
-\begin{figure}[!htb]
- \footnotesize \centering
- \begin{minipage}[c]{15cm}
- \includestruct{listati/sigevent.h}
- \end{minipage}
- \normalsize
- \caption{La struttura \structd{sigevent}, usata per specificare le modalità
- di notifica degli eventi relativi alle operazioni di I/O asincrono.}
- \label{fig:file_sigevent}
-\end{figure}
+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}).
-Infine il campo \var{aio\_sigevent} è una struttura di tipo \struct{sigevent}
-che serve a specificare il modo in cui si vuole che venga effettuata la
-notifica del completamento delle operazioni richieste. La struttura è
-riportata in fig.~\ref{fig:file_sigevent}; il campo \var{sigev\_notify} è
-quello che indica le modalità della notifica, esso può assumere i tre valori:
-\begin{basedescript}{\desclabelwidth{2.6cm}}
-\item[\const{SIGEV\_NONE}] Non viene inviata nessuna notifica.
-\item[\const{SIGEV\_SIGNAL}] La notifica viene effettuata inviando al processo
- chiamante il segnale specificato da \var{sigev\_signo}; se il gestore di
- questo è stato installato con \const{SA\_SIGINFO} gli verrà restituito il
- valore di \var{sigev\_value} (la cui definizione è in
- fig.~\ref{fig:sig_sigval}) come valore del campo \var{si\_value} di
- \struct{siginfo\_t}.
-\item[\const{SIGEV\_THREAD}] La notifica viene effettuata creando un nuovo
- \itindex{thread} \textit{thread} che esegue la funzione specificata da
- \var{sigev\_notify\_function} con argomento \var{sigev\_value}, e con gli
- attributi specificati da \var{sigev\_notify\_attribute}.
-\end{basedescript}
+Per far questo però occorre utilizzare le funzionalità dei segnali real-time
+(vedi sez.~\ref{sec:sig_real_time}) impostando esplicitamente con il comando
+\const{F\_SETSIG} di \func{fcntl} un segnale real-time da inviare in caso di
+I/O asincrono (il segnale predefinito è \const{SIGIO}). In questo caso il
+gestore, tutte le volte che riceverà \const{SI\_SIGIO} come valore del campo
+\var{si\_code}\footnote{il valore resta \const{SI\_SIGIO} qualunque sia il
+ segnale che si è associato all'I/O, ed indica appunto che il segnale è stato
+ generato a causa di attività di I/O.} di \struct{siginfo\_t}, troverà nel
+campo \var{si\_fd} il valore del file descriptor che ha generato il segnale.
-Le due funzioni base dell'interfaccia per l'I/O asincrono sono
-\funcd{aio\_read} ed \funcd{aio\_write}. Esse permettono di richiedere una
-lettura od una scrittura asincrona di dati, usando la struttura \struct{aiocb}
-appena descritta; i rispettivi prototipi sono:
-\begin{functions}
- \headdecl{aio.h}
+Un secondo vantaggio dell'uso dei segnali real-time è che essendo questi
+ultimi dotati di una coda di consegna ogni segnale sarà associato ad uno solo
+file descriptor; inoltre sarà possibile stabilire delle priorità nella
+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.
- \funcdecl{int aio\_read(struct aiocb *aiocbp)}
- Richiede una lettura asincrona secondo quanto specificato con \param{aiocbp}.
+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
+ \procfile{/proc/sys/kernel/rtsig-max} allo stesso valore del contenuto di
+ \procfile{/proc/sys/fs/file-max}.}
- \funcdecl{int aio\_write(struct aiocb *aiocbp)}
- Richiede una scrittura asincrona secondo quanto specificato con
- \param{aiocbp}.
-
- \bodydesc{Le funzioni restituiscono 0 in caso di successo, 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.
- \item[\errcode{ENOSYS}] la funzione non è implementata.
- \item[\errcode{EINVAL}] si è specificato un valore non valido per i campi
- \var{aio\_offset} o \var{aio\_reqprio} di \param{aiocbp}.
- \item[\errcode{EAGAIN}] la coda delle richieste è momentaneamente piena.
- \end{errlist}
-}
-\end{functions}
+% TODO fare esempio che usa O_ASYNC
-Entrambe le funzioni ritornano immediatamente dopo aver messo in coda la
-richiesta, o in caso di errore. Non è detto che gli errori \errcode{EBADF} ed
-\errcode{EINVAL} siano rilevati immediatamente al momento della chiamata,
-potrebbero anche emergere nelle fasi successive delle operazioni. Lettura e
-scrittura avvengono alla posizione indicata da \var{aio\_offset}, a meno che
-il file non sia stato aperto in \itindex{append~mode} \textit{append mode}
-(vedi sez.~\ref{sec:file_open}), nel qual caso le scritture vengono effettuate
-comunque alla fine de file, nell'ordine delle chiamate a \func{aio\_write}.
+\itindend{signal~driven~I/O}
-Si tenga inoltre presente che deallocare la memoria indirizzata da
-\param{aiocbp} o modificarne i valori prima della conclusione di una
-operazione può dar luogo a risultati impredicibili, perché l'accesso ai vari
-campi per eseguire l'operazione può avvenire in un momento qualsiasi dopo la
-richiesta. Questo comporta che non si devono usare per \param{aiocbp}
-variabili automatiche e che non si deve riutilizzare la stessa struttura per
-un'altra operazione fintanto che la precedente non sia stata ultimata. In
-generale per ogni operazione si deve utilizzare una diversa struttura
-\struct{aiocb}.
-Dato che si opera in modalità asincrona, il successo di \func{aio\_read} o
-\func{aio\_write} non implica che le operazioni siano state effettivamente
-eseguite in maniera corretta; per verificarne l'esito l'interfaccia prevede
-altre due funzioni, che permettono di controllare lo stato di esecuzione. La
-prima è \funcd{aio\_error}, che serve a determinare un eventuale stato di
-errore; il suo prototipo è:
-\begin{prototype}{aio.h}
- {int aio\_error(const struct aiocb *aiocbp)}
- Determina lo stato di errore delle operazioni di I/O associate a
- \param{aiocbp}.
-
- \bodydesc{La funzione restituisce 0 se le operazioni si sono concluse con
- successo, altrimenti restituisce il codice di errore relativo al loro
- fallimento.}
-\end{prototype}
+\subsection{I meccanismi di notifica asincrona.}
+\label{sec:file_asyncronous_lease}
-Se l'operazione non si è ancora completata viene restituito l'errore di
-\errcode{EINPROGRESS}. La funzione ritorna zero quando l'operazione si è
-conclusa con successo, altrimenti restituisce il codice dell'errore
-verificatosi, ed esegue la corrispondente impostazione di \var{errno}. Il
-codice può essere sia \errcode{EINVAL} ed \errcode{EBADF}, dovuti ad un valore
-errato per \param{aiocbp}, che uno degli errori possibili durante l'esecuzione
-dell'operazione di I/O richiesta, nel qual caso saranno restituiti, a seconda
-del caso, i codici di errore delle system call \func{read}, \func{write} e
-\func{fsync}.
+Una delle domande più frequenti nella programmazione in ambiente unix-like è
+quella di come fare a sapere quando un file viene modificato. La
+risposta\footnote{o meglio la non risposta, tanto che questa nelle Unix FAQ
+ \cite{UnixFAQ} viene anche chiamata una \textit{Frequently Unanswered
+ Question}.} è che nell'architettura classica di Unix questo non è
+possibile. Al contrario di altri sistemi operativi infatti un kernel unix-like
+classico non prevedeva alcun meccanismo per cui un processo possa essere
+\textsl{notificato} di eventuali modifiche avvenute su un file. Questo è il
+motivo per cui i demoni devono essere \textsl{avvisati} in qualche
+modo\footnote{in genere questo vien fatto inviandogli un segnale di
+ \const{SIGHUP} che, per una convenzione adottata dalla gran parte di detti
+ programmi, causa la rilettura della configurazione.} se il loro file di
+configurazione è stato modificato, perché possano rileggerlo e riconoscere le
+modifiche.
-Una volta che si sia certi che le operazioni siano state concluse (cioè dopo
-che una chiamata ad \func{aio\_error} non ha restituito
-\errcode{EINPROGRESS}), si potrà usare la funzione \funcd{aio\_return}, che
-permette di verificare il completamento delle operazioni di I/O asincrono; il
-suo prototipo è:
-\begin{prototype}{aio.h}
-{ssize\_t aio\_return(const struct aiocb *aiocbp)}
+Questa scelta è stata fatta perché provvedere un simile meccanismo a livello
+generico per qualunque file comporterebbe un notevole aumento di complessità
+dell'architettura della gestione dei file, il tutto per fornire una
+funzionalità che serve soltanto in alcuni casi particolari. Dato che
+all'origine di Unix i soli programmi che potevano avere una tale esigenza
+erano i demoni, attenendosi a uno dei criteri base della progettazione, che
+era di far fare al kernel solo le operazioni strettamente necessarie e
+lasciare tutto il resto a processi in user space, non era stata prevista
+nessuna funzionalità di notifica.
-Recupera il valore dello stato di ritorno delle operazioni di I/O associate a
-\param{aiocbp}.
-
-\bodydesc{La funzione restituisce lo stato di uscita dell'operazione
- eseguita.}
-\end{prototype}
+Visto però il crescente interesse nei confronti di una funzionalità di questo
+tipo, che è molto richiesta specialmente nello sviluppo dei programmi ad
+interfaccia grafica, quando si deve presentare all'utente lo stato del
+filesystem, sono state successivamente introdotte delle estensioni che
+permettessero la creazione di meccanismi di notifica più efficienti dell'unica
+soluzione disponibile con l'interfaccia tradizionale, che è quella del
+\itindex{polling} \textit{polling}.
-La funzione deve essere chiamata una sola volte per ciascuna operazione
-asincrona, essa infatti fa sì che il sistema rilasci le risorse ad essa
-associate. É per questo motivo che occorre chiamare la funzione solo dopo che
-l'operazione cui \param{aiocbp} fa riferimento si è completata. Una chiamata
-precedente il completamento delle operazioni darebbe risultati indeterminati.
+Queste nuove funzionalità sono delle estensioni specifiche, non
+standardizzate, che sono disponibili soltanto su Linux (anche se altri kernel
+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}.
-La funzione restituisce il valore di ritorno relativo all'operazione eseguita,
-così come ricavato dalla sottostante system call (il numero di byte letti,
-scritti o il valore di ritorno di \func{fsync}). É importante chiamare sempre
-questa funzione, altrimenti le risorse disponibili per le operazioni di I/O
-asincrono non verrebbero liberate, rischiando di arrivare ad un loro
-esaurimento.
+\index{file!lease|(}
-Oltre alle operazioni di lettura e scrittura l'interfaccia POSIX.1b mette a
-disposizione un'altra operazione, quella di sincronizzazione dell'I/O,
-compiuta dalla funzione \funcd{aio\_fsync}, che ha lo stesso effetto della
-analoga \func{fsync}, ma viene eseguita in maniera asincrona; il suo prototipo
-è:
-\begin{prototype}{aio.h}
-{int aio\_fsync(int op, struct aiocb *aiocbp)}
+La prima di queste funzionalità è quella del cosiddetto \textit{file lease};
+questo è un meccanismo che consente ad un processo, detto \textit{lease
+ holder}, di essere notificato quando un altro processo, chiamato a sua volta
+\textit{lease breaker}, cerca di eseguire una \func{open} o una
+\func{truncate} sul file del quale l'\textit{holder} detiene il
+\textit{lease}.
+La notifica avviene in maniera analoga a come illustrato in precedenza per
+l'uso di \const{O\_ASYNC}: di default viene inviato al \textit{lease holder}
+il segnale \const{SIGIO}, ma questo segnale può essere modificato usando il
+comando \const{F\_SETSIG} di \func{fcntl}.\footnote{anche in questo caso si
+ può rispecificare lo stesso \const{SIGIO}.} Se si è fatto questo\footnote{è
+ in genere è opportuno farlo, come in precedenza, per utilizzare segnali
+ real-time.} e si è installato il gestore del segnale con \const{SA\_SIGINFO}
+si riceverà nel campo \var{si\_fd} della struttura \struct{siginfo\_t} il
+valore del file descriptor del file sul quale è stato compiuto l'accesso; in
+questo modo un processo può mantenere anche più di un \textit{file lease}.
-Richiede la sincronizzazione dei dati per il file indicato da \param{aiocbp}.
-
-\bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di
- errore, che può essere, con le stesse modalità di \func{aio\_read},
- \errval{EAGAIN}, \errval{EBADF} o \errval{EINVAL}.}
-\end{prototype}
+Esistono due tipi di \textit{file lease}: di lettura (\textit{read lease}) e
+di scrittura (\textit{write lease}). Nel primo caso la notifica avviene quando
+un altro processo esegue l'apertura del file in scrittura o usa
+\func{truncate} per troncarlo. Nel secondo caso la notifica avviene anche se
+il file viene aperto il lettura; in quest'ultimo caso però il \textit{lease}
+può essere ottenuto solo se nessun altro processo ha aperto lo stesso file.
-La funzione richiede la sincronizzazione delle operazioni di I/O, ritornando
-immediatamente. L'esecuzione effettiva della sincronizzazione dovrà essere
-verificata con \func{aio\_error} e \func{aio\_return} come per le operazioni
-di lettura e scrittura. L'argomento \param{op} permette di indicare la
-modalità di esecuzione, se si specifica il valore \const{O\_DSYNC} le
-operazioni saranno completate con una chiamata a \func{fdatasync}, se si
-specifica \const{O\_SYNC} con una chiamata a \func{fsync} (per i dettagli vedi
-sez.~\ref{sec:file_sync}).
+Come accennato in sez.~\ref{sec:file_fcntl} il comando di \func{fcntl} che
+consente di acquisire un \textit{file lease} è \const{F\_SETLEASE}, che viene
+utilizzato anche per rilasciarlo. In tal caso il file descriptor \param{fd}
+passato a \func{fcntl} servirà come riferimento per il file su cui si vuole
+operare, mentre per indicare il tipo di operazione (acquisizione o rilascio)
+occorrerà specificare come valore dell'argomento \param{arg} di \func{fcntl}
+uno dei tre valori di tab.~\ref{tab:file_lease_fctnl}.
-Il successo della chiamata assicura la sincronizzazione delle operazioni fino
-allora richieste, niente è garantito riguardo la sincronizzazione dei dati
-relativi ad eventuali operazioni richieste successivamente. Se si è
-specificato un meccanismo di notifica questo sarà innescato una volta che le
-operazioni di sincronizzazione dei dati saranno completate.
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|l|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{F\_RDLCK} & Richiede un \textit{read lease}.\\
+ \const{F\_WRLCK} & Richiede un \textit{write lease}.\\
+ \const{F\_UNLCK} & Rilascia un \textit{file lease}.\\
+ \hline
+ \end{tabular}
+ \caption{Costanti per i tre possibili valori dell'argomento \param{arg} di
+ \func{fcntl} quando usata con i comandi \const{F\_SETLEASE} e
+ \const{F\_GETLEASE}.}
+ \label{tab:file_lease_fctnl}
+\end{table}
-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 funzione apposita, \funcd{aio\_cancel}, che
-permette di cancellare una operazione richiesta in precedenza; il suo
-prototipo è:
-\begin{prototype}{aio.h}
-{int aio\_cancel(int fildes, struct aiocb *aiocbp)}
+Se invece si vuole conoscere lo stato di eventuali \textit{file lease}
+occorrerà chiamare \func{fcntl} sul relativo file descriptor \param{fd} con il
+comando \const{F\_GETLEASE}, e si otterrà indietro nell'argomento \param{arg}
+uno dei valori di tab.~\ref{tab:file_lease_fctnl}, che indicheranno la
+presenza del rispettivo tipo di \textit{lease}, o, nel caso di
+\const{F\_UNLCK}, l'assenza di qualunque \textit{file lease}.
-Richiede la cancellazione delle operazioni sul file \param{fildes} specificate
-da \param{aiocbp}.
-
-\bodydesc{La funzione restituisce il risultato dell'operazione con un codice
- di positivo, e -1 in caso di errore, che avviene qualora si sia specificato
- un valore non valido di \param{fildes}, imposta \var{errno} al valore
- \errval{EBADF}.}
-\end{prototype}
+Si tenga presente che un processo può mantenere solo un tipo di \textit{lease}
+su un file, e che un \textit{lease} può essere ottenuto solo su file di dati
+(pipe e dispositivi sono quindi esclusi). Inoltre un processo non privilegiato
+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}, vedi sez.~\ref{sec:proc_capabilities}) può acquisire
+\textit{lease} su qualunque file.
-La funzione permette di cancellare una operazione specifica sul file
-\param{fildes}, o tutte le operazioni pendenti, specificando \val{NULL} come
-valore di \param{aiocbp}. Quando una operazione viene cancellata una
-successiva chiamata ad \func{aio\_error} riporterà \errcode{ECANCELED} come
-codice di errore, ed il suo codice di ritorno sarà -1, inoltre il meccanismo
-di notifica non verrà invocato. Se si specifica una operazione relativa ad un
-altro file descriptor il risultato è indeterminato. In caso di successo, i
-possibili valori di ritorno per \func{aio\_cancel} (anch'essi definiti in
-\file{aio.h}) sono tre:
-\begin{basedescript}{\desclabelwidth{3.0cm}}
-\item[\const{AIO\_ALLDONE}] indica che le operazioni di cui si è richiesta la
- cancellazione sono state già completate,
-
-\item[\const{AIO\_CANCELED}] indica che tutte le operazioni richieste sono
- state cancellate,
-
-\item[\const{AIO\_NOTCANCELED}] indica che alcune delle operazioni erano in
- corso e non sono state cancellate.
-\end{basedescript}
+Se su un file è presente un \textit{lease} quando il \textit{lease breaker}
+esegue una \func{truncate} o una \func{open} che confligge con
+esso,\footnote{in realtà \func{truncate} confligge sempre, mentre \func{open},
+ se eseguita in sola lettura, non confligge se si tratta di un \textit{read
+ lease}.} la funzione si blocca\footnote{a meno di non avere aperto il file
+ con \const{O\_NONBLOCK}, nel qual caso \func{open} fallirebbe con un errore
+ di \errcode{EWOULDBLOCK}.} e viene eseguita la notifica al \textit{lease
+ holder}, così che questo possa completare le sue operazioni sul file e
+rilasciare il \textit{lease}. In sostanza con un \textit{read lease} si
+rilevano i tentativi di accedere al file per modificarne i dati da parte di un
+altro processo, mentre con un \textit{write lease} si rilevano anche i
+tentativi di accesso in lettura. Si noti comunque che le operazioni di
+notifica avvengono solo in fase di apertura del file e non sulle singole
+operazioni di lettura e scrittura.
-Nel caso si abbia \const{AIO\_NOTCANCELED} occorrerà chiamare
-\func{aio\_error} per determinare quali sono le operazioni effettivamente
-cancellate. Le operazioni che non sono state cancellate proseguiranno il loro
-corso normale, compreso quanto richiesto riguardo al meccanismo di notifica
-del loro avvenuto completamento.
+L'utilizzo dei \textit{file lease} consente al \textit{lease holder} di
+assicurare la consistenza di un file, a seconda dei due casi, prima che un
+altro processo inizi con le sue operazioni di scrittura o di lettura su di
+esso. In genere un \textit{lease holder} che riceve una notifica deve
+provvedere a completare le necessarie operazioni (ad esempio scaricare
+eventuali buffer), per poi rilasciare il \textit{lease} così che il
+\textit{lease breaker} possa eseguire le sue operazioni. Questo si fa con il
+comando \const{F\_SETLEASE}, o rimuovendo il \textit{lease} con
+\const{F\_UNLCK}, o, nel caso di \textit{write lease} che confligge con una
+operazione di lettura, declassando il \textit{lease} a lettura con
+\const{F\_RDLCK}.
-Benché l'I/O asincrono preveda un meccanismo di notifica, l'interfaccia
-fornisce anche una apposita funzione, \funcd{aio\_suspend}, che permette di
-sospendere l'esecuzione del processo chiamante fino al completamento di una
-specifica operazione; il suo prototipo è:
-\begin{prototype}{aio.h}
-{int aio\_suspend(const struct aiocb * const list[], int nent, const struct
- timespec *timeout)}
-
- Attende, per un massimo di \param{timeout}, il completamento di una delle
- operazioni specificate da \param{list}.
-
- \bodydesc{La funzione restituisce 0 se una (o più) operazioni sono state
- completate, e -1 in caso di errore nel qual caso \var{errno} assumerà uno
- dei valori:
- \begin{errlist}
- \item[\errcode{EAGAIN}] nessuna operazione è stata completata entro
- \param{timeout}.
- \item[\errcode{ENOSYS}] la funzione non è implementata.
- \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale.
- \end{errlist}
- }
-\end{prototype}
-
-La funzione permette di bloccare il processo fintanto che almeno una delle
-\param{nent} operazioni specificate nella lista \param{list} è completata, per
-un tempo massimo specificato da \param{timout}, o fintanto che non arrivi un
-segnale.\footnote{si tenga conto che questo segnale può anche essere quello
- utilizzato come meccanismo di notifica.} La lista deve essere inizializzata
-con delle strutture \struct{aiocb} relative ad operazioni effettivamente
-richieste, ma può contenere puntatori nulli, che saranno ignorati. In caso si
-siano specificati valori non validi l'effetto è indefinito. Un valore
-\val{NULL} per \param{timout} comporta l'assenza di timeout.
-
-Lo standard POSIX.1b infine ha previsto pure una funzione, \funcd{lio\_listio},
-che permette di effettuare la richiesta di una intera lista di operazioni di
-lettura o scrittura; il suo prototipo è:
-\begin{prototype}{aio.h}
- {int lio\_listio(int mode, struct aiocb * const list[], int nent, struct
- sigevent *sig)}
-
- Richiede l'esecuzione delle operazioni di I/O elencata da \param{list},
- secondo la modalità \param{mode}.
-
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
- errore, nel qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EAGAIN}] nessuna operazione è stata completata entro
- \param{timeout}.
- \item[\errcode{EINVAL}] si è passato un valore di \param{mode} non valido
- o un numero di operazioni \param{nent} maggiore di
- \const{AIO\_LISTIO\_MAX}.
- \item[\errcode{ENOSYS}] la funzione non è implementata.
- \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale.
- \end{errlist}
- }
-\end{prototype}
-
-La funzione esegue la richiesta delle \param{nent} operazioni indicate nella
-lista \param{list} che deve contenere gli indirizzi di altrettanti
-\textit{control block} opportunamente inizializzati; in particolare dovrà
-essere specificato il tipo di operazione con il campo \var{aio\_lio\_opcode},
-che può prendere i valori:
-\begin{basedescript}{\desclabelwidth{2.0cm}}
-\item[\const{LIO\_READ}] si richiede una operazione di lettura.
-\item[\const{LIO\_WRITE}] si richiede una operazione di scrittura.
-\item[\const{LIO\_NOP}] non si effettua nessuna operazione.
-\end{basedescript}
-dove \const{LIO\_NOP} viene usato quando si ha a che fare con un vettore di
-dimensione fissa, per poter specificare solo alcune operazioni, o quando si
-sono dovute cancellare delle operazioni e si deve ripetere la richiesta per
-quelle non completate.
-
-L'argomento \param{mode} controlla il comportamento della funzione, se viene
-usato il valore \const{LIO\_WAIT} la funzione si blocca fino al completamento
-di tutte le operazioni richieste; se si usa \const{LIO\_NOWAIT} la funzione
-ritorna immediatamente dopo aver messo in coda tutte le richieste. In tal caso
-il chiamante può richiedere la notifica del completamento di tutte le
-richieste, impostando l'argomento \param{sig} in maniera analoga a come si fa
-per il campo \var{aio\_sigevent} di \struct{aiocb}.
-
-
-\section{Altre modalità di I/O avanzato}
-\label{sec:file_advanced_io}
-
-Oltre alle precedenti modalità di \textit{I/O multiplexing} e \textsl{I/O
- asincrono}, esistono altre funzioni che implementano delle modalità di
-accesso ai file più evolute rispetto alle normali funzioni di lettura e
-scrittura che abbiamo esaminato in sez.~\ref{sec:file_base_func}. In questa
-sezione allora prenderemo in esame le interfacce per l'\textsl{I/O mappato in
- memoria}, per l'\textsl{I/O vettorizzato} e altre funzioni di I/O avanzato.
-
-
-\subsection{File mappati in memoria}
-\label{sec:file_memory_map}
-
-\itindbeg{memory~mapping}
-Una modalità alternativa di I/O, che usa una interfaccia completamente diversa
-rispetto a quella classica vista in cap.~\ref{cha:file_unix_interface}, è il
-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}
- \caption{Disposizione della memoria di un processo quando si esegue la
- mappatura in memoria di un file.}
- \label{fig:file_mmap_layout}
-\end{figure}
-
-Il meccanismo è illustrato in fig.~\ref{fig:file_mmap_layout}, una sezione del
-file viene \textsl{mappata} direttamente nello spazio degli indirizzi del
-programma. Tutte le operazioni di lettura e scrittura su variabili contenute
-in questa zona di memoria verranno eseguite leggendo e scrivendo dal contenuto
-del file attraverso il sistema della memoria virtuale \index{memoria~virtuale}
-che in maniera analoga a quanto avviene per le pagine che vengono salvate e
-rilette nella swap, si incaricherà di sincronizzare il contenuto di quel
-segmento di memoria con quello del file mappato su di esso. Per questo motivo
-si può parlare tanto di \textsl{file mappato in memoria}, quanto di
-\textsl{memoria mappata su file}.
-
-L'uso del \textit{memory-mapping} comporta una notevole semplificazione delle
-operazioni di I/O, in quanto non sarà più necessario utilizzare dei buffer
-intermedi su cui appoggiare i dati da traferire, poiché questi potranno essere
-acceduti direttamente nella sezione di memoria mappata; inoltre questa
-interfaccia è più efficiente delle usuali funzioni di I/O, in quanto permette
-di caricare in memoria solo le parti del file che sono effettivamente usate ad
-un dato istante.
-
-Infatti, dato che l'accesso è fatto direttamente attraverso la
-\index{memoria~virtuale} memoria virtuale, la sezione di memoria mappata su
-cui si opera sarà a sua volta letta o scritta sul file una pagina alla volta e
-solo per le parti effettivamente usate, il tutto in maniera completamente
-trasparente al processo; l'accesso alle pagine non ancora caricate avverrà
-allo stesso modo con cui vengono caricate in memoria le pagine che sono state
-salvate sullo swap.
+Se il \textit{lease holder} non provvede a rilasciare il \textit{lease} entro
+il numero di secondi specificato dal parametro di sistema mantenuto in
+\procfile{/proc/sys/fs/lease-break-time} sarà il kernel stesso a rimuoverlo (o
+declassarlo) automaticamente.\footnote{questa è una misura di sicurezza per
+ evitare che un processo blocchi indefinitamente l'accesso ad un file
+ acquisendo un \textit{lease}.} Una volta che un \textit{lease} è stato
+rilasciato o declassato (che questo sia fatto dal \textit{lease holder} o dal
+kernel è lo stesso) le chiamate a \func{open} o \func{truncate} eseguite dal
+\textit{lease breaker} rimaste bloccate proseguono automaticamente.
-Infine in situazioni in cui la memoria è scarsa, le pagine che mappano un file
-vengono salvate automaticamente, così come le pagine dei programmi vengono
-scritte sulla swap; questo consente di accedere ai file su dimensioni il cui
-solo limite è quello dello spazio di indirizzi disponibile, e non della
-memoria su cui possono esserne lette delle porzioni.
-L'interfaccia POSIX implementata da Linux prevede varie funzioni per la
-gestione del \textit{memory mapped I/O}, la prima di queste, che serve ad
-eseguire la mappatura in memoria di un file, è \funcd{mmap}; il suo prototipo
-è:
-\begin{functions}
-
- \headdecl{unistd.h}
- \headdecl{sys/mman.h}
+\index{file!dnotify|(}
- \funcdecl{void * mmap(void * start, size\_t length, int prot, int flags, int
- fd, off\_t offset)}
-
- Esegue la mappatura in memoria della sezione specificata del file \param{fd}.
-
- \bodydesc{La funzione restituisce il puntatore alla zona di memoria mappata
- in caso di successo, e \const{MAP\_FAILED} (-1) in caso di errore, nel
- qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EBADF}] il file descriptor non è valido, e non si è usato
- \const{MAP\_ANONYMOUS}.
- \item[\errcode{EACCES}] o \param{fd} non si riferisce ad un file regolare,
- o si è usato \const{MAP\_PRIVATE} ma \param{fd} non è aperto in lettura,
- o si è usato \const{MAP\_SHARED} e impostato \const{PROT\_WRITE} ed
- \param{fd} non è aperto in lettura/scrittura, o si è impostato
- \const{PROT\_WRITE} ed \param{fd} è in \textit{append-only}.
- \item[\errcode{EINVAL}] i valori di \param{start}, \param{length} o
- \param{offset} non sono validi (o troppo grandi o non allineati sulla
- dimensione delle pagine).
- \item[\errcode{ETXTBSY}] si è impostato \const{MAP\_DENYWRITE} ma
- \param{fd} è aperto in scrittura.
- \item[\errcode{EAGAIN}] il file è bloccato, o si è bloccata troppa memoria
- rispetto a quanto consentito dai limiti di sistema (vedi
- sez.~\ref{sec:sys_resource_limit}).
- \item[\errcode{ENOMEM}] non c'è memoria o si è superato il limite sul
- numero di mappature possibili.
- \item[\errcode{ENODEV}] il filesystem di \param{fd} non supporta il memory
- mapping.
- \item[\errcode{EPERM}] l'argomento \param{prot} ha richiesto
- \const{PROT\_EXEC}, ma il filesystem di \param{fd} è montato con
- l'opzione \texttt{noexec}.
- \item[\errcode{ENFILE}] si è superato il limite del sistema sul numero di
- file aperti (vedi sez.~\ref{sec:sys_resource_limit}).
- \end{errlist}
- }
-\end{functions}
+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 o una
+directory vengono modificati, che è quanto necessario ad esempio ai programma
+di gestione dei file dei vari desktop grafici.
-La funzione richiede di mappare in memoria la sezione del file \param{fd} a
-partire da \param{offset} per \param{lenght} byte, preferibilmente
-all'indirizzo \param{start}. Il valore di \param{offset} deve essere un
-multiplo della dimensione di una pagina di memoria.
+Per risolvere questo problema a partire dal kernel 2.4 è stata allora creata
+un'altra interfaccia,\footnote{si ricordi che anche questa è una interfaccia
+ specifica di Linux che deve essere evitata se si vogliono scrivere programmi
+ portabili, e che le funzionalità illustrate sono disponibili soltanto se è
+ stata definita la macro \macro{\_GNU\_SOURCE}.} chiamata \textit{dnotify},
+che consente di richiedere una notifica quando una directory, o 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 se ne
+può utilizzare un altro.\footnote{e di nuovo, per le ragioni già esposte in
+ precedenza, è opportuno che si utilizzino dei segnali real-time.} Inoltre,
+come in precedenza, si potrà ottenere nel gestore del segnale il file
+descriptor che è stato modificato tramite il contenuto della struttura
+\struct{siginfo\_t}.
+\index{file!lease|)}
\begin{table}[htb]
\centering
\footnotesize
- \begin{tabular}[c]{|l|l|}
+ \begin{tabular}[c]{|l|p{8cm}|}
\hline
- \textbf{Valore} & \textbf{Significato} \\
+ \textbf{Valore} & \textbf{Significato} \\
\hline
\hline
- \const{PROT\_EXEC} & Le pagine possono essere eseguite.\\
- \const{PROT\_READ} & Le pagine possono essere lette.\\
- \const{PROT\_WRITE} & Le pagine possono essere scritte.\\
- \const{PROT\_NONE} & L'accesso alle pagine è vietato.\\
+ \const{DN\_ACCESS} & Un file è stato acceduto, con l'esecuzione di una fra
+ \func{read}, \func{pread}, \func{readv}.\\
+ \const{DN\_MODIFY} & Un file è stato modificato, con l'esecuzione di una
+ fra \func{write}, \func{pwrite}, \func{writev},
+ \func{truncate}, \func{ftruncate}.\\
+ \const{DN\_CREATE} & È stato creato un file nella directory, con
+ l'esecuzione di una fra \func{open}, \func{creat},
+ \func{mknod}, \func{mkdir}, \func{link},
+ \func{symlink}, \func{rename} (da un'altra
+ directory).\\
+ \const{DN\_DELETE} & È stato cancellato un file dalla directory con
+ l'esecuzione di una fra \func{unlink}, \func{rename}
+ (su un'altra directory), \func{rmdir}.\\
+ \const{DN\_RENAME} & È stato rinominato un file all'interno della
+ directory (con \func{rename}).\\
+ \const{DN\_ATTRIB} & È stato modificato un attributo di un file con
+ l'esecuzione di una fra \func{chown}, \func{chmod},
+ \func{utime}.\\
+ \const{DN\_MULTISHOT}& Richiede una notifica permanente di tutti gli
+ eventi.\\
\hline
\end{tabular}
- \caption{Valori dell'argomento \param{prot} di \func{mmap}, relativi alla
- protezione applicate alle pagine del file mappate in memoria.}
- \label{tab:file_mmap_prot}
+ \caption{Le costanti che identificano le varie classi di eventi per i quali
+ si richiede la notifica con il comando \const{F\_NOTIFY} di \func{fcntl}.}
+ \label{tab:file_notify}
\end{table}
-Il valore dell'argomento \param{prot} indica la protezione\footnote{come
- accennato in sez.~\ref{sec:proc_memory} in Linux la memoria reale è divisa
- in pagine: ogni processo vede la sua memoria attraverso uno o più segmenti
- lineari di memoria virtuale. Per ciascuno di questi segmenti il kernel
- mantiene nella \itindex{page~table} \textit{page table} la mappatura sulle
- pagine di memoria reale, ed le modalità di accesso (lettura, esecuzione,
- scrittura); una loro violazione causa quella una \itindex{segment~violation}
- \textit{segment violation}, e la relativa emissione del segnale
- \const{SIGSEGV}.} da applicare al segmento di memoria e deve essere
-specificato come maschera binaria ottenuta dall'OR di uno o più dei valori
-riportati in tab.~\ref{tab:file_mmap_prot}; il valore specificato deve essere
-compatibile con la modalità di accesso con cui si è aperto il file.
-
-L'argomento \param{flags} specifica infine qual è il tipo di oggetto mappato,
-le opzioni relative alle modalità con cui è effettuata la mappatura e alle
-modalità con cui le modifiche alla memoria mappata vengono condivise o
-mantenute private al processo che le ha effettuate. Deve essere specificato
-come maschera binaria ottenuta dall'OR di uno o più dei valori riportati in
-tab.~\ref{tab:file_mmap_flag}.
-
-\begin{table}[htb]
- \centering
- \footnotesize
- \begin{tabular}[c]{|l|p{11cm}|}
- \hline
- \textbf{Valore} & \textbf{Significato} \\
- \hline
- \hline
- \const{MAP\_FIXED} & Non permette di restituire un indirizzo diverso
- da \param{start}, se questo non può essere usato
- \func{mmap} fallisce. Se si imposta questo flag il
- valore di \param{start} deve essere allineato
- alle dimensioni di una pagina.\\
- \const{MAP\_SHARED} & I cambiamenti sulla memoria mappata vengono
- riportati sul file e saranno immediatamente
- visibili agli altri processi che mappano lo stesso
- file.\footnotemark Il file su disco però non sarà
- aggiornato fino alla chiamata di \func{msync} o
- \func{munmap}), e solo allora le modifiche saranno
- visibili per l'I/O convenzionale. Incompatibile
- con \const{MAP\_PRIVATE}.\\
- \const{MAP\_PRIVATE} & I cambiamenti sulla memoria mappata non vengono
- riportati sul file. Ne viene fatta una copia
- privata cui solo il processo chiamante ha
- accesso. Le modifiche sono mantenute attraverso
- il meccanismo del \textit{copy on
- write} \itindex{copy~on~write} e
- salvate su swap in caso di necessità. Non è
- specificato se i cambiamenti sul file originale
- vengano riportati sulla regione
- mappata. Incompatibile con \const{MAP\_SHARED}.\\
- \const{MAP\_DENYWRITE} & In Linux viene ignorato per evitare
- \textit{DoS} \itindex{Denial~of~Service~(DoS)}
- (veniva usato per segnalare che tentativi di
- scrittura sul file dovevano fallire con
- \errcode{ETXTBSY}).\\
- \const{MAP\_EXECUTABLE}& Ignorato.\\
- \const{MAP\_NORESERVE} & Si usa con \const{MAP\_PRIVATE}. Non riserva
- delle pagine di swap ad uso del meccanismo del
- \textit{copy on write} \itindex{copy~on~write}
- per mantenere le
- modifiche fatte alla regione mappata, in
- questo caso dopo una scrittura, se non c'è più
- memoria disponibile, si ha l'emissione di
- un \const{SIGSEGV}.\\
- \const{MAP\_LOCKED} & Se impostato impedisce lo swapping delle pagine
- mappate.\\
- \const{MAP\_GROWSDOWN} & Usato per gli \itindex{stack} \textit{stack}.
- Indica che la mappatura deve essere effettuata
- con gli indirizzi crescenti verso il basso.\\
- \const{MAP\_ANONYMOUS} & La mappatura non è associata a nessun file. Gli
- argomenti \param{fd} e \param{offset} sono
- ignorati.\footnotemark\\
- \const{MAP\_ANON} & Sinonimo di \const{MAP\_ANONYMOUS}, deprecato.\\
- \const{MAP\_FILE} & Valore di compatibilità, ignorato.\\
- \const{MAP\_32BIT} & Esegue la mappatura sui primi 2GiB dello spazio
- degli indirizzi, viene supportato solo sulle
- piattaforme \texttt{x86-64} per compatibilità con
- le applicazioni a 32 bit. Viene ignorato se si è
- richiesto \const{MAP\_FIXED}.\\
- \const{MAP\_POPULATE} & Esegue il \itindex{prefaulting}
- \textit{prefaulting} delle pagine di memoria
- necessarie alla mappatura.\\
- \const{MAP\_NONBLOCK} & Esegue un \textit{prefaulting} più limitato che
- non causa I/O.\footnotemark\\
-% \const{MAP\_DONTEXPAND}& Non consente una successiva espansione dell'area
-% mappata con \func{mremap}, proposto ma pare non
-% implementato.\\
- \hline
- \end{tabular}
- \caption{Valori possibili dell'argomento \param{flag} di \func{mmap}.}
- \label{tab:file_mmap_flag}
-\end{table}
+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.
-Gli effetti dell'accesso ad una zona di memoria mappata su file possono essere
-piuttosto complessi, essi si possono comprendere solo tenendo presente che
-tutto quanto è comunque basato sul meccanismo della \index{memoria~virtuale}
-memoria virtuale. Questo comporta allora una serie di conseguenze. La più
-ovvia è che se si cerca di scrivere su una zona mappata in sola lettura si
-avrà l'emissione di un segnale di violazione di accesso (\const{SIGSEGV}),
-dato che i permessi sul segmento di memoria relativo non consentono questo
-tipo di accesso.
+\index{file!inotify|(}
-È invece assai diversa la questione relativa agli accessi al di fuori della
-regione di cui si è richiesta la mappatura. A prima vista infatti si potrebbe
-ritenere che anch'essi debbano generare un segnale di violazione di accesso;
-questo però non tiene conto del fatto che, essendo basata sul meccanismo della
-paginazione \index{paginazione}, la mappatura in memoria non può che essere
-eseguita su un segmento di dimensioni rigorosamente multiple di quelle di una
-pagina, ed in generale queste potranno non corrispondere alle dimensioni
-effettive del file o della sezione che si vuole mappare.
+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 avere un eccesso di file aperti. Inoltre
+quando la directory che si controlla è all'interno di un dispositivo
+rimovibile, mantenere il relativo file descriptor aperto comporta
+l'impossibilità di smontare il dispositivo e di rimuoverlo, il che in genere
+complica notevolmente la gestione dell'uso di questi dispositivi.
-\footnotetext[68]{dato che tutti faranno riferimento alle stesse pagine di
- memoria.}
+Un altro problema è che l'interfaccia di \textit{dnotify} consente solo di
+tenere sotto controllo il contenuto di una directory; la modifica di un file
+viene segnalata, ma poi è necessario verificare di quale file si tratta
+(operazione che può essere molto onerosa quando una directory contiene un gran
+numero di file). 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_adv_control}. Per tutta questa serie di motivi in generale
+quella di \textit{dnotify} viene considerata una interfaccia di usabilità
+problematica.
-\footnotetext[69]{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}.}
+\index{file!dnotify|)}
-\footnotetext{questo flag ed il precedente \const{MAP\_POPULATE} sono stati
- introdotti nel kernel 2.5.46 insieme alla mappatura non lineare di cui
- parleremo più avanti.}
+Per risolvere i problemi appena illustrati è stata introdotta una nuova
+interfaccia per l'osservazione delle modifiche a file o directory, chiamata
+\textit{inotify}.\footnote{l'interfaccia è disponibile a partire dal kernel
+ 2.6.13, le relative funzioni sono state introdotte nelle glibc 2.4.} Anche
+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, il che permette
+di risolvere 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}
-\begin{figure}[!htb]
- \centering
- \includegraphics[height=6cm]{img/mmap_boundary}
- \caption{Schema della mappatura in memoria di una sezione di file di
- dimensioni non corrispondenti al bordo di una pagina.}
- \label{fig:file_mmap_boundary}
-\end{figure}
+La funzione non prende alcun argomento; inizializza una istanza di
+\textit{inotify} e restituisce un file descriptor attraverso il quale verranno
+effettuate le operazioni di notifica;\footnote{per evitare abusi delle risorse
+ di sistema è previsto che un utente possa utilizzare un numero limitato di
+ istanze di \textit{inotify}; il valore di default del limite è di 128, ma
+ questo valore può essere cambiato con \func{sysctl} o usando il file
+ \procfile{/proc/sys/fs/inotify/max\_user\_instances}.} si tratta di un file
+descriptor speciale che non è associato a nessun file su disco, e che viene
+utilizzato solo per notificare gli eventi che sono stati posti in
+osservazione. Dato che questo file descriptor non è associato a nessun file o
+directory reale, l'inconveniente di non poter smontare un filesystem i cui
+file sono tenuti sotto osservazione viene completamente
+eliminato.\footnote{anzi, una delle capacità 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
+con l'interfaccia di \textit{epoll};\footnote{ed a partire dal kernel 2.6.25 è
+ stato introdotto anche il supporto per il \itindex{signal~driven~I/O}
+ \texttt{signal-driven I/O} trattato in
+ sez.~\ref{sec:file_asyncronous_operation}.} siccome gli eventi vengono
+notificati come dati disponibili in lettura, dette funzioni ritorneranno tutte
+le volte che si avrà un evento di notifica. Così, invece di dover utilizzare i
+segnali,\footnote{considerati una pessima scelta dal punto di vista
+ dell'interfaccia utente.} si potrà gestire l'osservazione degli eventi con
+una qualunque delle modalità di \textit{I/O multiplexing} illustrate in
+sez.~\ref{sec:file_multiplexing}. Qualora si voglia cessare l'osservazione,
+sarà sufficiente chiudere il file descriptor e tutte le risorse allocate
+saranno automaticamente rilasciate.
-Il caso più comune è quello illustrato in fig.~\ref{fig:file_mmap_boundary},
-in cui la sezione di file non rientra nei confini di una pagina: in tal caso
-verrà il file sarà mappato su un segmento di memoria che si estende fino al
-bordo della pagina successiva.
+Infine l'interfaccia di \textit{inotify} consente di mettere sotto
+osservazione, oltre che una directory, anche singoli file. Una volta creata
+la coda di notifica si devono definire gli eventi da tenere sotto
+osservazione; questo viene fatto attraverso una \textsl{lista di osservazione}
+(o \textit{watch list}) che è 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)}
-In questo caso è possibile accedere a quella zona di memoria che eccede le
-dimensioni specificate da \param{lenght}, senza ottenere un \const{SIGSEGV}
-poiché essa è presente nello spazio di indirizzi del processo, anche se non è
-mappata sul file. Il comportamento del sistema è quello di restituire un
-valore nullo per quanto viene letto, e di non riportare su file quanto viene
-scritto.
+ Aggiunge un evento di osservazione alla lista di osservazione di \param{fd}.
-Un caso più complesso è quello che si viene a creare quando le dimensioni del
-file mappato sono più corte delle dimensioni della mappatura, oppure quando il
-file è stato troncato, dopo che è stato mappato, ad una dimensione inferiore a
-quella della mappatura in memoria.
+ \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 file descriptor 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}
-In questa situazione, per la sezione di pagina parzialmente coperta dal
-contenuto del file, vale esattamente quanto visto in precedenza; invece per la
-parte che eccede, fino alle dimensioni date da \param{length}, l'accesso non
-sarà più possibile, ma il segnale emesso non sarà \const{SIGSEGV}, ma
-\const{SIGBUS}, come illustrato in fig.~\ref{fig:file_mmap_exceed}.
+La funzione consente di creare un ``\textsl{osservatore}'' (il cosiddetto
+``\textit{watch}'') nella lista di osservazione di una coda di notifica, che
+deve essere indicata specificando il file descriptor ad essa associato
+nell'argomento \param{fd}.\footnote{questo ovviamente dovrà essere un file
+ descriptor creato con \func{inotify\_init}.} Il file o la directory da
+porre sotto osservazione vengono invece indicati per nome, da passare
+nell'argomento \param{pathname}. Infine il terzo argomento, \param{mask},
+indica che tipo di eventi devono essere tenuti sotto osservazione e le
+modalità della stessa. L'operazione può essere ripetuta per tutti i file e le
+directory che si vogliono tenere sotto osservazione,\footnote{anche in questo
+ caso c'è un limite massimo che di default è pari a 8192, ed anche questo
+ valore può essere cambiato con \func{sysctl} o usando il file
+ \procfile{/proc/sys/fs/inotify/max\_user\_watches}.} e si utilizzerà sempre
+un solo file descriptor.
-Non tutti i file possono venire mappati in memoria, dato che, come illustrato
-in fig.~\ref{fig:file_mmap_layout}, la mappatura introduce una corrispondenza
-biunivoca fra una sezione di un file ed una sezione di memoria. Questo
-comporta che ad esempio non è possibile mappare in memoria file descriptor
-relativi a pipe, socket e fifo, per i quali non ha senso parlare di
-\textsl{sezione}. Lo stesso vale anche per alcuni file di dispositivo, che non
-dispongono della relativa operazione \func{mmap} (si ricordi quanto esposto in
-sez.~\ref{sec:file_vfs_work}). Si tenga presente però che esistono anche casi
-di dispositivi (un esempio è l'interfaccia al ponte PCI-VME del chip Universe)
-che sono utilizzabili solo con questa interfaccia.
+Il tipo di evento che si vuole osservare deve essere specificato
+nell'argomento \param{mask} come maschera binaria, combinando i valori delle
+costanti riportate in tab.~\ref{tab:inotify_event_watch} che identificano i
+singoli bit della maschera ed il relativo significato. 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. Nella seconda
+parte della tabella si sono poi indicate alcune combinazioni predefinite dei
+flag della prima parte.
-\begin{figure}[htb]
+\begin{table}[htb]
\centering
- \includegraphics[height=6cm]{img/mmap_exceed}
- \caption{Schema della mappatura in memoria di file di dimensioni inferiori
- alla lunghezza richiesta.}
- \label{fig:file_mmap_exceed}
-\end{figure}
-
-Dato che passando attraverso una \func{fork} lo spazio di indirizzi viene
-copiato integralmente, i file mappati in memoria verranno ereditati in maniera
-trasparente dal processo figlio, mantenendo gli stessi attributi avuti nel
-padre; così se si è usato \const{MAP\_SHARED} padre e figlio accederanno allo
-stesso file in maniera condivisa, mentre se si è usato \const{MAP\_PRIVATE}
-ciascuno di essi manterrà una sua versione privata indipendente. Non c'è
-invece nessun passaggio attraverso una \func{exec}, dato che quest'ultima
-sostituisce tutto lo spazio degli indirizzi di un processo con quello di un
-nuovo programma.
-
-Quando si effettua la mappatura di un file vengono pure modificati i tempi ad
-esso associati (di cui si è trattato in sez.~\ref{sec:file_file_times}). Il
-valore di \var{st\_atime} può venir cambiato in qualunque istante a partire
-dal momento in cui la mappatura è stata effettuata: il primo riferimento ad
-una pagina mappata su un file aggiorna questo tempo. I valori di
-\var{st\_ctime} e \var{st\_mtime} possono venir cambiati solo quando si è
-consentita la scrittura sul file (cioè per un file mappato con
-\const{PROT\_WRITE} e \const{MAP\_SHARED}) e sono aggiornati dopo la scrittura
-o in corrispondenza di una eventuale \func{msync}.
-
-Dato per i file mappati in memoria le operazioni di I/O sono gestite
-direttamente dalla \index{memoria~virtuale}memoria virtuale, occorre essere
-consapevoli delle interazioni che possono esserci con operazioni effettuate
-con l'interfaccia standard dei file di cap.~\ref{cha:file_unix_interface}. Il
-problema è che una volta che si è mappato un file, le operazioni di lettura e
-scrittura saranno eseguite sulla memoria, e riportate su disco in maniera
-autonoma dal sistema della memoria virtuale.
-
-Pertanto se si modifica un file con l'interfaccia standard queste modifiche
-potranno essere visibili o meno a seconda del momento in cui la memoria
-virtuale trasporterà dal disco in memoria quella sezione del file, perciò è
-del tutto imprevedibile il risultato della modifica di un file nei confronti
-del contenuto della memoria su cui è mappato.
-
-Per questo, è sempre sconsigliabile eseguire scritture su file attraverso
-l'interfaccia standard, quando lo si è mappato in memoria, è invece possibile
-usare l'interfaccia standard per leggere un file mappato in memoria, purché si
-abbia una certa cura; infatti l'interfaccia dell'I/O mappato in memoria mette
-a disposizione la funzione \funcd{msync} per sincronizzare il contenuto della
-memoria mappata con il file su disco; il suo prototipo è:
-\begin{functions}
- \headdecl{unistd.h}
- \headdecl{sys/mman.h}
-
- \funcdecl{int msync(const void *start, size\_t length, int flags)}
-
- Sincronizza i contenuti di una sezione di un file mappato in memoria.
-
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
- errore nel qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] o \param{start} non è multiplo di
- \const{PAGE\_SIZE}, o si è specificato un valore non valido per
- \param{flags}.
- \item[\errcode{EFAULT}] l'intervallo specificato non ricade in una zona
- precedentemente mappata.
- \end{errlist}
- }
-\end{functions}
+ \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 (o sugli attributi
+ estesi, vedi
+ sez.~\ref{sec:file_xattr}).\\
+ \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
+ \const{IN\_CLOSE} & & Combinazione di
+ \const{IN\_CLOSE\_WRITE} e
+ \const{IN\_CLOSE\_NOWRITE}.\\
+ \const{IN\_MOVE} & & Combinazione di
+ \const{IN\_MOVED\_FROM} e
+ \const{IN\_MOVED\_TO}.\\
+ \const{IN\_ALL\_EVENTS} & & Combinazione di tutti i flag
+ possibili.\\
+ \hline
+ \end{tabular}
+ \caption{Le costanti che identificano i bit della maschera binaria
+ dell'argomento \param{mask} di \func{inotify\_add\_watch} che indicano il
+ tipo di evento da tenere sotto osservazione.}
+ \label{tab:inotify_event_watch}
+\end{table}
-La funzione esegue la sincronizzazione di quanto scritto nella sezione di
-memoria indicata da \param{start} e \param{offset}, scrivendo le modifiche sul
-file (qualora questo non sia già stato fatto). Provvede anche ad aggiornare i
-relativi tempi di modifica. In questo modo si è sicuri che dopo l'esecuzione
-di \func{msync} le funzioni dell'interfaccia standard troveranno un contenuto
-del file aggiornato.
+Oltre ai flag di tab.~\ref{tab:inotify_event_watch}, che indicano il tipo di
+evento da osservare e che vengono utilizzati anche in uscita per indicare il
+tipo di evento avvenuto, \func{inotify\_add\_watch} supporta ulteriori
+flag,\footnote{i flag \const{IN\_DONT\_FOLLOW}, \const{IN\_MASK\_ADD} e
+ \const{IN\_ONLYDIR} sono stati introdotti a partire dalle glibc 2.5, se si
+ usa la versione 2.4 è necessario definirli a mano.} riportati in
+tab.~\ref{tab:inotify_add_watch_flag}, che indicano le modalità di
+osservazione (da passare sempre nell'argomento \param{mask}) e che al
+contrario dei precedenti non vengono mai impostati nei risultati in uscita.
\begin{table}[htb]
\centering
\footnotesize
- \begin{tabular}[c]{|l|l|}
+ \begin{tabular}[c]{|l|p{10cm}|}
\hline
- \textbf{Valore} & \textbf{Significato} \\
+ \textbf{Valore} & \textbf{Significato} \\
\hline
\hline
- \const{MS\_ASYNC} & Richiede la sincronizzazione.\\
- \const{MS\_SYNC} & Attende che la sincronizzazione si eseguita.\\
- \const{MS\_INVALIDATE}& Richiede che le altre mappature dello stesso file
- siano invalidate.\\
+ \const{IN\_DONT\_FOLLOW}& Non dereferenzia \param{pathname} se questo è un
+ link simbolico.\\
+ \const{IN\_MASK\_ADD} & Aggiunge a quelli già impostati i flag indicati
+ nell'argomento \param{mask}, invece di
+ sovrascriverli.\\
+ \const{IN\_ONESHOT} & Esegue l'osservazione su \param{pathname} per una
+ sola volta, rimuovendolo poi dalla \textit{watch
+ list}.\\
+ \const{IN\_ONLYDIR} & Se \param{pathname} è una directory riporta
+ soltanto gli eventi ad essa relativi e non
+ quelli per i file che contiene.\\
\hline
\end{tabular}
- \caption{Le costanti che identificano i bit per la maschera binaria
- dell'argomento \param{flag} di \func{msync}.}
- \label{tab:file_mmap_rsync}
+ \caption{Le costanti che identificano i bit della maschera binaria
+ dell'argomento \param{mask} di \func{inotify\_add\_watch} che indicano le
+ modalità di osservazione.}
+ \label{tab:inotify_add_watch_flag}
\end{table}
-L'argomento \param{flag} è specificato come maschera binaria composta da un OR
-dei valori riportati in tab.~\ref{tab:file_mmap_rsync}, di questi però
-\const{MS\_ASYNC} e \const{MS\_SYNC} sono incompatibili; con il primo valore
-infatti la funzione si limita ad inoltrare la richiesta di sincronizzazione al
-meccanismo della memoria virtuale, ritornando subito, mentre con il secondo
-attende che la sincronizzazione sia stata effettivamente eseguita. Il terzo
-flag fa invalidare le pagine di cui si richiede la sincronizzazione per tutte
-le mappature dello stesso file, così che esse possano essere immediatamente
-aggiornate ai nuovi valori.
+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, a meno che
+non si sia usato il flag \const{IN\_MASK\_ADD}, nel qual caso gli eventi
+specificati saranno aggiunti a quelli già presenti.
-Una volta che si sono completate le operazioni di I/O si può eliminare la
-mappatura della memoria usando la funzione \funcd{munmap}, il suo prototipo è:
-\begin{functions}
- \headdecl{unistd.h}
- \headdecl{sys/mman.h}
+Come accennato quando si tiene sotto osservazione una directory vengono
+restituite le informazioni sia riguardo alla directory stessa che ai file che
+essa contiene; questo comportamento può essere disabilitato utilizzando il
+flag \const{IN\_ONLYDIR}, che richiede di riportare soltanto gli eventi
+relativi alla directory stessa. Si tenga presente inoltre che quando si
+osserva una directory vengono riportati solo gli eventi sui file che essa
+contiene direttamente, non quelli relativi a file contenuti in eventuali
+sottodirectory; se si vogliono osservare anche questi sarà necessario creare
+ulteriori \textit{watch} per ciascuna sottodirectory.
- \funcdecl{int munmap(void *start, size\_t length)}
+Infine usando il flag \const{IN\_ONESHOT} è possibile richiedere una notifica
+singola;\footnote{questa funzionalità però è disponibile soltanto a partire dal
+ kernel 2.6.16.} una volta verificatosi uno qualunque fra gli eventi
+richiesti con \func{inotify\_add\_watch} l'\textsl{osservatore} verrà
+automaticamente rimosso dalla lista di osservazione e nessun ulteriore evento
+sarà più notificato.
+
+In caso di successo \func{inotify\_add\_watch} ritorna un intero positivo,
+detto \textit{watch descriptor}, che identifica univocamente un
+\textsl{osservatore} su una coda di notifica; esso viene usato per farvi
+riferimento sia riguardo i risultati restituiti da \textit{inotify}, che per
+la eventuale rimozione dello stesso.
+
+La seconda funzione per la gestione delle code di notifica, che permette di
+rimuovere un \textsl{osservatore}, è \funcd{inotify\_rm\_watch}, ed il suo
+prototipo è:
+\begin{prototype}{sys/inotify.h}
+ {int inotify\_rm\_watch(int fd, uint32\_t wd)}
+
+ Rimuove un \textsl{osservatore} da una coda di notifica.
- Rilascia la mappatura sulla sezione di memoria specificata.
+ \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}
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
- errore nel qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] l'intervallo specificato non ricade in una zona
- precedentemente mappata.
- \end{errlist}
- }
-\end{functions}
+La funzione rimuove dalla coda di notifica identificata dall'argomento
+\param{fd} l'osservatore identificato dal \textit{watch descriptor}
+\param{wd};\footnote{ovviamente deve essere usato per questo argomento un
+ valore ritornato da \func{inotify\_add\_watch}, altrimenti si avrà un errore
+ di \errval{EINVAL}.} in caso di successo della rimozione, contemporaneamente
+alla cancellazione dell'osservatore, sulla coda di notifica verrà generato un
+evento di tipo \const{IN\_IGNORED} (vedi
+tab.~\ref{tab:inotify_read_event_flag}). Si tenga presente che se un file
+viene cancellato o un filesystem viene smontato i relativi osservatori vengono
+rimossi automaticamente e non è necessario utilizzare
+\func{inotify\_rm\_watch}.
-La funzione cancella la mappatura per l'intervallo specificato con
-\param{start} e \param{length}; ogni successivo accesso a tale regione causerà
-un errore di accesso in memoria. L'argomento \param{start} deve essere
-allineato alle dimensioni di una pagina, e la mappatura di tutte le pagine
-contenute anche parzialmente nell'intervallo indicato, verrà rimossa.
-Indicare un intervallo che non contiene mappature non è un errore. Si tenga
-presente inoltre che alla conclusione di un processo ogni pagina mappata verrà
-automaticamente rilasciata, mentre la chiusura del file descriptor usato per
-il \textit{memory mapping} non ha alcun effetto su di esso.
+Come accennato l'interfaccia di \textit{inotify} prevede che gli eventi siano
+notificati come dati presenti in lettura sul file descriptor associato alla
+coda di notifica. Una applicazione pertanto dovrà leggere i dati da detto file
+con una \func{read}, che ritornerà sul buffer i dati presenti nella forma di
+una o più strutture di tipo \struct{inotify\_event} (la cui definizione è
+riportata in fig.~\ref{fig:inotify_event}). Qualora non siano presenti dati la
+\func{read} si bloccherà (a meno di non aver impostato il file descriptor in
+modalità non bloccante) fino all'arrivo di almeno un evento.
-Lo standard POSIX prevede anche una funzione che permetta di cambiare le
-protezioni delle pagine di memoria; lo standard prevede che essa si applichi
-solo ai \textit{memory mapping} creati con \func{mmap}, ma nel caso di Linux
-la funzione può essere usata con qualunque pagina valida nella memoria
-virtuale. Questa funzione è \funcd{mprotect} ed il suo prototipo è:
-\begin{functions}
-% \headdecl{unistd.h}
- \headdecl{sys/mman.h}
+\begin{figure}[!htb]
+ \footnotesize \centering
+ \begin{minipage}[c]{15cm}
+ \includestruct{listati/inotify_event.h}
+ \end{minipage}
+ \normalsize
+ \caption{La struttura \structd{inotify\_event} usata dall'interfaccia di
+ \textit{inotify} per riportare gli eventi.}
+ \label{fig:inotify_event}
+\end{figure}
- \funcdecl{int mprotect(const void *addr, size\_t len, int prot)}
-
- Modifica le protezioni delle pagine di memoria comprese nell'intervallo
- specificato.
+Una ulteriore caratteristica dell'interfaccia di \textit{inotify} è che essa
+permette di ottenere con \func{ioctl}, come per i file descriptor associati ai
+socket (si veda sez.~\ref{sec:sock_ioctl_IP}) il numero di byte disponibili in
+lettura sul file descriptor, utilizzando su di esso l'operazione
+\const{FIONREAD}.\footnote{questa è una delle operazioni speciali per i file
+ (vedi sez.~\ref{sec:file_ioctl}), che è disponibile solo per i socket e per
+ i file descriptor creati con \func{inotify\_init}.} Si può così utilizzare
+questa operazione, oltre che per predisporre una operazione di lettura con un
+buffer di dimensioni adeguate, anche per ottenere rapidamente il numero di
+file che sono cambiati.
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
- errore nel qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] il valore di \param{addr} non è valido o non è un
- multiplo di \const{PAGE\_SIZE}.
- \item[\errcode{EACCESS}] l'operazione non è consentita, ad esempio si è
- cercato di marcare con \const{PROT\_WRITE} un segmento di memoria cui si
- ha solo accesso in lettura.
-% \item[\errcode{ENOMEM}] non è stato possibile allocare le risorse
-% necessarie all'interno del kernel.
-% \item[\errcode{EFAULT}] si è specificato un indirizzo di memoria non
-% accessibile.
- \end{errlist}
- ed inoltre \errval{ENOMEM} ed \errval{EFAULT}.
- }
-\end{functions}
+Una volta effettuata la lettura con \func{read} a ciascun evento sarà
+associata una struttura \struct{inotify\_event} contenente i rispettivi dati.
+Per identificare a quale file o directory l'evento corrisponde viene
+restituito nel campo \var{wd} il \textit{watch descriptor} con cui il relativo
+osservatore è stato registrato. Il campo \var{mask} contiene invece una
+maschera di bit che identifica il tipo di evento verificatosi; in essa
+compariranno sia i bit elencati nella prima parte di
+tab.~\ref{tab:inotify_event_watch}, che gli eventuali valori
+aggiuntivi\footnote{questi compaiono solo nel campo \var{mask} di
+ \struct{inotify\_event}, e non utilizzabili in fase di registrazione
+ dell'osservatore.} di tab.~\ref{tab:inotify_read_event_flag}.
+
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|p{10cm}|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{IN\_IGNORED} & L'osservatore è stato rimosso, sia in maniera
+ esplicita con l'uso di \func{inotify\_rm\_watch},
+ che in maniera implicita per la rimozione
+ dell'oggetto osservato o per lo smontaggio del
+ filesystem su cui questo si trova.\\
+ \const{IN\_ISDIR} & L'evento avvenuto fa riferimento ad una directory
+ (consente così di distinguere, quando si pone
+ sotto osservazione una directory, fra gli eventi
+ relativi ad essa e quelli relativi ai file che
+ essa contiene).\\
+ \const{IN\_Q\_OVERFLOW}& Si sono eccedute le dimensioni della coda degli
+ eventi (\textit{overflow} della coda); in questo
+ caso il valore di \var{wd} è $-1$.\footnotemark\\
+ \const{IN\_UNMOUNT} & Il filesystem contenente l'oggetto posto sotto
+ osservazione è stato smontato.\\
+ \hline
+ \end{tabular}
+ \caption{Le costanti che identificano i bit aggiuntivi usati nella maschera
+ binaria del campo \var{mask} di \struct{inotify\_event}.}
+ \label{tab:inotify_read_event_flag}
+\end{table}
+\footnotetext{la coda di notifica ha una dimensione massima specificata dal
+ parametro di sistema \procfile{/proc/sys/fs/inotify/max\_queued\_events} che
+ indica il numero massimo di eventi che possono essere mantenuti sulla
+ stessa; quando detto valore viene ecceduto gli ulteriori eventi vengono
+ scartati, ma viene comunque generato un evento di tipo
+ \const{IN\_Q\_OVERFLOW}.}
-La funzione prende come argomenti un indirizzo di partenza in \param{addr},
-allineato alle dimensioni delle pagine di memoria, ed una dimensione
-\param{size}. La nuova protezione deve essere specificata in \param{prot} con
-una combinazione dei valori di tab.~\ref{tab:file_mmap_prot}. La nuova
-protezione verrà applicata a tutte le pagine contenute, anche parzialmente,
-dall'intervallo fra \param{addr} e \param{addr}+\param{size}-1.
+Il campo \var{cookie} contiene invece un intero univoco che permette di
+identificare eventi correlati (per i quali avrà lo stesso valore), al momento
+viene utilizzato soltanto per rilevare lo spostamento di un file, consentendo
+così all'applicazione di collegare la corrispondente coppia di eventi
+\const{IN\_MOVED\_TO} e \const{IN\_MOVED\_FROM}.
-Infine Linux supporta alcune operazioni specifiche non disponibili su altri
-kernel unix-like. La prima di queste è la possibilità di modificare un
-precedente \textit{memory mapping}, ad esempio per espanderlo o restringerlo.
-Questo è realizzato dalla funzione \funcd{mremap}, il cui prototipo è:
-\begin{functions}
- \headdecl{unistd.h}
- \headdecl{sys/mman.h}
+Infine due campi \var{name} e \var{len} sono utilizzati soltanto quando
+l'evento è relativo ad un file presente in una directory posta sotto
+osservazione, in tal caso essi contengono rispettivamente il nome del file
+(come pathname relativo alla directory osservata) e la relativa dimensione in
+byte. Il campo \var{name} viene sempre restituito come stringa terminata da
+NUL, con uno o più zeri di terminazione, a seconda di eventuali necessità di
+allineamento del risultato, ed il valore di \var{len} corrisponde al totale
+della dimensione di \var{name}, zeri aggiuntivi compresi. La stringa con il
+nome del file viene restituita nella lettura subito dopo la struttura
+\struct{inotify\_event}; questo significa che le dimensioni di ciascun evento
+di \textit{inotify} saranno pari a \code{sizeof(\struct{inotify\_event}) +
+ len}.
- \funcdecl{void * mremap(void *old\_address, size\_t old\_size , size\_t
- new\_size, unsigned long flags)}
-
- Restringe o allarga una mappatura in memoria di un file.
+Vediamo allora un esempio dell'uso dell'interfaccia di \textit{inotify} con un
+semplice programma che permette di mettere sotto osservazione uno o più file e
+directory. Il programma si chiama \texttt{inotify\_monitor.c} ed il codice
+completo è disponibile coi sorgenti allegati alla guida, il corpo principale
+del programma, che non contiene la sezione di gestione delle opzioni e le
+funzioni di ausilio è riportato in fig.~\ref{fig:inotify_monitor_example}.
- \bodydesc{La funzione restituisce l'indirizzo alla nuova area di memoria in
- caso di successo od il valore \const{MAP\_FAILED} (pari a \texttt{(void *)
- -1}) in caso di errore, nel qual caso \var{errno} assumerà uno dei
- valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] il valore di \param{old\_address} non è un
- puntatore valido.
- \item[\errcode{EFAULT}] ci sono indirizzi non validi nell'intervallo
- specificato da \param{old\_address} e \param{old\_size}, o ci sono altre
- mappature di tipo non corrispondente a quella richiesta.
- \item[\errcode{ENOMEM}] non c'è memoria sufficiente oppure l'area di
- memoria non può essere espansa all'indirizzo virtuale corrente, e non si
- è specificato \const{MREMAP\_MAYMOVE} nei flag.
- \item[\errcode{EAGAIN}] il segmento di memoria scelto è bloccato e non può
- essere rimappato.
- \end{errlist}
- }
-\end{functions}
+\begin{figure}[!htbp]
+ \footnotesize \centering
+ \begin{minipage}[c]{15cm}
+ \includecodesample{listati/inotify_monitor.c}
+ \end{minipage}
+ \normalsize
+ \caption{Esempio di codice che usa l'interfaccia di \textit{inotify}.}
+ \label{fig:inotify_monitor_example}
+\end{figure}
-La funzione richiede come argomenti \param{old\_address} (che deve essere
-allineato alle dimensioni di una pagina di memoria) che specifica il
-precedente indirizzo del \textit{memory mapping} e \param{old\_size}, che ne
-indica la dimensione. Con \param{new\_size} si specifica invece la nuova
-dimensione che si vuole ottenere. Infine l'argomento \param{flags} è una
-maschera binaria per i flag che controllano il comportamento della funzione.
-Il solo valore utilizzato è \const{MREMAP\_MAYMOVE}\footnote{per poter
- utilizzare questa costante occorre aver definito \macro{\_GNU\_SOURCE} prima
- di includere \file{sys/mman.h}.} che consente di eseguire l'espansione
-anche quando non è possibile utilizzare il precedente indirizzo. Per questo
-motivo, se si è usato questo flag, la funzione può restituire un indirizzo
-della nuova zona di memoria che non è detto coincida con \param{old\_address}.
+Una volta completata la scansione delle opzioni il corpo principale del
+programma inizia controllando (\texttt{\small 11--15}) che sia rimasto almeno
+un argomento che indichi quale file o directory mettere sotto osservazione (e
+qualora questo non avvenga esce stampando la pagina di aiuto); dopo di che
+passa (\texttt{\small 16--20}) all'inizializzazione di \textit{inotify}
+ottenendo con \func{inotify\_init} il relativo file descriptor (oppure usce in
+caso di errore).
-La funzione si appoggia al sistema della \index{memoria~virtuale} memoria
-virtuale per modificare l'associazione fra gli indirizzi virtuali del processo
-e le pagine di memoria, modificando i dati direttamente nella
-\itindex{page~table} \textit{page table} del processo. Come per
-\func{mprotect} la funzione può essere usata in generale, anche per pagine di
-memoria non corrispondenti ad un \textit{memory mapping}, e consente così di
-implementare la funzione \func{realloc} in maniera molto efficiente.
+Il passo successivo è aggiungere (\texttt{\small 21--30}) alla coda di
+notifica gli opportuni osservatori per ciascuno dei file o directory indicati
+all'invocazione del comando; questo viene fatto eseguendo un ciclo
+(\texttt{\small 22--29}) fintanto che la variabile \var{i}, inizializzata a
+zero (\texttt{\small 21}) all'inizio del ciclo, è minore del numero totale di
+argomenti rimasti. All'interno del ciclo si invoca (\texttt{\small 23})
+\func{inotify\_add\_watch} per ciascuno degli argomenti, usando la maschera
+degli eventi data dalla variabile \var{mask} (il cui valore viene impostato
+nella scansione delle opzioni), in caso di errore si esce dal programma
+altrimenti si incrementa l'indice (\texttt{\small 29}).
-Una caratteristica comune a tutti i sistemi unix-like è che la mappatura in
-memoria di un file viene eseguita in maniera lineare, cioè parti successive di
-un file vengono mappate linearmente su indirizzi successivi in memoria.
-Esistono però delle applicazioni\footnote{in particolare la tecnica è usata
- dai database o dai programmi che realizzano macchine virtuali.} in cui è
-utile poter mappare sezioni diverse di un file su diverse zone di memoria.
+Completa l'inizializzazione di \textit{inotify} inizia il ciclo principale
+(\texttt{\small 32--56}) del programma, nel quale si resta in attesa degli
+eventi che si intendono osservare. Questo viene fatto eseguendo all'inizio del
+ciclo (\texttt{\small 33}) una \func{read} che si bloccherà fintanto che non
+si saranno verificati eventi.
-Questo è ovviamente sempre possibile eseguendo ripetutamente la funzione
-\func{mmap} per ciascuna delle diverse aree del file che si vogliono mappare
-in sequenza non lineare,\footnote{ed in effetti è quello che veniva fatto
- anche con Linux prima che fossero introdotte queste estensioni.} ma questo
-approccio ha delle conseguenze molto pesanti in termini di prestazioni.
-Infatti per ciascuna mappatura in memoria deve essere definita nella
-\itindex{page~table} \textit{page table} del processo una nuova area di
-memoria virtuale\footnote{quella che nel gergo del kernel viene chiamata VMA
- (\textit{virtual memory area}).} che corrisponda alla mappatura, in modo che
-questa diventi visibile nello spazio degli indirizzi come illustrato in
-fig.~\ref{fig:file_mmap_layout}.
+Dato che l'interfaccia di \textit{inotify} può riportare anche più eventi in
+una sola lettura, si è avuto cura di passare alla \func{read} un buffer di
+dimensioni adeguate, inizializzato in (\texttt{\small 7}) ad un valore di
+approssimativamente 512 eventi.\footnote{si ricordi che la quantità di dati
+ restituita da \textit{inotify} è variabile a causa della diversa lunghezza
+ del nome del file restituito insieme a \struct{inotify\_event}.} In caso di
+errore di lettura (\texttt{\small 35--40}) il programma esce con un messaggio
+di errore (\texttt{\small 37--39}), a meno che non si tratti di una
+interruzione della system call, nel qual caso (\texttt{\small 36}) si ripete la
+lettura.
-Quando un processo esegue un gran numero di mappature diverse\footnote{si può
- arrivare anche a centinaia di migliaia.} per realizzare a mano una mappatura
-non-lineare si avrà un accrescimento eccessivo della sua \itindex{page~table}
-\textit{page table}, e lo stesso accadrà per tutti gli altri processi che
-utilizzano questa tecnica. In situazioni in cui le applicazioni hanno queste
-esigenze si avranno delle prestazioni ridotte, dato che il kernel dovrà
-impiegare molte risorse\footnote{sia in termini di memoria interna per i dati
- delle \itindex{page~table} \textit{page table}, che di CPU per il loro
- aggiornamento.} solo per mantenere i dati di una gran quantità di
-\textit{memory mapping}.
+Se la lettura è andata a buon fine invece si esegue un ciclo (\texttt{\small
+ 43--52}) per leggere tutti gli eventi restituiti, al solito si inizializza
+l'indice \var{i} a zero (\texttt{\small 42}) e si ripetono le operazioni
+(\texttt{\small 43}) fintanto che esso non supera il numero di byte restituiti
+in lettura. Per ciascun evento all'interno del ciclo si assegna\footnote{si
+ noti come si sia eseguito un opportuno \textit{casting} del puntatore.} alla
+variabile \var{event} l'indirizzo nel buffer della corrispondente struttura
+\struct{inotify\_event} (\texttt{\small 44}), e poi si stampano il numero di
+\textit{watch descriptor} (\texttt{\small 45}) ed il file a cui questo fa
+riferimento (\texttt{\small 46}), ricavato dagli argomenti passati a riga di
+comando sfruttando il fatto che i \textit{watch descriptor} vengono assegnati
+in ordine progressivo crescente a partire da 1.
-Per questo motivo con il kernel 2.5.46 è stato introdotto, ad opera di Ingo
-Molnar, un meccanismo che consente la mappatura non-lineare. Anche questa è
-una caratteristica specifica di Linux, non presente in altri sistemi
-unix-like. Diventa così possibile utilizzare una sola mappatura
-iniziale\footnote{e quindi una sola \textit{virtual memory area} nella
- \itindex{page~table} \textit{page table} del processo.} e poi rimappare a
-piacere all'interno di questa i dati del file. Ciò è possibile grazie ad una
-nuova system call, \funcd{remap\_file\_pages}, il cui prototipo è:
-\begin{functions}
- \headdecl{sys/mman.h}
+Qualora sia presente il riferimento ad un nome di file associato all'evento lo
+si stampa (\texttt{\small 47--49}); si noti come in questo caso si sia
+utilizzato il valore del campo \var{event->len} e non al fatto che
+\var{event->name} riporti o meno un puntatore nullo.\footnote{l'interfaccia
+ infatti, qualora il nome non sia presente, non avvalora il campo
+ \var{event->name}, che si troverà a contenere quello che era precedentemente
+ presente nella rispettiva locazione di memoria, nel caso più comune il
+ puntatore al nome di un file osservato in precedenza.} Si utilizza poi
+(\texttt{\small 50}) la funzione \code{printevent}, che interpreta il valore
+del campo \var{event->mask} per stampare il tipo di eventi
+accaduti.\footnote{per il relativo codice, che non riportiamo in quanto non
+ essenziale alla comprensione dell'esempio, si possono utilizzare direttamente
+ i sorgenti allegati alla guida.} Infine (\texttt{\small 51}) si provvede ad
+aggiornare l'indice \var{i} per farlo puntare all'evento successivo.
- \funcdecl{int remap\_file\_pages(void *start, size\_t size, int prot,
- ssize\_t pgoff, int flags)}
-
- Permette di rimappare non linearmente un precedente \textit{memory mapping}.
+Se adesso usiamo il programma per mettere sotto osservazione una directory, e
+da un altro terminale eseguiamo il comando \texttt{ls} otterremo qualcosa del
+tipo di:
+\begin{verbatim}
+piccardi@gethen:~/gapil/sources$ ./inotify_monitor -a /home/piccardi/gapil/
+Watch descriptor 1
+Observed event on /home/piccardi/gapil/
+IN_OPEN,
+Watch descriptor 1
+Observed event on /home/piccardi/gapil/
+IN_CLOSE_NOWRITE,
+\end{verbatim}
- \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di
- errore, nel qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] si è usato un valore non valido per uno degli
- argomenti o \param{start} non fa riferimento ad un \textit{memory
- mapping} valido creato con \const{MAP\_SHARED}.
- \end{errlist}
- }
-\end{functions}
+I lettori più accorti si saranno resi conto che nel ciclo di lettura degli
+eventi appena illustrato non viene trattato il caso particolare in cui la
+funzione \func{read} restituisce in \var{nread} un valore nullo. Lo si è fatto
+perché con \textit{inotify} il ritorno di una \func{read} con un valore nullo
+avviene soltanto, come forma di avviso, quando si sia eseguita la funzione
+specificando un buffer di dimensione insufficiente a contenere anche un solo
+evento. Nel nostro caso le dimensioni erano senz'altro sufficienti, per cui
+tale evenienza non si verificherà mai.
-Per poter utilizzare questa funzione occorre anzitutto effettuare
-preliminarmente una chiamata a \func{mmap} con \const{MAP\_SHARED} per
-definire l'area di memoria che poi sarà rimappata non linearmente. Poi di
-chiamerà questa funzione per modificare le corrispondenze fra pagine di
-memoria e pagine del file; si tenga presente che \func{remap\_file\_pages}
-permette anche di mappare la stessa pagina di un file in più pagine della
-regione mappata.
+Ci si potrà però chiedere cosa succede se il buffer è sufficiente per un
+evento, ma non per tutti gli eventi verificatisi. Come si potrà notare nel
+codice illustrato in precedenza non si è presa nessuna precauzione per
+verificare che non ci fossero stati troncamenti dei dati. Anche in questo caso
+il comportamento scelto è corretto, perché l'interfaccia di \textit{inotify}
+garantisce automaticamente, anche quando ne sono presenti in numero maggiore,
+di restituire soltanto il numero di eventi che possono rientrare completamente
+nelle dimensioni del buffer specificato.\footnote{si avrà cioè, facendo
+ riferimento sempre al codice di fig.~\ref{fig:inotify_monitor_example}, che
+ \var{read} sarà in genere minore delle dimensioni di \var{buffer} ed uguale
+ soltanto qualora gli eventi corrispondano esattamente alle dimensioni di
+ quest'ultimo.} Se gli eventi sono di più saranno restituiti solo quelli che
+entrano interamente nel buffer e gli altri saranno restituiti alla successiva
+chiamata di \func{read}.
-La funzione richiede che si identifichi la sezione del file che si vuole
-riposizionare all'interno del \textit{memory mapping} con gli argomenti
-\param{pgoff} e \param{size}; l'argomento \param{start} invece deve indicare
-un indirizzo all'interno dell'area definita dall'\func{mmap} iniziale, a
-partire dal quale la sezione di file indicata verrà rimappata. L'argomento
-\param{prot} deve essere sempre nullo, mentre \param{flags} prende gli stessi
-valori di \func{mmap} (quelli di tab.~\ref{tab:file_mmap_prot}) ma di tutti i
-flag solo \const{MAP\_NONBLOCK} non viene ignorato.
+Infine un'ultima caratteristica dell'interfaccia di \textit{inotify} è che gli
+eventi restituiti nella lettura formano una sequenza ordinata, è cioè
+garantito che se si esegue uno spostamento di un file gli eventi vengano
+generati nella sequenza corretta. L'interfaccia garantisce anche che se si
+verificano più eventi consecutivi identici (vale a dire con gli stessi valori
+dei campi \var{wd}, \var{mask}, \var{cookie}, e \var{name}) questi vengono
+raggruppati in un solo evento.
-Insieme alla funzione \func{remap\_file\_pages} nel kernel 2.5.46 con sono
-stati introdotti anche due nuovi flag per \func{mmap}: \const{MAP\_POPULATE} e
-\const{MAP\_NONBLOCK}. Il primo dei due consente di abilitare il meccanismo
-del \itindex{prefaulting} \textit{prefaulting}. Questo viene di nuovo in aiuto
-per migliorare le prestazioni in certe condizioni di utilizzo del
-\textit{memory mapping}.
+\index{file!inotify|)}
-Il problema si pone tutte le volte che si vuole mappare in memoria un file di
-grosse dimensioni. Il comportamento normale del sistema della
-\index{memoria~virtuale} memoria virtuale è quello per cui la regione mappata
-viene aggiunta alla \itindex{page~table} \textit{page table} del processo, ma
-i dati verranno effettivamente utilizzati (si avrà cioè un
-\itindex{page~fault} \textit{page fault} che li trasferisce dal disco alla
-memoria) soltanto in corrispondenza dell'accesso a ciascuna delle pagine
-interessate dal \textit{memory mapping}.
+% TODO trattare fanotify, vedi http://lwn.net/Articles/339399/ e
+% http://lwn.net/Articles/343346/
-Questo vuol dire che il passaggio dei dati dal disco alla memoria avverrà una
-pagina alla volta con un gran numero di \itindex{page~fault} \textit{page
- fault}, chiaramente se si sa in anticipo che il file verrà utilizzato
-immediatamente, è molto più efficiente eseguire un \itindex{prefaulting}
-\textit{prefaulting} in cui tutte le pagine di memoria interessate alla
-mappatura vengono ``\textsl{popolate}'' in una sola volta, questo
-comportamento viene abilitato quando si usa con \func{mmap} il flag
-\const{MAP\_POPULATE}.
-Dato che l'uso di \const{MAP\_POPULATE} comporta dell'I/O su disco che può
-rallentare l'esecuzione di \func{mmap} è stato introdotto anche un secondo
-flag, \const{MAP\_NONBLOCK}, che esegue un \itindex{prefaulting}
-\textit{prefaulting} più limitato in cui vengono popolate solo le pagine della
-mappatura che già si trovano nella cache del kernel.\footnote{questo può
- essere utile per il linker dinamico, in particolare quando viene effettuato
- il \textit{prelink} delle applicazioni.}
+\subsection{L'interfaccia POSIX per l'I/O asincrono}
+\label{sec:file_asyncronous_io}
-\itindend{memory~mapping}
+Una modalità alternativa all'uso dell'\textit{I/O multiplexing} per gestione
+dell'I/O simultaneo su molti file è costituita dal cosiddetto \textsl{I/O
+ asincrono}. Il concetto base dell'\textsl{I/O asincrono} è che le funzioni
+di I/O non attendono il completamento delle operazioni prima di ritornare,
+così che il processo non viene bloccato. In questo modo diventa ad esempio
+possibile effettuare una richiesta preventiva di dati, in modo da poter
+effettuare in contemporanea le operazioni di calcolo e quelle di I/O.
-% TODO documentare \func{madvise}
+Benché la modalità di apertura asincrona di un file possa risultare utile in
+varie occasioni (in particolar modo con i socket e gli altri file per i quali
+le funzioni di I/O sono \index{system~call~lente} system call lente), essa è
+comunque limitata alla notifica della disponibilità del file descriptor per le
+operazioni di I/O, e non ad uno svolgimento asincrono delle medesime. Lo
+standard POSIX.1b definisce una interfaccia apposita per l'I/O asincrono vero
+e proprio, che prevede un insieme di funzioni dedicate per la lettura e la
+scrittura dei file, completamente separate rispetto a quelle usate
+normalmente.
-\subsection{I/O vettorizzato: \func{readv} e \func{writev}}
-\label{sec:file_multiple_io}
+In generale questa interfaccia è completamente astratta e può essere
+implementata sia direttamente nel kernel, che in user space attraverso l'uso
+di \itindex{thread} \textit{thread}. Per le versioni del kernel meno recenti
+esiste una implementazione di questa interfaccia fornita delle \acr{glibc},
+che è realizzata completamente in user space, ed è accessibile linkando i
+programmi con la libreria \file{librt}. Nelle versioni più recenti (a partire
+dalla 2.5.32) è stato introdotto direttamente nel kernel un nuovo layer per
+l'I/O asincrono.
-Un caso abbastanza comune è quello in cui ci si trova a dover eseguire una
-serie multipla di operazioni di I/O, come una serie di letture o scritture di
-vari buffer. Un esempio tipico è quando i dati sono strutturati nei campi di
-una struttura ed essi devono essere caricati o salvati su un file. Benché
-l'operazione sia facilmente eseguibile attraverso una serie multipla di
-chiamate, ci sono casi in cui si vuole poter contare sulla atomicità delle
-operazioni.
+Lo standard prevede che tutte le operazioni di I/O asincrono siano controllate
+attraverso l'uso di una apposita struttura \struct{aiocb} (il cui nome sta per
+\textit{asyncronous I/O control block}), che viene passata come argomento a
+tutte le funzioni dell'interfaccia. La sua definizione, come effettuata in
+\file{aio.h}, è riportata in fig.~\ref{fig:file_aiocb}. Nello steso file è
+definita la macro \macro{\_POSIX\_ASYNCHRONOUS\_IO}, che dichiara la
+disponibilità dell'interfaccia per l'I/O asincrono.
-Per questo motivo su BSD 4.2 sono state introdotte due nuove system call,
-\funcd{readv} e \funcd{writev},\footnote{in Linux le due funzioni sono riprese
- da BSD4.4, esse sono previste anche dallo standard POSIX.1-2001.} che
-permettono di effettuare con una sola chiamata una lettura o una scrittura su
-una serie di buffer (quello che viene chiamato \textsl{I/O vettorizzato}. I
-relativi prototipi sono:
-\begin{functions}
- \headdecl{sys/uio.h}
-
- \funcdecl{int readv(int fd, const struct iovec *vector, int count)}
- \funcdecl{int writev(int fd, const struct iovec *vector, int count)}
+\begin{figure}[!htb]
+ \footnotesize \centering
+ \begin{minipage}[c]{15cm}
+ \includestruct{listati/aiocb.h}
+ \end{minipage}
+ \normalsize
+ \caption{La struttura \structd{aiocb}, usata per il controllo dell'I/O
+ asincrono.}
+ \label{fig:file_aiocb}
+\end{figure}
- Eseguono rispettivamente una lettura o una scrittura vettorizzata.
-
- \bodydesc{Le funzioni restituiscono il numero di byte letti o scritti in
- caso di successo, e -1 in caso di errore, nel qual caso \var{errno}
- assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] si è specificato un valore non valido per uno degli
- argomenti (ad esempio \param{count} è maggiore di \const{IOV\_MAX}).
- \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima di
- di avere eseguito una qualunque lettura o scrittura.
- \item[\errcode{EAGAIN}] \param{fd} è stato aperto in modalità non bloccante e
- non ci sono dati in lettura.
- \item[\errcode{EOPNOTSUPP}] la coda delle richieste è momentaneamente piena.
- \end{errlist}
- ed anche \errval{EISDIR}, \errval{EBADF}, \errval{ENOMEM}, \errval{EFAULT}
- (se non sono stati allocati correttamente i buffer specificati nei campi
- \var{iov\_base}), più gli eventuali errori delle funzioni di lettura e
- scrittura eseguite su \param{fd}.}
-\end{functions}
+Le operazioni di I/O asincrono possono essere effettuate solo su un file già
+aperto; il file deve inoltre supportare la funzione \func{lseek}, pertanto
+terminali e pipe sono esclusi. Non c'è limite al numero di operazioni
+contemporanee effettuabili su un singolo file. Ogni operazione deve
+inizializzare opportunamente un \textit{control block}. Il file descriptor su
+cui operare deve essere specificato tramite il campo \var{aio\_fildes}; dato
+che più operazioni possono essere eseguita in maniera asincrona, il concetto
+di posizione corrente sul file viene a mancare; pertanto si deve sempre
+specificare nel campo \var{aio\_offset} la posizione sul file da cui i dati
+saranno letti o scritti. Nel campo \var{aio\_buf} deve essere specificato
+l'indirizzo del buffer usato per l'I/O, ed in \var{aio\_nbytes} la lunghezza
+del blocco di dati da trasferire.
-Entrambe le funzioni usano una struttura \struct{iovec}, la cui definizione è
-riportata in fig.~\ref{fig:file_iovec}, che definisce dove i dati devono
-essere letti o scritti ed in che quantità. Il primo campo della struttura,
-\var{iov\_base}, contiene l'indirizzo del buffer ed il secondo,
-\var{iov\_len}, la dimensione dello stesso.
+Il campo \var{aio\_reqprio} permette di impostare la priorità delle operazioni
+di I/O.\footnote{in generale perché ciò sia possibile occorre che la
+ piattaforma supporti questa caratteristica, questo viene indicato definendo
+ le macro \macro{\_POSIX\_PRIORITIZED\_IO}, e
+ \macro{\_POSIX\_PRIORITY\_SCHEDULING}.} La priorità viene impostata a
+partire da quella del processo chiamante (vedi sez.~\ref{sec:proc_priority}),
+cui viene sottratto il valore di questo campo. Il campo
+\var{aio\_lio\_opcode} è usato solo dalla funzione \func{lio\_listio}, che,
+come vedremo, permette di eseguire con una sola chiamata una serie di
+operazioni, usando un vettore di \textit{control block}. Tramite questo campo
+si specifica quale è la natura di ciascuna di esse.
\begin{figure}[!htb]
\footnotesize \centering
\begin{minipage}[c]{15cm}
- \includestruct{listati/iovec.h}
+ \includestruct{listati/sigevent.h}
\end{minipage}
\normalsize
- \caption{La struttura \structd{iovec}, usata dalle operazioni di I/O
- vettorizzato.}
- \label{fig:file_iovec}
+ \caption{La struttura \structd{sigevent}, usata per specificare le modalità
+ di notifica degli eventi relativi alle operazioni di I/O asincrono.}
+ \label{fig:file_sigevent}
\end{figure}
-La lista dei buffer da utilizzare viene indicata attraverso l'argomento
-\param{vector} che è un vettore di strutture \struct{iovec}, la cui lunghezza
-è specificata dall'argomento \param{count}.\footnote{fino alle libc5, Linux
- usava \type{size\_t} come tipo dell'argomento \param{count}, una scelta
- logica, che però è stata dismessa per restare aderenti allo standard
- POSIX.1-2001.} Ciascuna struttura dovrà essere inizializzata opportunamente
-per indicare i vari buffer da e verso i quali verrà eseguito il trasferimento
-dei dati. Essi verranno letti (o scritti) nell'ordine in cui li si sono
-specificati nel vettore \param{vector}.
+Infine il campo \var{aio\_sigevent} è una struttura di tipo \struct{sigevent}
+che serve a specificare il modo in cui si vuole che venga effettuata la
+notifica del completamento delle operazioni richieste. La struttura è
+riportata in fig.~\ref{fig:file_sigevent}; il campo \var{sigev\_notify} è
+quello che indica le modalità della notifica, esso può assumere i tre valori:
+\begin{basedescript}{\desclabelwidth{2.6cm}}
+\item[\const{SIGEV\_NONE}] Non viene inviata nessuna notifica.
+\item[\const{SIGEV\_SIGNAL}] La notifica viene effettuata inviando al processo
+ chiamante il segnale specificato da \var{sigev\_signo}; se il gestore di
+ questo è stato installato con \const{SA\_SIGINFO} gli verrà restituito il
+ valore di \var{sigev\_value} (la cui definizione è in
+ fig.~\ref{fig:sig_sigval}) come valore del campo \var{si\_value} di
+ \struct{siginfo\_t}.
+\item[\const{SIGEV\_THREAD}] La notifica viene effettuata creando un nuovo
+ \itindex{thread} \textit{thread} che esegue la funzione specificata da
+ \var{sigev\_notify\_function} con argomento \var{sigev\_value}, e con gli
+ attributi specificati da \var{sigev\_notify\_attribute}.
+\end{basedescript}
-La standardizzazione delle due funzioni all'interno della revisione
-POSIX.1-2001 prevede anche che sia possibile avere un limite al numero di
-elementi del vettore \param{vector}. Qualora questo sussista, esso deve essere
-indicato dal valore dalla costante \const{IOV\_MAX}, definita come le altre
-costanti analoghe (vedi sez.~\ref{sec:sys_limits}) in \file{limits.h}; lo
-stesso valore deve essere ottenibile in esecuzione tramite la funzione
-\func{sysconf} richiedendo l'argomento \const{\_SC\_IOV\_MAX} (vedi
-sez.~\ref{sec:sys_sysconf}).
+Le due funzioni base dell'interfaccia per l'I/O asincrono sono
+\funcd{aio\_read} ed \funcd{aio\_write}. Esse permettono di richiedere una
+lettura od una scrittura asincrona di dati, usando la struttura \struct{aiocb}
+appena descritta; i rispettivi prototipi sono:
+\begin{functions}
+ \headdecl{aio.h}
+
+ \funcdecl{int aio\_read(struct aiocb *aiocbp)}
+ Richiede una lettura asincrona secondo quanto specificato con \param{aiocbp}.
+
+ \funcdecl{int aio\_write(struct aiocb *aiocbp)}
+ Richiede una scrittura asincrona secondo quanto specificato con
+ \param{aiocbp}.
+
+ \bodydesc{Le funzioni restituiscono 0 in caso di successo, 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.
+ \item[\errcode{ENOSYS}] la funzione non è implementata.
+ \item[\errcode{EINVAL}] si è specificato un valore non valido per i campi
+ \var{aio\_offset} o \var{aio\_reqprio} di \param{aiocbp}.
+ \item[\errcode{EAGAIN}] la coda delle richieste è momentaneamente piena.
+ \end{errlist}
+}
+\end{functions}
+
+Entrambe le funzioni ritornano immediatamente dopo aver messo in coda la
+richiesta, o in caso di errore. Non è detto che gli errori \errcode{EBADF} ed
+\errcode{EINVAL} siano rilevati immediatamente al momento della chiamata,
+potrebbero anche emergere nelle fasi successive delle operazioni. Lettura e
+scrittura avvengono alla posizione indicata da \var{aio\_offset}, a meno che
+il file non sia stato aperto in \itindex{append~mode} \textit{append mode}
+(vedi sez.~\ref{sec:file_open}), nel qual caso le scritture vengono effettuate
+comunque alla fine de file, nell'ordine delle chiamate a \func{aio\_write}.
+
+Si tenga inoltre presente che deallocare la memoria indirizzata da
+\param{aiocbp} o modificarne i valori prima della conclusione di una
+operazione può dar luogo a risultati impredicibili, perché l'accesso ai vari
+campi per eseguire l'operazione può avvenire in un momento qualsiasi dopo la
+richiesta. Questo comporta che non si devono usare per \param{aiocbp}
+variabili automatiche e che non si deve riutilizzare la stessa struttura per
+un'altra operazione fintanto che la precedente non sia stata ultimata. In
+generale per ogni operazione si deve utilizzare una diversa struttura
+\struct{aiocb}.
+
+Dato che si opera in modalità asincrona, il successo di \func{aio\_read} o
+\func{aio\_write} non implica che le operazioni siano state effettivamente
+eseguite in maniera corretta; per verificarne l'esito l'interfaccia prevede
+altre due funzioni, che permettono di controllare lo stato di esecuzione. La
+prima è \funcd{aio\_error}, che serve a determinare un eventuale stato di
+errore; il suo prototipo è:
+\begin{prototype}{aio.h}
+ {int aio\_error(const struct aiocb *aiocbp)}
+
+ Determina lo stato di errore delle operazioni di I/O associate a
+ \param{aiocbp}.
+
+ \bodydesc{La funzione restituisce 0 se le operazioni si sono concluse con
+ successo, altrimenti restituisce il codice di errore relativo al loro
+ fallimento.}
+\end{prototype}
+
+Se l'operazione non si è ancora completata viene restituito l'errore di
+\errcode{EINPROGRESS}. La funzione ritorna zero quando l'operazione si è
+conclusa con successo, altrimenti restituisce il codice dell'errore
+verificatosi, ed esegue la corrispondente impostazione di \var{errno}. Il
+codice può essere sia \errcode{EINVAL} ed \errcode{EBADF}, dovuti ad un valore
+errato per \param{aiocbp}, che uno degli errori possibili durante l'esecuzione
+dell'operazione di I/O richiesta, nel qual caso saranno restituiti, a seconda
+del caso, i codici di errore delle system call \func{read}, \func{write} e
+\func{fsync}.
+
+Una volta che si sia certi che le operazioni siano state concluse (cioè dopo
+che una chiamata ad \func{aio\_error} non ha restituito
+\errcode{EINPROGRESS}), si potrà usare la funzione \funcd{aio\_return}, che
+permette di verificare il completamento delle operazioni di I/O asincrono; il
+suo prototipo è:
+\begin{prototype}{aio.h}
+{ssize\_t aio\_return(const struct aiocb *aiocbp)}
+
+Recupera il valore dello stato di ritorno delle operazioni di I/O associate a
+\param{aiocbp}.
+
+\bodydesc{La funzione restituisce lo stato di uscita dell'operazione
+ eseguita.}
+\end{prototype}
-Nel caso di Linux il limite di sistema è di 1024, però se si usano le
-\acr{glibc} queste forniscono un \textit{wrapper} per le system call che si
-accorge se una operazione supererà il precedente limite, in tal caso i dati
-verranno letti o scritti con le usuali \func{read} e \func{write} usando un
-buffer di dimensioni sufficienti appositamente allocato e sufficiente a
-contenere tutti i dati indicati da \param{vector}. L'operazione avrà successo
-ma si perderà l'atomicità del trasferimento da e verso la destinazione finale.
+La funzione deve essere chiamata una sola volte per ciascuna operazione
+asincrona, essa infatti fa sì che il sistema rilasci le risorse ad essa
+associate. É per questo motivo che occorre chiamare la funzione solo dopo che
+l'operazione cui \param{aiocbp} fa riferimento si è completata. Una chiamata
+precedente il completamento delle operazioni darebbe risultati indeterminati.
-% TODO verificare cosa succederà a preadv e pwritev o alla nuova niovec
-% vedi http://lwn.net/Articles/164887/
-% inserite nel kernel 2.6.30, vedi http://lwn.net/Articles/326818/
+La funzione restituisce il valore di ritorno relativo all'operazione eseguita,
+così come ricavato dalla sottostante system call (il numero di byte letti,
+scritti o il valore di ritorno di \func{fsync}). É importante chiamare sempre
+questa funzione, altrimenti le risorse disponibili per le operazioni di I/O
+asincrono non verrebbero liberate, rischiando di arrivare ad un loro
+esaurimento.
+Oltre alle operazioni di lettura e scrittura l'interfaccia POSIX.1b mette a
+disposizione un'altra operazione, quella di sincronizzazione dell'I/O,
+compiuta dalla funzione \funcd{aio\_fsync}, che ha lo stesso effetto della
+analoga \func{fsync}, ma viene eseguita in maniera asincrona; il suo prototipo
+è:
+\begin{prototype}{aio.h}
+{int aio\_fsync(int op, struct aiocb *aiocbp)}
-\subsection{L'I/O diretto fra file descriptor: \func{sendfile} e \func{splice}}
-\label{sec:file_sendfile_splice}
+Richiede la sincronizzazione dei dati per il file indicato da \param{aiocbp}.
+
+\bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di
+ errore, che può essere, con le stesse modalità di \func{aio\_read},
+ \errval{EAGAIN}, \errval{EBADF} o \errval{EINVAL}.}
+\end{prototype}
-Uno dei problemi che si presentano nella gestione dell'I/O è quello in cui si
-devono trasferire grandi quantità di dati da un file descriptor ed un altro;
-questo usualmente comporta la lettura dei dati dal primo file descriptor in un
-buffer in memoria, da cui essi vengono poi scritti sul secondo.
+La funzione richiede la sincronizzazione delle operazioni di I/O, ritornando
+immediatamente. L'esecuzione effettiva della sincronizzazione dovrà essere
+verificata con \func{aio\_error} e \func{aio\_return} come per le operazioni
+di lettura e scrittura. L'argomento \param{op} permette di indicare la
+modalità di esecuzione, se si specifica il valore \const{O\_DSYNC} le
+operazioni saranno completate con una chiamata a \func{fdatasync}, se si
+specifica \const{O\_SYNC} con una chiamata a \func{fsync} (per i dettagli vedi
+sez.~\ref{sec:file_sync}).
-Benché il kernel ottimizzi la gestione di questo processo quando si ha a che
-fare con file normali, in generale quando i dati da trasferire sono molti si
-pone il problema di effettuare trasferimenti di grandi quantità di dati da
-kernel space a user space e all'indietro, quando in realtà potrebbe essere più
-efficiente mantenere tutto in kernel space. Tratteremo in questa sezione
-alcune funzioni specialistiche che permettono di ottimizzare le prestazioni in
-questo tipo di situazioni.
+Il successo della chiamata assicura la sincronizzazione delle operazioni fino
+allora richieste, niente è garantito riguardo la sincronizzazione dei dati
+relativi ad eventuali operazioni richieste successivamente. Se si è
+specificato un meccanismo di notifica questo sarà innescato una volta che le
+operazioni di sincronizzazione dei dati saranno completate.
-La prima funzione che si pone l'obiettivo di ottimizzare il trasferimento dei
-dati fra due file descriptor è \funcd{sendfile};\footnote{la funzione è stata
- introdotta con i kernel della serie 2.2, e disponibile dalle \acr{glibc}
- 2.1.} la funzione è presente in diverse versioni di Unix,\footnote{la si
- ritrova ad esempio in FreeBSD, HPUX ed altri Unix.} ma non è presente né in
-POSIX.1-2001 né in altri standard,\footnote{pertanto si eviti di utilizzarla
- se si devono scrivere programmi portabili.} per cui per essa vengono
-utilizzati prototipi e semantiche differenti; nel caso di Linux il suo
+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 funzione apposita, \funcd{aio\_cancel}, che
+permette di cancellare una operazione richiesta in precedenza; il suo
prototipo è:
-\begin{functions}
- \headdecl{sys/sendfile.h}
+\begin{prototype}{aio.h}
+{int aio\_cancel(int fildes, struct aiocb *aiocbp)}
- \funcdecl{ssize\_t sendfile(int out\_fd, int in\_fd, off\_t *offset, size\_t
- count)}
+Richiede la cancellazione delle operazioni sul file \param{fildes} specificate
+da \param{aiocbp}.
- Copia dei dati da un file descriptor ad un altro.
+\bodydesc{La funzione restituisce il risultato dell'operazione con un codice
+ di positivo, e -1 in caso di errore, che avviene qualora si sia specificato
+ un valore non valido di \param{fildes}, imposta \var{errno} al valore
+ \errval{EBADF}.}
+\end{prototype}
- \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
- successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
+La funzione permette di cancellare una operazione specifica sul file
+\param{fildes}, o tutte le operazioni pendenti, specificando \val{NULL} come
+valore di \param{aiocbp}. Quando una operazione viene cancellata una
+successiva chiamata ad \func{aio\_error} riporterà \errcode{ECANCELED} come
+codice di errore, ed il suo codice di ritorno sarà -1, inoltre il meccanismo
+di notifica non verrà invocato. Se si specifica una operazione relativa ad un
+altro file descriptor il risultato è indeterminato. In caso di successo, i
+possibili valori di ritorno per \func{aio\_cancel} (anch'essi definiti in
+\file{aio.h}) sono tre:
+\begin{basedescript}{\desclabelwidth{3.0cm}}
+\item[\const{AIO\_ALLDONE}] indica che le operazioni di cui si è richiesta la
+ cancellazione sono state già completate,
+
+\item[\const{AIO\_CANCELED}] indica che tutte le operazioni richieste sono
+ state cancellate,
+
+\item[\const{AIO\_NOTCANCELED}] indica che alcune delle operazioni erano in
+ corso e non sono state cancellate.
+\end{basedescript}
+
+Nel caso si abbia \const{AIO\_NOTCANCELED} occorrerà chiamare
+\func{aio\_error} per determinare quali sono le operazioni effettivamente
+cancellate. Le operazioni che non sono state cancellate proseguiranno il loro
+corso normale, compreso quanto richiesto riguardo al meccanismo di notifica
+del loro avvenuto completamento.
+
+Benché l'I/O asincrono preveda un meccanismo di notifica, l'interfaccia
+fornisce anche una apposita funzione, \funcd{aio\_suspend}, che permette di
+sospendere l'esecuzione del processo chiamante fino al completamento di una
+specifica operazione; il suo prototipo è:
+\begin{prototype}{aio.h}
+{int aio\_suspend(const struct aiocb * const list[], int nent, const struct
+ timespec *timeout)}
+
+ Attende, per un massimo di \param{timeout}, il completamento di una delle
+ operazioni specificate da \param{list}.
+
+ \bodydesc{La funzione restituisce 0 se una (o più) operazioni sono state
+ completate, e -1 in caso di errore nel qual caso \var{errno} assumerà uno
dei valori:
\begin{errlist}
- \item[\errcode{EAGAIN}] si è impostata la modalità non bloccante su
- \param{out\_fd} e la scrittura si bloccherebbe.
- \item[\errcode{EINVAL}] i file descriptor non sono validi, o sono bloccati
- (vedi sez.~\ref{sec:file_locking}), o \func{mmap} non è disponibile per
- \param{in\_fd}.
- \item[\errcode{EIO}] si è avuto un errore di lettura da \param{in\_fd}.
- \item[\errcode{ENOMEM}] non c'è memoria sufficiente per la lettura da
- \param{in\_fd}.
+ \item[\errcode{EAGAIN}] nessuna operazione è stata completata entro
+ \param{timeout}.
+ \item[\errcode{ENOSYS}] la funzione non è implementata.
+ \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale.
\end{errlist}
- ed inoltre \errcode{EBADF} e \errcode{EFAULT}.
}
-\end{functions}
-
-La funzione copia direttamente \param{count} byte dal file descriptor
-\param{in\_fd} al file descriptor \param{out\_fd}; in caso di successo
-funzione ritorna il numero di byte effettivamente copiati da \param{in\_fd} a
-\param{out\_fd} o $-1$ in caso di errore, come le ordinarie \func{read} e
-\func{write} questo valore può essere inferiore a quanto richiesto con
-\param{count}.
-
-Se il puntatore \param{offset} è nullo la funzione legge i dati a partire
-dalla posizione corrente su \param{in\_fd}, altrimenti verrà usata la
-posizione indicata dal valore puntato da \param{offset}; in questo caso detto
-valore sarà aggiornato, come \textit{value result argument}, per indicare la
-posizione del byte successivo all'ultimo che è stato letto, mentre la
-posizione corrente sul file non sarà modificata. Se invece \param{offset} è
-nullo la posizione corrente sul file sarà aggiornata tenendo conto dei byte
-letti da \param{in\_fd}.
-
-Fino ai kernel della serie 2.4 la funzione è utilizzabile su un qualunque file
-descriptor, e permette di sostituire la invocazione successiva di una
-\func{read} e una \func{write} (e l'allocazione del relativo buffer) con una
-sola chiamata a \funcd{sendfile}. In questo modo si può diminuire il numero di
-chiamate al sistema e risparmiare in trasferimenti di dati da kernel space a
-user space e viceversa. La massima utilità della funzione si ha comunque per
-il trasferimento di dati da un file su disco ad un socket di
-rete,\footnote{questo è il caso classico del lavoro eseguito da un server web,
- ed infatti Apache ha una opzione per il supporto esplicito di questa
- funzione.} dato che in questo caso diventa possibile effettuare il
-trasferimento diretto via DMA dal controller del disco alla scheda di rete,
-senza neanche allocare un buffer nel kernel,\footnote{il meccanismo è detto
- \textit{zerocopy} in quanto i dati non vengono mai copiati dal kernel, che
- si limita a programmare solo le operazioni di lettura e scrittura via DMA.}
-ottenendo la massima efficienza possibile senza pesare neanche sul processore.
-
-In seguito però ci si è accorti che, fatta eccezione per il trasferimento
-diretto da file a socket, non sempre \func{sendfile} comportava miglioramenti
-significativi delle prestazioni rispetto all'uso in sequenza di \func{read} e
-\func{write},\footnote{nel caso generico infatti il kernel deve comunque
- allocare un buffer ed effettuare la copia dei dati, e in tal caso spesso il
- guadagno ottenibile nel ridurre il numero di chiamate al sistema non
- compensa le ottimizzazioni che possono essere fatte da una applicazione in
- user space che ha una conoscenza diretta su come questi sono strutturati.} e
-che anzi in certi casi si potevano avere anche dei peggioramenti. Questo ha
-portato, per i kernel della serie 2.6,\footnote{per alcune motivazioni di
- questa scelta si può fare riferimento a quanto illustrato da Linus Torvalds
- in \href{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}
- {\textsf{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}}.}
-alla decisione di consentire l'uso della funzione soltanto quando il file da
-cui si legge supporta le operazioni di \textit{memory mapping} (vale a dire
-non è un socket) e quello su cui si scrive è un socket; in tutti gli altri
-casi l'uso di \func{sendfile} darà luogo ad un errore di \errcode{EINVAL}.
-
-Nonostante ci possano essere casi in cui \func{sendfile} non migliora le
-prestazioni, le motivazioni addotte non convincono del tutto e resta il dubbio
-se la scelta di disabilitarla sempre per il trasferimento di dati fra file di
-dati sia davvero corretta. Se ci sono peggioramenti di prestazioni infatti si
-può sempre fare ricorso all'uso successivo di, ma lasciare a disposizione la
-funzione consentirebbe se non altro, anche in assenza di guadagni di
-prestazioni, di semplificare la gestione della copia dei dati fra file,
-evitando di dover gestire l'allocazione di un buffer temporaneo per il loro
-trasferimento; inoltre si avrebbe comunque il vantaggio di evitare inutili
-trasferimenti di dati da kernel space a user space e viceversa.
-
-Questo dubbio si può comunque ritenere superato con l'introduzione, avvenuto a
-partire dal kernel 2.6.17, della nuova system call \func{splice}. Lo scopo di
-questa funzione è quello di fornire un meccanismo generico per il
-trasferimento di dati da o verso un file utilizzando un buffer gestito
-internamente dal kernel. Descritta in questi termini \func{splice} sembra
-semplicemente un ``\textsl{dimezzamento}'' di \func{sendfile}.\footnote{nel
- senso che un trasferimento di dati fra due file con \func{sendfile} non
- sarebbe altro che la lettura degli stessi su un buffer seguita dalla
- relativa scrittura, cosa che in questo caso si dovrebbe eseguire con due
- chiamate a \func{splice}.} In realtà le due system call sono profondamente
-diverse nel loro meccanismo di funzionamento;\footnote{questo fino al kernel
- 2.6.23, dove \func{sendfile} è stata reimplementata in termini di
- \func{splice}, pur mantenendo disponibile la stessa interfaccia verso l'user
- space.} \func{sendfile} infatti, come accennato, non necessita di avere a
-disposizione un buffer interno, perché esegue un trasferimento diretto di
-dati; questo la rende in generale più efficiente, ma anche limitata nelle sue
-applicazioni, dato che questo tipo di trasferimento è possibile solo in casi
-specifici.\footnote{e nel caso di Linux questi sono anche solo quelli in cui
- essa può essere effettivamente utilizzata.}
-
-Il concetto che sta dietro a \func{splice} invece è diverso,\footnote{in
- realtà la proposta originale di Larry Mc Voy non differisce poi tanto negli
- scopi da \func{sendfile}, quello che rende \func{splice} davvero diversa è
- stata la reinterpretazione che ne è stata fatta nell'implementazione su
- Linux realizzata da Jens Anxboe, concetti che sono esposti sinteticamente
- dallo stesso Linus Torvalds in \href{http://kerneltrap.org/node/6505}
- {\textsf{http://kerneltrap.org/node/6505}}.} si tratta semplicemente di una
-funzione che consente di fare in maniera del tutto generica delle operazioni
-di trasferimento di dati fra un file e un buffer gestito interamente in kernel
-space. In questo caso il cuore della funzione (e delle affini \func{vmsplice}
-e \func{tee}, che tratteremo più avanti) è appunto l'uso di un buffer in
-kernel space, e questo è anche quello che ne ha semplificato l'adozione,
-perché l'infrastruttura per la gestione di un tale buffer è presente fin dagli
-albori di Unix per la realizzazione delle \textit{pipe} (vedi
-sez.~\ref{sec:ipc_unix}). Dal punto di vista concettuale allora \func{splice}
-non è altro che una diversa interfaccia (rispetto alle \textit{pipe}) con cui
-utilizzare in user space l'oggetto ``\textsl{buffer in kernel space}''.
+\end{prototype}
-Così se per una \textit{pipe} o una \textit{fifo} il buffer viene utilizzato
-come area di memoria (vedi fig.~\ref{fig:ipc_pipe_singular}) dove appoggiare i
-dati che vengono trasferiti da un capo all'altro della stessa per creare un
-meccanismo di comunicazione fra processi, nel caso di \func{splice} il buffer
-viene usato o come fonte dei dati che saranno scritti su un file, o come
-destinazione dei dati che vengono letti da un file. La funzione \funcd{splice}
-fornisce quindi una interfaccia generica che consente di trasferire dati da un
-buffer ad un file o viceversa; il suo prototipo, accessibile solo dopo aver
-definito la macro \macro{\_GNU\_SOURCE},\footnote{si ricordi che questa
- funzione non è contemplata da nessuno standard, è presente solo su Linux, e
- pertanto deve essere evitata se si vogliono scrivere programmi portabili.}
-è il seguente:
-\begin{functions}
- \headdecl{fcntl.h}
+La funzione permette di bloccare il processo fintanto che almeno una delle
+\param{nent} operazioni specificate nella lista \param{list} è completata, per
+un tempo massimo specificato da \param{timout}, o fintanto che non arrivi un
+segnale.\footnote{si tenga conto che questo segnale può anche essere quello
+ utilizzato come meccanismo di notifica.} La lista deve essere inizializzata
+con delle strutture \struct{aiocb} relative ad operazioni effettivamente
+richieste, ma può contenere puntatori nulli, che saranno ignorati. In caso si
+siano specificati valori non validi l'effetto è indefinito. Un valore
+\val{NULL} per \param{timout} comporta l'assenza di timeout.
- \funcdecl{long splice(int fd\_in, off\_t *off\_in, int fd\_out, off\_t
- *off\_out, size\_t len, unsigned int flags)}
+Lo standard POSIX.1b infine ha previsto pure una funzione, \funcd{lio\_listio},
+che permette di effettuare la richiesta di una intera lista di operazioni di
+lettura o scrittura; il suo prototipo è:
+\begin{prototype}{aio.h}
+ {int lio\_listio(int mode, struct aiocb * const list[], int nent, struct
+ sigevent *sig)}
- Trasferisce dati da un file verso una pipe o viceversa.
-
- \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
- successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
- dei valori:
+ Richiede l'esecuzione delle operazioni di I/O elencata da \param{list},
+ secondo la modalità \param{mode}.
+
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ errore, nel qual caso \var{errno} assumerà uno dei valori:
\begin{errlist}
- \item[\errcode{EBADF}] uno o entrambi fra \param{fd\_in} e \param{fd\_out}
- non sono file descriptor validi o, rispettivamente, non sono stati
- aperti in lettura o scrittura.
- \item[\errcode{EINVAL}] il filesystem su cui si opera non supporta
- \func{splice}, oppure nessuno dei file descriptor è una pipe, oppure si
- è dato un valore a \param{off\_in} o \param{off\_out} ma il
- corrispondente file è un dispositivo che non supporta la funzione
- \func{seek}.
- \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione
- richiesta.
- \item[\errcode{ESPIPE}] o \param{off\_in} o \param{off\_out} non sono
- \const{NULL} ma il corrispondente file descriptor è una \textit{pipe}.
+ \item[\errcode{EAGAIN}] nessuna operazione è stata completata entro
+ \param{timeout}.
+ \item[\errcode{EINVAL}] si è passato un valore di \param{mode} non valido
+ o un numero di operazioni \param{nent} maggiore di
+ \const{AIO\_LISTIO\_MAX}.
+ \item[\errcode{ENOSYS}] la funzione non è implementata.
+ \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale.
\end{errlist}
}
-\end{functions}
+\end{prototype}
-La funzione esegue un trasferimento di \param{len} byte dal file descriptor
-\param{fd\_in} al file descriptor \param{fd\_out}, uno dei quali deve essere
-una \textit{pipe}; l'altro file descriptor può essere
-qualunque.\footnote{questo significa che può essere, oltre che un file di
- dati, anche un altra \textit{pipe}, o un socket.} Come accennato una
-\textit{pipe} non è altro che un buffer in kernel space, per cui a seconda che
-essa sia usata per \param{fd\_in} o \param{fd\_out} si avrà rispettivamente la
-copia dei dati dal buffer al file o viceversa.
+La funzione esegue la richiesta delle \param{nent} operazioni indicate nella
+lista \param{list} che deve contenere gli indirizzi di altrettanti
+\textit{control block} opportunamente inizializzati; in particolare dovrà
+essere specificato il tipo di operazione con il campo \var{aio\_lio\_opcode},
+che può prendere i valori:
+\begin{basedescript}{\desclabelwidth{2.0cm}}
+\item[\const{LIO\_READ}] si richiede una operazione di lettura.
+\item[\const{LIO\_WRITE}] si richiede una operazione di scrittura.
+\item[\const{LIO\_NOP}] non si effettua nessuna operazione.
+\end{basedescript}
+dove \const{LIO\_NOP} viene usato quando si ha a che fare con un vettore di
+dimensione fissa, per poter specificare solo alcune operazioni, o quando si
+sono dovute cancellare delle operazioni e si deve ripetere la richiesta per
+quelle non completate.
-In caso di successo la funzione ritorna il numero di byte trasferiti, che può
-essere, come per le normali funzioni di lettura e scrittura su file, inferiore
-a quelli richiesti; un valore negativo indicherà un errore mentre un valore
-nullo indicherà che non ci sono dati da trasferire (ad esempio si è giunti
-alla fine del file in lettura). Si tenga presente che, a seconda del verso del
-trasferimento dei dati, la funzione si comporta nei confronti del file
-descriptor che fa riferimento al file ordinario, come \func{read} o
-\func{write}, e pertanto potrà anche bloccarsi (a meno che non si sia aperto
-il suddetto file in modalità non bloccante).
+L'argomento \param{mode} controlla il comportamento della funzione, se viene
+usato il valore \const{LIO\_WAIT} la funzione si blocca fino al completamento
+di tutte le operazioni richieste; se si usa \const{LIO\_NOWAIT} la funzione
+ritorna immediatamente dopo aver messo in coda tutte le richieste. In tal caso
+il chiamante può richiedere la notifica del completamento di tutte le
+richieste, impostando l'argomento \param{sig} in maniera analoga a come si fa
+per il campo \var{aio\_sigevent} di \struct{aiocb}.
-I due argomenti \param{off\_in} e \param{off\_out} consentono di specificare,
-come per l'analogo \param{offset} di \func{sendfile}, la posizione all'interno
-del file da cui partire per il trasferimento dei dati. Come per
-\func{sendfile} un valore nullo indica di usare la posizione corrente sul
-file, ed essa sarà aggiornata automaticamente secondo il numero di byte
-trasferiti. Un valore non nullo invece deve essere un puntatore ad una
-variabile intera che indica la posizione da usare; questa verrà aggiornata, al
-ritorno della funzione, al byte successivo all'ultimo byte trasferito.
-Ovviamente soltanto uno di questi due argomenti, e più precisamente quello che
-fa riferimento al file descriptor non associato alla \textit{pipe}, può essere
-specificato come valore non nullo.
-Infine l'argomento \param{flags} consente di controllare alcune
-caratteristiche del funzionamento della funzione; il contenuto è una maschera
-binaria e deve essere specificato come OR aritmetico dei valori riportati in
-tab.~\ref{tab:splice_flag}. Alcuni di questi valori vengono utilizzati anche
-dalle funzioni \func{vmsplice} e \func{tee} per cui la tabella riporta le
-descrizioni complete di tutti i valori possibili anche quando, come per
-\const{SPLICE\_F\_GIFT}, questi non hanno effetto su \func{splice}.
+\section{Altre modalità di I/O avanzato}
+\label{sec:file_advanced_io}
-\begin{table}[htb]
+Oltre alle precedenti modalità di \textit{I/O multiplexing} e \textsl{I/O
+ asincrono}, esistono altre funzioni che implementano delle modalità di
+accesso ai file più evolute rispetto alle normali funzioni di lettura e
+scrittura che abbiamo esaminato in sez.~\ref{sec:file_base_func}. In questa
+sezione allora prenderemo in esame le interfacce per l'\textsl{I/O mappato in
+ memoria}, per l'\textsl{I/O vettorizzato} e altre funzioni di I/O avanzato.
+
+
+\subsection{File mappati in memoria}
+\label{sec:file_memory_map}
+
+\itindbeg{memory~mapping}
+Una modalità alternativa di I/O, che usa una interfaccia completamente diversa
+rispetto a quella classica vista in cap.~\ref{cha:file_unix_interface}, è il
+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
- \footnotesize
- \begin{tabular}[c]{|l|p{10cm}|}
- \hline
- \textbf{Valore} & \textbf{Significato} \\
- \hline
- \hline
- \const{SPLICE\_F\_MOVE} & Suggerisce al kernel di spostare le pagine
- di memoria contenenti i dati invece di
- copiarle;\footnotemark viene usato soltanto
- da \func{splice}.\\
- \const{SPLICE\_F\_NONBLOCK}& Richiede di operare in modalità non
- bloccante; questo flag influisce solo sulle
- operazioni che riguardano l'I/O da e verso la
- \textit{pipe}. Nel caso di \func{splice}
- questo significa che la funzione potrà
- comunque bloccarsi nell'accesso agli altri
- file descriptor (a meno che anch'essi non
- siano stati aperti in modalità non
- bloccante).\\
- \const{SPLICE\_F\_MORE} & Indica al kernel che ci sarà l'invio di
- ulteriori dati in una \func{splice}
- successiva, questo è un suggerimento utile
- che viene usato quando \param{fd\_out} è un
- socket.\footnotemark Attualmente viene usato
- solo da \func{splice}, potrà essere
- implementato in futuro anche per
- \func{vmsplice} e \func{tee}.\\
- \const{SPLICE\_F\_GIFT} & Le pagine di memoria utente sono
- ``\textsl{donate}'' al kernel;\footnotemark
- se impostato una seguente \func{splice} che
- usa \const{SPLICE\_F\_MOVE} potrà spostare le
- pagine con successo, altrimenti esse dovranno
- essere copiate; per usare questa opzione i
- dati dovranno essere opportunamente allineati
- in posizione ed in dimensione alle pagine di
- memoria. Viene usato soltanto da
- \func{vmsplice}.\\
- \hline
- \end{tabular}
- \caption{Le costanti che identificano i bit della maschera binaria
- dell'argomento \param{flags} di \func{splice}, \func{vmsplice} e
- \func{tee}.}
- \label{tab:splice_flag}
-\end{table}
+ \includegraphics[width=12cm]{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}
+\end{figure}
-\footnotetext{per una maggiore efficienza \func{splice} usa quando possibile i
- meccanismi della memoria virtuale per eseguire i trasferimenti di dati (in
- maniera analoga a \func{mmap}), qualora le pagine non possano essere
- spostate dalla pipe o il buffer non corrisponda a pagine intere esse saranno
- comunque copiate.}
+Il meccanismo è illustrato in fig.~\ref{fig:file_mmap_layout}, una sezione del
+file viene \textsl{mappata} direttamente nello spazio degli indirizzi del
+programma. Tutte le operazioni di lettura e scrittura su variabili contenute
+in questa zona di memoria verranno eseguite leggendo e scrivendo dal contenuto
+del file attraverso il sistema della memoria virtuale \index{memoria~virtuale}
+che in maniera analoga a quanto avviene per le pagine che vengono salvate e
+rilette nella swap, si incaricherà di sincronizzare il contenuto di quel
+segmento di memoria con quello del file mappato su di esso. Per questo motivo
+si può parlare tanto di \textsl{file mappato in memoria}, quanto di
+\textsl{memoria mappata su file}.
-\footnotetext{questa opzione consente di utilizzare delle opzioni di gestione
- dei socket che permettono di ottimizzare le trasmissioni via rete, si veda
- la descrizione di \const{TCP\_CORK} in sez.~\ref{sec:sock_tcp_udp_options} e
- quella di \const{MSG\_MORE} in sez.~\ref{sec:net_sendmsg}.}
+L'uso del \textit{memory-mapping} comporta una notevole semplificazione delle
+operazioni di I/O, in quanto non sarà più necessario utilizzare dei buffer
+intermedi su cui appoggiare i dati da traferire, poiché questi potranno essere
+acceduti direttamente nella sezione di memoria mappata; inoltre questa
+interfaccia è più efficiente delle usuali funzioni di I/O, in quanto permette
+di caricare in memoria solo le parti del file che sono effettivamente usate ad
+un dato istante.
-\footnotetext{questo significa che la cache delle pagine e i dati su disco
- potranno differire, e che l'applicazione non potrà modificare quest'area di
- memoria.}
+Infatti, dato che l'accesso è fatto direttamente attraverso la
+\index{memoria~virtuale} memoria virtuale, la sezione di memoria mappata su
+cui si opera sarà a sua volta letta o scritta sul file una pagina alla volta e
+solo per le parti effettivamente usate, il tutto in maniera completamente
+trasparente al processo; l'accesso alle pagine non ancora caricate avverrà
+allo stesso modo con cui vengono caricate in memoria le pagine che sono state
+salvate sullo swap.
+
+Infine in situazioni in cui la memoria è scarsa, le pagine che mappano un file
+vengono salvate automaticamente, così come le pagine dei programmi vengono
+scritte sulla swap; questo consente di accedere ai file su dimensioni il cui
+solo limite è quello dello spazio di indirizzi disponibile, e non della
+memoria su cui possono esserne lette delle porzioni.
+
+L'interfaccia POSIX implementata da Linux prevede varie funzioni per la
+gestione del \textit{memory mapped I/O}, la prima di queste, che serve ad
+eseguire la mappatura in memoria di un file, è \funcd{mmap}; il suo prototipo
+è:
+\begin{functions}
+
+ \headdecl{unistd.h}
+ \headdecl{sys/mman.h}
+
+ \funcdecl{void * mmap(void * start, size\_t length, int prot, int flags, int
+ fd, off\_t offset)}
+
+ Esegue la mappatura in memoria della sezione specificata del file \param{fd}.
+
+ \bodydesc{La funzione restituisce il puntatore alla zona di memoria mappata
+ in caso di successo, e \const{MAP\_FAILED} (-1) in caso di errore, nel
+ qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EBADF}] il file descriptor non è valido, e non si è usato
+ \const{MAP\_ANONYMOUS}.
+ \item[\errcode{EACCES}] o \param{fd} non si riferisce ad un file regolare,
+ o si è usato \const{MAP\_PRIVATE} ma \param{fd} non è aperto in lettura,
+ o si è usato \const{MAP\_SHARED} e impostato \const{PROT\_WRITE} ed
+ \param{fd} non è aperto in lettura/scrittura, o si è impostato
+ \const{PROT\_WRITE} ed \param{fd} è in \textit{append-only}.
+ \item[\errcode{EINVAL}] i valori di \param{start}, \param{length} o
+ \param{offset} non sono validi (o troppo grandi o non allineati sulla
+ dimensione delle pagine).
+ \item[\errcode{ETXTBSY}] si è impostato \const{MAP\_DENYWRITE} ma
+ \param{fd} è aperto in scrittura.
+ \item[\errcode{EAGAIN}] il file è bloccato, o si è bloccata troppa memoria
+ rispetto a quanto consentito dai limiti di sistema (vedi
+ sez.~\ref{sec:sys_resource_limit}).
+ \item[\errcode{ENOMEM}] non c'è memoria o si è superato il limite sul
+ numero di mappature possibili.
+ \item[\errcode{ENODEV}] il filesystem di \param{fd} non supporta il memory
+ mapping.
+ \item[\errcode{EPERM}] l'argomento \param{prot} ha richiesto
+ \const{PROT\_EXEC}, ma il filesystem di \param{fd} è montato con
+ l'opzione \texttt{noexec}.
+ \item[\errcode{ENFILE}] si è superato il limite del sistema sul numero di
+ file aperti (vedi sez.~\ref{sec:sys_resource_limit}).
+ \end{errlist}
+ }
+\end{functions}
-Per capire meglio il funzionamento di \func{splice} vediamo un esempio con un
-semplice programma che usa questa funzione per effettuare la copia di un file
-su un altro senza utilizzare buffer in user space. Il programma si chiama
-\texttt{splicecp.c} ed il codice completo è disponibile coi sorgenti allegati
-alla guida, il corpo principale del programma, che non contiene la sezione di
-gestione delle opzioni e le funzioni di ausilio è riportato in
-fig.~\ref{fig:splice_example}.
+La funzione richiede di mappare in memoria la sezione del file \param{fd} a
+partire da \param{offset} per \param{lenght} byte, preferibilmente
+all'indirizzo \param{start}. Il valore di \param{offset} deve essere un
+multiplo della dimensione di una pagina di memoria.
-Lo scopo del programma è quello di eseguire la copia dei con \func{splice},
-questo significa che si dovrà usare la funzione due volte, prima per leggere i
-dati e poi per scriverli, appoggiandosi ad un buffer in kernel space (vale a
-dire ad una \textit{pipe}); lo schema del flusso dei dati è illustrato in
-fig.~\ref{fig:splicecp_data_flux}.
-\begin{figure}[htb]
+\begin{table}[htb]
\centering
- \includegraphics[height=6cm]{img/splice_copy}
- \caption{Struttura del flusso di dati usato dal programma \texttt{splicecp}.}
- \label{fig:splicecp_data_flux}
-\end{figure}
-
-Una volta trattate le opzioni il programma verifica che restino
-(\texttt{\small 13--16}) i due argomenti che indicano il file sorgente ed il
-file destinazione. Il passo successivo è aprire il file sorgente
-(\texttt{\small 18--22}), quello di destinazione (\texttt{\small 23--27}) ed
-infine (\texttt{\small 28--31}) la \textit{pipe} che verrà usata come buffer.
-
-\begin{figure}[!phtb]
- \footnotesize \centering
- \begin{minipage}[c]{15cm}
- \includecodesample{listati/splicecp.c}
- \end{minipage}
- \normalsize
- \caption{Esempio di codice che usa \func{splice} per effettuare la copia di
- un file.}
- \label{fig:splice_example}
-\end{figure}
-
-Il ciclo principale (\texttt{\small 33--58}) inizia con la lettura dal file
-sorgente tramite la prima \func{splice} (\texttt{\small 34--35}), in questo
-caso si è usato come primo argomento il file descriptor del file sorgente e
-come terzo quello del capo in scrittura della \textit{pipe} (il funzionamento
-delle \textit{pipe} e l'uso della coppia di file descriptor ad esse associati
-è trattato in dettaglio in sez.~\ref{sec:ipc_unix}; non ne parleremo qui dato
-che nell'ottica dell'uso di \func{splice} questa operazione corrisponde
-semplicemente al trasferimento dei dati dal file al buffer).
+ \footnotesize
+ \begin{tabular}[c]{|l|l|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{PROT\_EXEC} & Le pagine possono essere eseguite.\\
+ \const{PROT\_READ} & Le pagine possono essere lette.\\
+ \const{PROT\_WRITE} & Le pagine possono essere scritte.\\
+ \const{PROT\_NONE} & L'accesso alle pagine è vietato.\\
+ \hline
+ \end{tabular}
+ \caption{Valori dell'argomento \param{prot} di \func{mmap}, relativi alla
+ protezione applicate alle pagine del file mappate in memoria.}
+ \label{tab:file_mmap_prot}
+\end{table}
-La lettura viene eseguita in blocchi pari alla dimensione specificata
-dall'opzione \texttt{-s} (il default è 4096); essendo in questo caso
-\func{splice} equivalente ad una \func{read} sul file, se ne controlla il
-valore di uscita in \var{nread} che indica quanti byte sono stati letti, se
-detto valore è nullo (\texttt{\small 36}) questo significa che si è giunti
-alla fine del file sorgente e pertanto l'operazione di copia è conclusa e si
-può uscire dal ciclo arrivando alla conclusione del programma (\texttt{\small
- 59}). In caso di valore negativo (\texttt{\small 37--44}) c'è stato un
-errore ed allora si ripete la lettura (\texttt{\small 36}) se questo è dovuto
-ad una interruzione, o altrimenti si esce con un messaggio di errore
-(\texttt{\small 41--43}).
+Il valore dell'argomento \param{prot} indica la protezione\footnote{come
+ accennato in sez.~\ref{sec:proc_memory} in Linux la memoria reale è divisa
+ in pagine: ogni processo vede la sua memoria attraverso uno o più segmenti
+ lineari di memoria virtuale. Per ciascuno di questi segmenti il kernel
+ mantiene nella \itindex{page~table} \textit{page table} la mappatura sulle
+ pagine di memoria reale, ed le modalità di accesso (lettura, esecuzione,
+ scrittura); una loro violazione causa quella una \itindex{segment~violation}
+ \textit{segment violation}, e la relativa emissione del segnale
+ \const{SIGSEGV}.} da applicare al segmento di memoria e deve essere
+specificato come maschera binaria ottenuta dall'OR di uno o più dei valori
+riportati in tab.~\ref{tab:file_mmap_prot}; il valore specificato deve essere
+compatibile con la modalità di accesso con cui si è aperto il file.
-Una volta completata con successo la lettura si avvia il ciclo di scrittura
-(\texttt{\small 45--57}); questo inizia (\texttt{\small 46--47}) con la
-seconda \func{splice} che cerca di scrivere gli \var{nread} byte letti, si
-noti come in questo caso il primo argomento faccia di nuovo riferimento alla
-\textit{pipe} (in questo caso si usa il capo in lettura, per i dettagli si
-veda al solito sez.~\ref{sec:ipc_unix}) mentre il terzo sia il file descriptor
-del file di destinazione.
+L'argomento \param{flags} specifica infine qual è il tipo di oggetto mappato,
+le opzioni relative alle modalità con cui è effettuata la mappatura e alle
+modalità con cui le modifiche alla memoria mappata vengono condivise o
+mantenute private al processo che le ha effettuate. Deve essere specificato
+come maschera binaria ottenuta dall'OR di uno o più dei valori riportati in
+tab.~\ref{tab:file_mmap_flag}.
-Di nuovo si controlla il numero di byte effettivamente scritti restituito in
-\var{nwrite} e in caso di errore al solito si ripete la scrittura se questo è
-dovuto a una interruzione o si esce con un messaggio negli altri casi
-(\texttt{\small 48--55}). Infine si chiude il ciclo di scrittura sottraendo
-(\texttt{\small 57}) il numero di byte scritti a quelli di cui è richiesta la
-scrittura,\footnote{in questa parte del ciclo \var{nread}, il cui valore
- iniziale è dato dai byte letti dalla precedente chiamata a \func{splice},
- viene ad assumere il significato di byte da scrivere.} così che il ciclo di
-scrittura venga ripetuto fintanto che il valore risultante sia maggiore di
-zero, indice che la chiamata a \func{splice} non ha esaurito tutti i dati
-presenti sul buffer.
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|p{11cm}|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{MAP\_FIXED} & Non permette di restituire un indirizzo diverso
+ da \param{start}, se questo non può essere usato
+ \func{mmap} fallisce. Se si imposta questo flag il
+ valore di \param{start} deve essere allineato
+ alle dimensioni di una pagina.\\
+ \const{MAP\_SHARED} & I cambiamenti sulla memoria mappata vengono
+ riportati sul file e saranno immediatamente
+ visibili agli altri processi che mappano lo stesso
+ file.\footnotemark Il file su disco però non sarà
+ aggiornato fino alla chiamata di \func{msync} o
+ \func{munmap}), e solo allora le modifiche saranno
+ visibili per l'I/O convenzionale. Incompatibile
+ con \const{MAP\_PRIVATE}.\\
+ \const{MAP\_PRIVATE} & I cambiamenti sulla memoria mappata non vengono
+ riportati sul file. Ne viene fatta una copia
+ privata cui solo il processo chiamante ha
+ accesso. Le modifiche sono mantenute attraverso
+ il meccanismo del \textit{copy on
+ write} \itindex{copy~on~write} e
+ salvate su swap in caso di necessità. Non è
+ specificato se i cambiamenti sul file originale
+ vengano riportati sulla regione
+ mappata. Incompatibile con \const{MAP\_SHARED}.\\
+ \const{MAP\_DENYWRITE} & In Linux viene ignorato per evitare
+ \textit{DoS} \itindex{Denial~of~Service~(DoS)}
+ (veniva usato per segnalare che tentativi di
+ scrittura sul file dovevano fallire con
+ \errcode{ETXTBSY}).\\
+ \const{MAP\_EXECUTABLE}& Ignorato.\\
+ \const{MAP\_NORESERVE} & Si usa con \const{MAP\_PRIVATE}. Non riserva
+ delle pagine di swap ad uso del meccanismo del
+ \textit{copy on write} \itindex{copy~on~write}
+ per mantenere le
+ modifiche fatte alla regione mappata, in
+ questo caso dopo una scrittura, se non c'è più
+ memoria disponibile, si ha l'emissione di
+ un \const{SIGSEGV}.\\
+ \const{MAP\_LOCKED} & Se impostato impedisce lo swapping delle pagine
+ mappate.\\
+ \const{MAP\_GROWSDOWN} & Usato per gli \itindex{stack} \textit{stack}.
+ Indica che la mappatura deve essere effettuata
+ con gli indirizzi crescenti verso il basso.\\
+ \const{MAP\_ANONYMOUS} & La mappatura non è associata a nessun file. Gli
+ argomenti \param{fd} e \param{offset} sono
+ ignorati.\footnotemark\\
+ \const{MAP\_ANON} & Sinonimo di \const{MAP\_ANONYMOUS}, deprecato.\\
+ \const{MAP\_FILE} & Valore di compatibilità, ignorato.\\
+ \const{MAP\_32BIT} & Esegue la mappatura sui primi 2GiB dello spazio
+ degli indirizzi, viene supportato solo sulle
+ piattaforme \texttt{x86-64} per compatibilità con
+ le applicazioni a 32 bit. Viene ignorato se si è
+ richiesto \const{MAP\_FIXED}.\\
+ \const{MAP\_POPULATE} & Esegue il \itindex{prefaulting}
+ \textit{prefaulting} delle pagine di memoria
+ necessarie alla mappatura.\\
+ \const{MAP\_NONBLOCK} & Esegue un \textit{prefaulting} più limitato che
+ non causa I/O.\footnotemark\\
+% \const{MAP\_DONTEXPAND}& Non consente una successiva espansione dell'area
+% mappata con \func{mremap}, proposto ma pare non
+% implementato.\\
+ \hline
+ \end{tabular}
+ \caption{Valori possibili dell'argomento \param{flag} di \func{mmap}.}
+ \label{tab:file_mmap_flag}
+\end{table}
-Si noti come il programma sia concettualmente identico a quello che si sarebbe
-scritto usando \func{read} al posto della prima \func{splice} e \func{write}
-al posto della seconda, utilizzando un buffer in user space per eseguire la
-copia dei dati, solo che in questo caso non è stato necessario allocare nessun
-buffer e non si è trasferito nessun dato in user space.
-Si noti anche come si sia usata la combinazione \texttt{SPLICE\_F\_MOVE |
- SPLICE\_F\_MORE } per l'argomento \param{flags} di \func{splice}, infatti
-anche se un valore nullo avrebbe dato gli stessi risultati, l'uso di questi
-flag, che si ricordi servono solo a dare suggerimenti al kernel, permette in
-genere di migliorare le prestazioni.
+Gli effetti dell'accesso ad una zona di memoria mappata su file possono essere
+piuttosto complessi, essi si possono comprendere solo tenendo presente che
+tutto quanto è comunque basato sul meccanismo della \index{memoria~virtuale}
+memoria virtuale. Questo comporta allora una serie di conseguenze. La più
+ovvia è che se si cerca di scrivere su una zona mappata in sola lettura si
+avrà l'emissione di un segnale di violazione di accesso (\const{SIGSEGV}),
+dato che i permessi sul segmento di memoria relativo non consentono questo
+tipo di accesso.
-Come accennato con l'introduzione di \func{splice} sono state realizzate altre
-due system call, \func{vmsplice} e \func{tee}, che utilizzano la stessa
-infrastruttura e si basano sullo stesso concetto di manipolazione e
-trasferimento di dati attraverso un buffer in kernel space; benché queste non
-attengono strettamente ad operazioni di trasferimento dati fra file
-descriptor, le tratteremo qui.
+È invece assai diversa la questione relativa agli accessi al di fuori della
+regione di cui si è richiesta la mappatura. A prima vista infatti si potrebbe
+ritenere che anch'essi debbano generare un segnale di violazione di accesso;
+questo però non tiene conto del fatto che, essendo basata sul meccanismo della
+paginazione \index{paginazione}, la mappatura in memoria non può che essere
+eseguita su un segmento di dimensioni rigorosamente multiple di quelle di una
+pagina, ed in generale queste potranno non corrispondere alle dimensioni
+effettive del file o della sezione che si vuole mappare.
-La prima funzione, \funcd{vmsplice}, è la più simile a \func{splice} e come
-indica il suo nome consente di trasferire i dati dalla memoria di un processo
-verso una \textit{pipe}, il suo prototipo è:
-\begin{functions}
- \headdecl{fcntl.h}
- \headdecl{sys/uio.h}
+\footnotetext[68]{dato che tutti faranno riferimento alle stesse pagine di
+ memoria.}
- \funcdecl{long vmsplice(int fd, const struct iovec *iov, unsigned long
- nr\_segs, unsigned int flags)}
-
- Trasferisce dati dalla memoria di un processo verso una \textit{pipe}.
+\footnotetext[69]{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}.}
- \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
- successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
- dei valori:
- \begin{errlist}
- \item[\errcode{EBADF}] o \param{fd} non è un file descriptor valido o non
- fa riferimento ad una \textit{pipe}.
- \item[\errcode{EINVAL}] si è usato un valore nullo per \param{nr\_segs}
- oppure si è usato \const{SPLICE\_F\_GIFT} ma la memoria non è allineata.
- \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione
- richiesta.
- \end{errlist}
- }
-\end{functions}
+\footnotetext{questo flag ed il precedente \const{MAP\_POPULATE} sono stati
+ introdotti nel kernel 2.5.46 insieme alla mappatura non lineare di cui
+ parleremo più avanti.}
-La \textit{pipe} dovrà essere specificata tramite il file descriptor
-corrispondente al suo capo aperto in scrittura (di nuovo si faccia riferimento
-a sez.~\ref{sec:ipc_unix}), mentre per indicare quali zone di memoria devono
-essere trasferita si deve utilizzare un vettore di strutture \struct{iovec}
-(vedi fig.~\ref{fig:file_iovec}), con le stesse con cui le si usano per l'I/O
-vettorizzato; le dimensioni del suddetto vettore devono essere passate
-nell'argomento \param{nr\_segs} che indica il numero di segmenti di memoria da
-trasferire. Sia per il vettore che per il valore massimo di \param{nr\_segs}
-valgono le stesse limitazioni illustrate in sez.~\ref{sec:file_multiple_io}.
+\begin{figure}[!htb]
+ \centering
+ \includegraphics[height=6cm]{img/mmap_boundary}
+ \caption{Schema della mappatura in memoria di una sezione di file di
+ dimensioni non corrispondenti al bordo di una pagina.}
+ \label{fig:file_mmap_boundary}
+\end{figure}
-In caso di successo la funzione ritorna il numero di byte trasferiti sulla
-pipe, in generale (se i dati una volta creati non devono essere riutilizzati)
-è opportuno utilizzare il flag \const{SPLICE\_F\_GIFT}; questo fa si che il
-kernel possa rimuovere le relative pagine dallo spazio degli indirizzi del
-processo, e scaricarle nella cache, così che queste possono essere utilizzate
-immediatamente senza necessità di eseguire una copia dei dati che contengono.
-La seconda funzione aggiunta insieme a \func{splice} è \func{tee}, che deve il
-suo nome all'omonimo comando in user space, perché in analogia con questo
-permette di duplicare i dati in ingresso su una \textit{pipe} su un'altra
-\textit{pipe}. In sostanza, sempre nell'ottica della manipolazione dei dati su
-dei buffer in kernel space, la funzione consente di eseguire una copia del
-contenuto del buffer stesso. Il prototipo di \funcd{tee} è il seguente:
-\begin{functions}
- \headdecl{fcntl.h}
+Il caso più comune è quello illustrato in fig.~\ref{fig:file_mmap_boundary},
+in cui la sezione di file non rientra nei confini di una pagina: in tal caso
+verrà il file sarà mappato su un segmento di memoria che si estende fino al
+bordo della pagina successiva.
- \funcdecl{long tee(int fd\_in, int fd\_out, size\_t len, unsigned int
- flags)}
-
- Duplica \param{len} byte da una \textit{pipe} ad un'altra.
+In questo caso è possibile accedere a quella zona di memoria che eccede le
+dimensioni specificate da \param{lenght}, senza ottenere un \const{SIGSEGV}
+poiché essa è presente nello spazio di indirizzi del processo, anche se non è
+mappata sul file. Il comportamento del sistema è quello di restituire un
+valore nullo per quanto viene letto, e di non riportare su file quanto viene
+scritto.
- \bodydesc{La funzione restituisce il numero di byte copiati in caso di
- successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
- dei valori:
- \begin{errlist}
- \item[\errcode{EINVAL}] o uno fra \param{fd\_in} e \param{fd\_out} non fa
- riferimento ad una \textit{pipe} o entrambi fanno riferimento alla
- stessa \textit{pipe}.
- \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione
- richiesta.
- \end{errlist}
- }
-\end{functions}
+Un caso più complesso è quello che si viene a creare quando le dimensioni del
+file mappato sono più corte delle dimensioni della mappatura, oppure quando il
+file è stato troncato, dopo che è stato mappato, ad una dimensione inferiore a
+quella della mappatura in memoria.
-La funzione copia \param{len} byte del contenuto di una \textit{pipe} su di
-un'altra; \param{fd\_in} deve essere il capo in lettura della \textit{pipe}
-sorgente e \param{fd\_out} il capo in scrittura della \textit{pipe}
-destinazione; a differenza di quanto avviene con \func{read} i dati letti con
-\func{tee} da \func{fd\_in} non vengono \textsl{consumati} e restano
-disponibili sulla \textit{pipe} per una successiva lettura (di nuovo per il
-comportamento delle \textit{pipe} si veda sez.~\ref{sec:ipc_unix}).
+In questa situazione, per la sezione di pagina parzialmente coperta dal
+contenuto del file, vale esattamente quanto visto in precedenza; invece per la
+parte che eccede, fino alle dimensioni date da \param{length}, l'accesso non
+sarà più possibile, ma il segnale emesso non sarà \const{SIGSEGV}, ma
+\const{SIGBUS}, come illustrato in fig.~\ref{fig:file_mmap_exceed}.
-La funzione restituisce il numero di byte copiati da una \textit{pipe}
-all'altra (o $-1$ in caso di errore), un valore nullo indica che non ci sono
-byte disponibili da copiare e che il capo in scrittura della pipe è stato
-chiuso.\footnote{si tenga presente però che questo non avviene se si è
- impostato il flag \const{SPLICE\_F\_NONBLOCK}, in tal caso infatti si
- avrebbe un errore di \errcode{EAGAIN}.} Un esempio di realizzazione del
-comando \texttt{tee} usando questa funzione, ripreso da quello fornito nella
-pagina di manuale e dall'esempio allegato al patch originale, è riportato in
-fig.~\ref{fig:tee_example}. Il programma consente di copiare il contenuto
-dello standard input sullo standard output e su un file specificato come
-argomento, il codice completo si trova nel file \texttt{tee.c} dei sorgenti
-allegati alla guida.
+Non tutti i file possono venire mappati in memoria, dato che, come illustrato
+in fig.~\ref{fig:file_mmap_layout}, la mappatura introduce una corrispondenza
+biunivoca fra una sezione di un file ed una sezione di memoria. Questo
+comporta che ad esempio non è possibile mappare in memoria file descriptor
+relativi a pipe, socket e fifo, per i quali non ha senso parlare di
+\textsl{sezione}. Lo stesso vale anche per alcuni file di dispositivo, che non
+dispongono della relativa operazione \func{mmap} (si ricordi quanto esposto in
+sez.~\ref{sec:file_vfs_work}). Si tenga presente però che esistono anche casi
+di dispositivi (un esempio è l'interfaccia al ponte PCI-VME del chip Universe)
+che sono utilizzabili solo con questa interfaccia.
-\begin{figure}[!htbp]
- \footnotesize \centering
- \begin{minipage}[c]{15cm}
- \includecodesample{listati/tee.c}
- \end{minipage}
- \normalsize
- \caption{Esempio di codice che usa \func{tee} per copiare i dati dello
- standard input sullo standard output e su un file.}
- \label{fig:tee_example}
+\begin{figure}[htb]
+ \centering
+ \includegraphics[height=6cm]{img/mmap_exceed}
+ \caption{Schema della mappatura in memoria di file di dimensioni inferiori
+ alla lunghezza richiesta.}
+ \label{fig:file_mmap_exceed}
\end{figure}
-La prima parte del programma (\texttt{\small 10--35}) si cura semplicemente di
-controllare (\texttt{\small 11--14}) che sia stato fornito almeno un argomento
-(il nome del file su cui scrivere), di aprirlo ({\small 15--19}) e che sia lo
-standard input (\texttt{\small 20--27}) che lo standard output (\texttt{\small
- 28--35}) corrispondano ad una \textit{pipe}.
+Dato che passando attraverso una \func{fork} lo spazio di indirizzi viene
+copiato integralmente, i file mappati in memoria verranno ereditati in maniera
+trasparente dal processo figlio, mantenendo gli stessi attributi avuti nel
+padre; così se si è usato \const{MAP\_SHARED} padre e figlio accederanno allo
+stesso file in maniera condivisa, mentre se si è usato \const{MAP\_PRIVATE}
+ciascuno di essi manterrà una sua versione privata indipendente. Non c'è
+invece nessun passaggio attraverso una \func{exec}, dato che quest'ultima
+sostituisce tutto lo spazio degli indirizzi di un processo con quello di un
+nuovo programma.
-Il ciclo principale (\texttt{\small 37--58}) inizia con la chiamata a
-\func{tee} che duplica il contenuto dello standard input sullo standard output
-(\texttt{\small 39}), questa parte è del tutto analoga ad una lettura ed
-infatti come nell'esempio di fig.~\ref{fig:splice_example} si controlla il
-valore di ritorno della funzione in \var{len}; se questo è nullo significa che
-non ci sono più dati da leggere e si chiude il ciclo (\texttt{\small 40}), se
-è negativo c'è stato un errore, ed allora si ripete la chiamata se questo è
-dovuto ad una interruzione (\texttt{\small 42--44}) o si stampa un messaggio
-di errore e si esce negli altri casi (\texttt{\small 44--47}).
+Quando si effettua la mappatura di un file vengono pure modificati i tempi ad
+esso associati (di cui si è trattato in sez.~\ref{sec:file_file_times}). Il
+valore di \var{st\_atime} può venir cambiato in qualunque istante a partire
+dal momento in cui la mappatura è stata effettuata: il primo riferimento ad
+una pagina mappata su un file aggiorna questo tempo. I valori di
+\var{st\_ctime} e \var{st\_mtime} possono venir cambiati solo quando si è
+consentita la scrittura sul file (cioè per un file mappato con
+\const{PROT\_WRITE} e \const{MAP\_SHARED}) e sono aggiornati dopo la scrittura
+o in corrispondenza di una eventuale \func{msync}.
-Una volta completata la copia dei dati sullo standard output si possono
-estrarre dalla standard input e scrivere sul file, di nuovo su usa un ciclo di
-scrittura (\texttt{\small 50--58}) in cui si ripete una chiamata a
-\func{splice} (\texttt{\small 51}) fintanto che non si sono scritti tutti i
-\var{len} byte copiati in precedenza con \func{tee} (il funzionamento è
-identico all'analogo ciclo di scrittura del precedente esempio di
-fig.~\ref{fig:splice_example}).
+Dato per i file mappati in memoria le operazioni di I/O sono gestite
+direttamente dalla \index{memoria~virtuale}memoria virtuale, occorre essere
+consapevoli delle interazioni che possono esserci con operazioni effettuate
+con l'interfaccia standard dei file di cap.~\ref{cha:file_unix_interface}. Il
+problema è che una volta che si è mappato un file, le operazioni di lettura e
+scrittura saranno eseguite sulla memoria, e riportate su disco in maniera
+autonoma dal sistema della memoria virtuale.
-Infine una nota finale riguardo \func{splice}, \func{vmsplice} e \func{tee}:
-occorre sottolineare che benché finora si sia parlato di trasferimenti o copie
-di dati in realtà nella implementazione di queste system call non è affatto
-detto che i dati vengono effettivamente spostati o copiati, il kernel infatti
-realizza le \textit{pipe} come un insieme di puntatori\footnote{per essere
- precisi si tratta di un semplice buffer circolare, un buon articolo sul tema
- si trova su \href{http://lwn.net/Articles/118750/}
- {\textsf{http://lwn.net/Articles/118750/}}.} alle pagine di memoria interna
-che contengono i dati, per questo una volta che i dati sono presenti nella
-memoria del kernel tutto quello che viene fatto è creare i suddetti puntatori
-ed aumentare il numero di referenze; questo significa che anche con \func{tee}
-non viene mai copiato nessun byte, vengono semplicemente copiati i puntatori.
+Pertanto se si modifica un file con l'interfaccia standard queste modifiche
+potranno essere visibili o meno a seconda del momento in cui la memoria
+virtuale trasporterà dal disco in memoria quella sezione del file, perciò è
+del tutto imprevedibile il risultato della modifica di un file nei confronti
+del contenuto della memoria su cui è mappato.
-% TODO?? dal 2.6.25 splice ha ottenuto il supporto per la ricezione su rete
+Per questo, è sempre sconsigliabile eseguire scritture su file attraverso
+l'interfaccia standard, quando lo si è mappato in memoria, è invece possibile
+usare l'interfaccia standard per leggere un file mappato in memoria, purché si
+abbia una certa cura; infatti l'interfaccia dell'I/O mappato in memoria mette
+a disposizione la funzione \funcd{msync} per sincronizzare il contenuto della
+memoria mappata con il file su disco; il suo prototipo è:
+\begin{functions}
+ \headdecl{unistd.h}
+ \headdecl{sys/mman.h}
+ \funcdecl{int msync(const void *start, size\_t length, int flags)}
+
+ Sincronizza i contenuti di una sezione di un file mappato in memoria.
+
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ errore nel qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EINVAL}] o \param{start} non è multiplo di
+ \const{PAGE\_SIZE}, o si è specificato un valore non valido per
+ \param{flags}.
+ \item[\errcode{EFAULT}] l'intervallo specificato non ricade in una zona
+ precedentemente mappata.
+ \end{errlist}
+ }
+\end{functions}
-\subsection{Gestione avanzata dell'accesso ai dati dei file}
-\label{sec:file_fadvise}
+La funzione esegue la sincronizzazione di quanto scritto nella sezione di
+memoria indicata da \param{start} e \param{offset}, scrivendo le modifiche sul
+file (qualora questo non sia già stato fatto). Provvede anche ad aggiornare i
+relativi tempi di modifica. In questo modo si è sicuri che dopo l'esecuzione
+di \func{msync} le funzioni dell'interfaccia standard troveranno un contenuto
+del file aggiornato.
-Nell'uso generico dell'interfaccia per l'accesso al contenuto dei file le
-operazioni di lettura e scrittura non necessitano di nessun intervento di
-supervisione da parte dei programmi, si eseguirà una \func{read} o una
-\func{write}, i dati verranno passati al kernel che provvederà ad effettuare
-tutte le operazioni (e a gestire il \textit{caching} dei dati) per portarle a
-termine in quello che ritiene essere il modo più efficiente.
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|l|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{MS\_ASYNC} & Richiede la sincronizzazione.\\
+ \const{MS\_SYNC} & Attende che la sincronizzazione si eseguita.\\
+ \const{MS\_INVALIDATE}& Richiede che le altre mappature dello stesso file
+ siano invalidate.\\
+ \hline
+ \end{tabular}
+ \caption{Le costanti che identificano i bit per la maschera binaria
+ dell'argomento \param{flag} di \func{msync}.}
+ \label{tab:file_mmap_rsync}
+\end{table}
-Il problema è che il concetto di migliore efficienza impiegato dal kernel è
-relativo all'uso generico, mentre esistono molti casi in cui ci sono esigenze
-specifiche dei singoli programmi, che avendo una conoscenza diretta di come
-verranno usati i file, possono necessitare di effettuare delle ottimizzazioni
-specifiche, relative alle proprie modalità di I/O sugli stessi. Tratteremo in
-questa sezione una serie funzioni che consentono ai programmi di ottimizzare
-il loro accesso ai dati dei file e controllare la gestione del relativo
-\textit{caching}.
+L'argomento \param{flag} è specificato come maschera binaria composta da un OR
+dei valori riportati in tab.~\ref{tab:file_mmap_rsync}, di questi però
+\const{MS\_ASYNC} e \const{MS\_SYNC} sono incompatibili; con il primo valore
+infatti la funzione si limita ad inoltrare la richiesta di sincronizzazione al
+meccanismo della memoria virtuale, ritornando subito, mentre con il secondo
+attende che la sincronizzazione sia stata effettivamente eseguita. Il terzo
+flag fa invalidare le pagine di cui si richiede la sincronizzazione per tutte
+le mappature dello stesso file, così che esse possano essere immediatamente
+aggiornate ai nuovi valori.
-Una prima funzione che può essere utilizzata per modificare la gestione
-ordinaria dell'I/O su un file è \funcd{readahead},\footnote{questa è una
- funzione specifica di Linux, introdotta con il kernel 2.4.13, e non deve
- essere usata se si vogliono scrivere programmi portabili.} che consente di
-richiedere una lettura anticipata del contenuto dello stesso in cache, così
-che le seguenti operazioni di lettura non debbano subire il ritardo dovuto
-all'accesso al disco; il suo prototipo è:
-\begin{functions}
- \headdecl{fcntl.h}
+Una volta che si sono completate le operazioni di I/O si può eliminare la
+mappatura della memoria usando la funzione \funcd{munmap}, il suo prototipo è:
+\begin{functions}
+ \headdecl{unistd.h}
+ \headdecl{sys/mman.h}
- \funcdecl{ssize\_t readahead(int fd, off64\_t *offset, size\_t count)}
+ \funcdecl{int munmap(void *start, size\_t length)}
- Esegue una lettura preventiva del contenuto di un file in cache.
+ Rilascia la mappatura sulla sezione di memoria specificata.
- \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di
- errore, nel qual caso \var{errno} assumerà uno dei valori:
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ errore nel qual caso \var{errno} assumerà uno dei valori:
\begin{errlist}
- \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor
- valido o non è aperto in lettura.
- \item[\errcode{EINVAL}] l'argomento \param{fd} si riferisce ad un tipo di
- file che non supporta l'operazione (come una pipe o un socket).
+ \item[\errcode{EINVAL}] l'intervallo specificato non ricade in una zona
+ precedentemente mappata.
\end{errlist}
}
\end{functions}
-La funzione richiede che venga letto in anticipo il contenuto del file
-\param{fd} a partire dalla posizione \param{offset} e per un ammontare di
-\param{count} byte, in modo da portarlo in cache. La funzione usa la
-\index{memoria~virtuale} memoria virtuale ed il meccanismo della
-\index{paginazione} paginazione per cui la lettura viene eseguita in blocchi
-corrispondenti alle dimensioni delle pagine di memoria, ed i valori di
-\param{offset} e \param{count} arrotondati di conseguenza.
+La funzione cancella la mappatura per l'intervallo specificato con
+\param{start} e \param{length}; ogni successivo accesso a tale regione causerà
+un errore di accesso in memoria. L'argomento \param{start} deve essere
+allineato alle dimensioni di una pagina, e la mappatura di tutte le pagine
+contenute anche parzialmente nell'intervallo indicato, verrà rimossa.
+Indicare un intervallo che non contiene mappature non è un errore. Si tenga
+presente inoltre che alla conclusione di un processo ogni pagina mappata verrà
+automaticamente rilasciata, mentre la chiusura del file descriptor usato per
+il \textit{memory mapping} non ha alcun effetto su di esso.
-La funzione estende quello che è un comportamento normale del
-kernel\footnote{per ottimizzare gli accessi al disco il kernel quando si legge
- un file, aspettandosi che l'accesso prosegua, esegue sempre una lettura
- anticipata di una certa quantità di dati; questo meccanismo viene chiamato
- \textit{readahead}, da cui deriva il nome della funzione.} effettuando la
-lettura in cache della sezione richiesta e bloccandosi fintanto che questa non
-viene completata. La posizione corrente sul file non viene modificata ed
-indipendentemente da quanto indicato con \param{count} la lettura dei dati si
-interrompe una volta raggiunta la fine del file.
+Lo standard POSIX prevede anche una funzione che permetta di cambiare le
+protezioni delle pagine di memoria; lo standard prevede che essa si applichi
+solo ai \textit{memory mapping} creati con \func{mmap}, ma nel caso di Linux
+la funzione può essere usata con qualunque pagina valida nella memoria
+virtuale. Questa funzione è \funcd{mprotect} ed il suo prototipo è:
+\begin{functions}
+% \headdecl{unistd.h}
+ \headdecl{sys/mman.h}
-Si può utilizzare questa funzione per velocizzare le operazioni di lettura
-all'interno del programma tutte le volte che si conosce in anticipo quanti
-dati saranno necessari in seguito. Si potrà così concentrare in un unico
-momento (ad esempio in fase di inizializzazione) la lettura, così da ottenere
-una migliore risposta nelle operazioni successive.
+ \funcdecl{int mprotect(const void *addr, size\_t len, int prot)}
+
+ Modifica le protezioni delle pagine di memoria comprese nell'intervallo
+ specificato.
-Il concetto di \func{readahead} viene generalizzato nello standard
-POSIX.1-2001 dalla funzione \funcd{posix\_fadvise},\footnote{anche se
- l'argomento \param{len} è stato modificato da \ctyp{size\_t} a \ctyp{off\_t}
- nella revisione POSIX.1-2003 TC5.} che consente di ``\textsl{avvisare}'' il
-kernel sulle modalità con cui si intende accedere nel futuro ad una certa
-porzione di un file,\footnote{la funzione però è stata introdotta su Linux
- solo a partire dal kernel 2.5.60.} così che esso possa provvedere le
-opportune ottimizzazioni; il suo prototipo, che può è disponibile solo se si
-definisce la macro \macro{\_XOPEN\_SOURCE} ad almeno 600, è:
+ \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ errore nel qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EINVAL}] il valore di \param{addr} non è valido o non è un
+ multiplo di \const{PAGE\_SIZE}.
+ \item[\errcode{EACCESS}] l'operazione non è consentita, ad esempio si è
+ cercato di marcare con \const{PROT\_WRITE} un segmento di memoria cui si
+ ha solo accesso in lettura.
+% \item[\errcode{ENOMEM}] non è stato possibile allocare le risorse
+% necessarie all'interno del kernel.
+% \item[\errcode{EFAULT}] si è specificato un indirizzo di memoria non
+% accessibile.
+ \end{errlist}
+ ed inoltre \errval{ENOMEM} ed \errval{EFAULT}.
+ }
+\end{functions}
+
+
+La funzione prende come argomenti un indirizzo di partenza in \param{addr},
+allineato alle dimensioni delle pagine di memoria, ed una dimensione
+\param{size}. La nuova protezione deve essere specificata in \param{prot} con
+una combinazione dei valori di tab.~\ref{tab:file_mmap_prot}. La nuova
+protezione verrà applicata a tutte le pagine contenute, anche parzialmente,
+dall'intervallo fra \param{addr} e \param{addr}+\param{size}-1.
+
+Infine Linux supporta alcune operazioni specifiche non disponibili su altri
+kernel unix-like. La prima di queste è la possibilità di modificare un
+precedente \textit{memory mapping}, ad esempio per espanderlo o restringerlo.
+Questo è realizzato dalla funzione \funcd{mremap}, il cui prototipo è:
\begin{functions}
- \headdecl{fcntl.h}
+ \headdecl{unistd.h}
+ \headdecl{sys/mman.h}
- \funcdecl{int posix\_fadvise(int fd, off\_t offset, off\_t len, int advice)}
+ \funcdecl{void * mremap(void *old\_address, size\_t old\_size , size\_t
+ new\_size, unsigned long flags)}
- Dichiara al kernel le future modalità di accesso ad un file.
+ Restringe o allarga una mappatura in memoria di un file.
- \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di
- errore, nel qual caso \var{errno} assumerà uno dei valori:
+ \bodydesc{La funzione restituisce l'indirizzo alla nuova area di memoria in
+ caso di successo od il valore \const{MAP\_FAILED} (pari a \texttt{(void *)
+ -1}) in caso di errore, nel qual caso \var{errno} assumerà uno dei
+ valori:
\begin{errlist}
- \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor
- valido.
- \item[\errcode{EINVAL}] il valore di \param{advice} non è valido o
- \param{fd} si riferisce ad un tipo di file che non supporta l'operazione
- (come una pipe o un socket).
- \item[\errcode{ESPIPE}] previsto dallo standard se \param{fd} è una pipe o
- un socket (ma su Linux viene restituito \errcode{EINVAL}).
+ \item[\errcode{EINVAL}] il valore di \param{old\_address} non è un
+ puntatore valido.
+ \item[\errcode{EFAULT}] ci sono indirizzi non validi nell'intervallo
+ specificato da \param{old\_address} e \param{old\_size}, o ci sono altre
+ mappature di tipo non corrispondente a quella richiesta.
+ \item[\errcode{ENOMEM}] non c'è memoria sufficiente oppure l'area di
+ memoria non può essere espansa all'indirizzo virtuale corrente, e non si
+ è specificato \const{MREMAP\_MAYMOVE} nei flag.
+ \item[\errcode{EAGAIN}] il segmento di memoria scelto è bloccato e non può
+ essere rimappato.
\end{errlist}
}
\end{functions}
-La funzione dichiara al kernel le modalità con cui intende accedere alla
-regione del file indicato da \param{fd} che inizia alla posizione
-\param{offset} e si estende per \param{len} byte. Se per \param{len} si usa un
-valore nullo la regione coperta sarà da \param{offset} alla fine del
-file.\footnote{questo è vero solo per le versioni più recenti, fino al kernel
- 2.6.6 il valore nullo veniva interpretato letteralmente.} Le modalità sono
-indicate dall'argomento \param{advice} che è una maschera binaria dei valori
-illustrati in tab.~\ref{tab:posix_fadvise_flag}. Si tenga presente comunque
-che la funzione dà soltanto un avvertimento, non esiste nessun vincolo per il
-kernel, che utilizza semplicemente l'informazione.
+La funzione richiede come argomenti \param{old\_address} (che deve essere
+allineato alle dimensioni di una pagina di memoria) che specifica il
+precedente indirizzo del \textit{memory mapping} e \param{old\_size}, che ne
+indica la dimensione. Con \param{new\_size} si specifica invece la nuova
+dimensione che si vuole ottenere. Infine l'argomento \param{flags} è una
+maschera binaria per i flag che controllano il comportamento della funzione.
+Il solo valore utilizzato è \const{MREMAP\_MAYMOVE}\footnote{per poter
+ utilizzare questa costante occorre aver definito \macro{\_GNU\_SOURCE} prima
+ di includere \file{sys/mman.h}.} che consente di eseguire l'espansione
+anche quando non è possibile utilizzare il precedente indirizzo. Per questo
+motivo, se si è usato questo flag, la funzione può restituire un indirizzo
+della nuova zona di memoria che non è detto coincida con \param{old\_address}.
-\begin{table}[htb]
- \centering
- \footnotesize
- \begin{tabular}[c]{|l|p{10cm}|}
- \hline
- \textbf{Valore} & \textbf{Significato} \\
- \hline
- \hline
- \const{POSIX\_FADV\_NORMAL} & Non ci sono avvisi specifici da fare
- riguardo le modalità di accesso, il
- comportamento sarà identico a quello che si
- avrebbe senza nessun avviso.\\
- \const{POSIX\_FADV\_SEQUENTIAL}& L'applicazione si aspetta di accedere di
- accedere ai dati specificati in maniera
- sequenziale, a partire dalle posizioni più
- basse.\\
- \const{POSIX\_FADV\_RANDOM} & I dati saranno letti in maniera
- completamente causale.\\
- \const{POSIX\_FADV\_NOREUSE} & I dati saranno acceduti una sola volta.\\
- \const{POSIX\_FADV\_WILLNEED}& I dati saranno acceduti a breve.\\
- \const{POSIX\_FADV\_DONTNEED}& I dati non saranno acceduti a breve.\\
- \hline
- \end{tabular}
- \caption{Valori dei bit dell'argomento \param{advice} di
- \func{posix\_fadvise} che indicano la modalità con cui si intende accedere
- ad un file.}
- \label{tab:posix_fadvise_flag}
-\end{table}
+La funzione si appoggia al sistema della \index{memoria~virtuale} memoria
+virtuale per modificare l'associazione fra gli indirizzi virtuali del processo
+e le pagine di memoria, modificando i dati direttamente nella
+\itindex{page~table} \textit{page table} del processo. Come per
+\func{mprotect} la funzione può essere usata in generale, anche per pagine di
+memoria non corrispondenti ad un \textit{memory mapping}, e consente così di
+implementare la funzione \func{realloc} in maniera molto efficiente.
-Anche \func{posix\_fadvise} si appoggia al sistema della memoria virtuale ed
-al meccanismo standard del \textit{readahead} utilizzato dal kernel; in
-particolare con \const{POSIX\_FADV\_SEQUENTIAL} si raddoppia la dimensione
-dell'ammontare di dati letti preventivamente rispetto al default, aspettandosi
-appunto una lettura sequenziale che li utilizzerà, mentre con
-\const{POSIX\_FADV\_RANDOM} si disabilita del tutto il suddetto meccanismo,
-dato che con un accesso del tutto casuale è inutile mettersi a leggere i dati
-immediatamente successivi gli attuali; infine l'uso di
-\const{POSIX\_FADV\_NORMAL} consente di riportarsi al comportamento di
-default.
+Una caratteristica comune a tutti i sistemi unix-like è che la mappatura in
+memoria di un file viene eseguita in maniera lineare, cioè parti successive di
+un file vengono mappate linearmente su indirizzi successivi in memoria.
+Esistono però delle applicazioni\footnote{in particolare la tecnica è usata
+ dai database o dai programmi che realizzano macchine virtuali.} in cui è
+utile poter mappare sezioni diverse di un file su diverse zone di memoria.
-Le due modalità \const{POSIX\_FADV\_NOREUSE} e \const{POSIX\_FADV\_WILLNEED}
-danno invece inizio ad una lettura in cache della regione del file indicata.
-La quantità di dati che verranno letti è ovviamente limitata in base al carico
-che si viene a creare sul sistema della memoria virtuale, ma in genere una
-lettura di qualche megabyte viene sempre soddisfatta (ed un valore superiore è
-solo raramente di qualche utilità). In particolare l'uso di
-\const{POSIX\_FADV\_WILLNEED} si può considerare l'equivalente POSIX di
-\func{readahead}.
+Questo è ovviamente sempre possibile eseguendo ripetutamente la funzione
+\func{mmap} per ciascuna delle diverse aree del file che si vogliono mappare
+in sequenza non lineare,\footnote{ed in effetti è quello che veniva fatto
+ anche con Linux prima che fossero introdotte queste estensioni.} ma questo
+approccio ha delle conseguenze molto pesanti in termini di prestazioni.
+Infatti per ciascuna mappatura in memoria deve essere definita nella
+\itindex{page~table} \textit{page table} del processo una nuova area di
+memoria virtuale\footnote{quella che nel gergo del kernel viene chiamata VMA
+ (\textit{virtual memory area}).} che corrisponda alla mappatura, in modo che
+questa diventi visibile nello spazio degli indirizzi come illustrato in
+fig.~\ref{fig:file_mmap_layout}.
-Infine con \const{POSIX\_FADV\_DONTNEED} si dice al kernel di liberare le
-pagine di cache occupate dai dati presenti nella regione di file indicata.
-Questa è una indicazione utile che permette di alleggerire il carico sulla
-cache, ed un programma può utilizzare periodicamente questa funzione per
-liberare pagine di memoria da dati che non sono più utilizzati per far posto a
-nuovi dati utili.\footnote{la pagina di manuale riporta l'esempio dello
- streaming di file di grosse dimensioni, dove le pagine occupate dai dati già
- inviati possono essere tranquillamente scartate.}
+Quando un processo esegue un gran numero di mappature diverse\footnote{si può
+ arrivare anche a centinaia di migliaia.} per realizzare a mano una mappatura
+non-lineare si avrà un accrescimento eccessivo della sua \itindex{page~table}
+\textit{page table}, e lo stesso accadrà per tutti gli altri processi che
+utilizzano questa tecnica. In situazioni in cui le applicazioni hanno queste
+esigenze si avranno delle prestazioni ridotte, dato che il kernel dovrà
+impiegare molte risorse\footnote{sia in termini di memoria interna per i dati
+ delle \itindex{page~table} \textit{page table}, che di CPU per il loro
+ aggiornamento.} solo per mantenere i dati di una gran quantità di
+\textit{memory mapping}.
-Sia \func{posix\_fadvise} che \func{readahead} attengono alla ottimizzazione
-dell'accesso in lettura; lo standard POSIX.1-2001 prevede anche una funzione
-specifica per le operazioni di scrittura, \func{posix\_fallocate},\footnote{la
- funzione è stata introdotta a partire dalle glibc 2.1.94.} che consente di
-preallocare dello spazio disco per assicurarsi che una seguente scrittura non
-fallisca, il suo prototipo, anch'esso disponibile solo se si definisce la
-macro \macro{\_XOPEN\_SOURCE} ad almeno 600, è:
+Per questo motivo con il kernel 2.5.46 è stato introdotto, ad opera di Ingo
+Molnar, un meccanismo che consente la mappatura non-lineare. Anche questa è
+una caratteristica specifica di Linux, non presente in altri sistemi
+unix-like. Diventa così possibile utilizzare una sola mappatura
+iniziale\footnote{e quindi una sola \textit{virtual memory area} nella
+ \itindex{page~table} \textit{page table} del processo.} e poi rimappare a
+piacere all'interno di questa i dati del file. Ciò è possibile grazie ad una
+nuova system call, \funcd{remap\_file\_pages}, il cui prototipo è:
\begin{functions}
- \headdecl{fcntl.h}
+ \headdecl{sys/mman.h}
- \funcdecl{int posix\_fallocate(int fd, off\_t offset, off\_t len)}
+ \funcdecl{int remap\_file\_pages(void *start, size\_t size, int prot,
+ ssize\_t pgoff, int flags)}
- Richiede la allocazione di spazio disco per un file.
+ Permette di rimappare non linearmente un precedente \textit{memory mapping}.
- \bodydesc{La funzione restituisce 0 in caso di successo e direttamente un
- codice di errore, in caso di fallimento, in questo caso \var{errno} non
- viene impostata, ma sarà restituito direttamente uno dei valori:
+ \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di
+ errore, nel qual caso \var{errno} assumerà uno dei valori:
\begin{errlist}
- \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor
- valido o non è aperto in scrittura.
- \item[\errcode{EINVAL}] o \param{offset} o \param{len} sono minori di
- zero.
- \item[\errcode{EFBIG}] il valore di (\param{offset} + \param{len}) eccede
- la dimensione massima consentita per un file.
- \item[\errcode{ENODEV}] l'argomento \param{fd} non fa riferimento ad un
- file regolare.
- \item[\errcode{ENOSPC}] non c'è sufficiente spazio disco per eseguire
- l'operazione.
- \item[\errcode{ESPIPE}] l'argomento \param{fd} è una pipe.
- \end{errlist}
+ \item[\errcode{EINVAL}] si è usato un valore non valido per uno degli
+ argomenti o \param{start} non fa riferimento ad un \textit{memory
+ mapping} valido creato con \const{MAP\_SHARED}.
+ \end{errlist}
}
\end{functions}
-La funzione si assicura che venga allocato sufficiente spazio disco perché sia
-possibile scrivere sul file indicato dall'argomento \param{fd} nella regione
-che inizia dalla posizione \param{offset} e si estende per \param{len} byte;
-se questa si estende oltre la fine del file le dimensioni di quest'ultimo
-saranno incrementate di conseguenza. Dopo aver eseguito con successo la
-funzione è garantito che una scrittura nella regione indicata non fallirà per
-mancanza di spazio disco.
+Per poter utilizzare questa funzione occorre anzitutto effettuare
+preliminarmente una chiamata a \func{mmap} con \const{MAP\_SHARED} per
+definire l'area di memoria che poi sarà rimappata non linearmente. Poi di
+chiamerà questa funzione per modificare le corrispondenze fra pagine di
+memoria e pagine del file; si tenga presente che \func{remap\_file\_pages}
+permette anche di mappare la stessa pagina di un file in più pagine della
+regione mappata.
-% TODO controllare la trattazione della nuova funzionalità di preallocazione
+La funzione richiede che si identifichi la sezione del file che si vuole
+riposizionare all'interno del \textit{memory mapping} con gli argomenti
+\param{pgoff} e \param{size}; l'argomento \param{start} invece deve indicare
+un indirizzo all'interno dell'area definita dall'\func{mmap} iniziale, a
+partire dal quale la sezione di file indicata verrà rimappata. L'argomento
+\param{prot} deve essere sempre nullo, mentre \param{flags} prende gli stessi
+valori di \func{mmap} (quelli di tab.~\ref{tab:file_mmap_prot}) ma di tutti i
+flag solo \const{MAP\_NONBLOCK} non viene ignorato.
-% 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
+Insieme alla funzione \func{remap\_file\_pages} nel kernel 2.5.46 con sono
+stati introdotti anche due nuovi flag per \func{mmap}: \const{MAP\_POPULATE} e
+\const{MAP\_NONBLOCK}. Il primo dei due consente di abilitare il meccanismo
+del \itindex{prefaulting} \textit{prefaulting}. Questo viene di nuovo in aiuto
+per migliorare le prestazioni in certe condizioni di utilizzo del
+\textit{memory mapping}.
-% TODO documentare \func{fallocate}, introdotta con il 2.6.23
-% vedi http://lwn.net/Articles/226710/ e http://lwn.net/Articles/240571/
-% http://kernelnewbies.org/Linux_2_6_23
-% \func{fallocate} con il 2.6.25 supporta pure XFS
+Il problema si pone tutte le volte che si vuole mappare in memoria un file di
+grosse dimensioni. Il comportamento normale del sistema della
+\index{memoria~virtuale} memoria virtuale è quello per cui la regione mappata
+viene aggiunta alla \itindex{page~table} \textit{page table} del processo, ma
+i dati verranno effettivamente utilizzati (si avrà cioè un
+\itindex{page~fault} \textit{page fault} che li trasferisce dal disco alla
+memoria) soltanto in corrispondenza dell'accesso a ciascuna delle pagine
+interessate dal \textit{memory mapping}.
+Questo vuol dire che il passaggio dei dati dal disco alla memoria avverrà una
+pagina alla volta con un gran numero di \itindex{page~fault} \textit{page
+ fault}, chiaramente se si sa in anticipo che il file verrà utilizzato
+immediatamente, è molto più efficiente eseguire un \itindex{prefaulting}
+\textit{prefaulting} in cui tutte le pagine di memoria interessate alla
+mappatura vengono ``\textsl{popolate}'' in una sola volta, questo
+comportamento viene abilitato quando si usa con \func{mmap} il flag
+\const{MAP\_POPULATE}.
-%\subsection{L'utilizzo delle porte di I/O}
-%\label{sec:file_io_port}
-%
-% TODO l'I/O sulle porte di I/O
-% consultare le manpage di ioperm, iopl e outb
+Dato che l'uso di \const{MAP\_POPULATE} comporta dell'I/O su disco che può
+rallentare l'esecuzione di \func{mmap} è stato introdotto anche un secondo
+flag, \const{MAP\_NONBLOCK}, che esegue un \itindex{prefaulting}
+\textit{prefaulting} più limitato in cui vengono popolate solo le pagine della
+mappatura che già si trovano nella cache del kernel.\footnote{questo può
+ essere utile per il linker dinamico, in particolare quando viene effettuato
+ il \textit{prelink} delle applicazioni.}
+\itindend{memory~mapping}
+% TODO documentare \func{madvise}
+\subsection{I/O vettorizzato: \func{readv} e \func{writev}}
+\label{sec:file_multiple_io}
+Un caso abbastanza comune è quello in cui ci si trova a dover eseguire una
+serie multipla di operazioni di I/O, come una serie di letture o scritture di
+vari buffer. Un esempio tipico è quando i dati sono strutturati nei campi di
+una struttura ed essi devono essere caricati o salvati su un file. Benché
+l'operazione sia facilmente eseguibile attraverso una serie multipla di
+chiamate, ci sono casi in cui si vuole poter contare sulla atomicità delle
+operazioni.
-\section{Il file locking}
-\label{sec:file_locking}
+Per questo motivo su BSD 4.2 sono state introdotte due nuove system call,
+\funcd{readv} e \funcd{writev},\footnote{in Linux le due funzioni sono riprese
+ da BSD4.4, esse sono previste anche dallo standard POSIX.1-2001.} che
+permettono di effettuare con una sola chiamata una lettura o una scrittura su
+una serie di buffer (quello che viene chiamato \textsl{I/O vettorizzato}. I
+relativi prototipi sono:
+\begin{functions}
+ \headdecl{sys/uio.h}
+
+ \funcdecl{int readv(int fd, const struct iovec *vector, int count)}
+ \funcdecl{int writev(int fd, const struct iovec *vector, int count)}
-\index{file!locking|(}
+ Eseguono rispettivamente una lettura o una scrittura vettorizzata.
+
+ \bodydesc{Le funzioni restituiscono il numero di byte letti o scritti in
+ caso di successo, e -1 in caso di errore, nel qual caso \var{errno}
+ assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EINVAL}] si è specificato un valore non valido per uno degli
+ argomenti (ad esempio \param{count} è maggiore di \const{IOV\_MAX}).
+ \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima di
+ di avere eseguito una qualunque lettura o scrittura.
+ \item[\errcode{EAGAIN}] \param{fd} è stato aperto in modalità non bloccante e
+ non ci sono dati in lettura.
+ \item[\errcode{EOPNOTSUPP}] la coda delle richieste è momentaneamente piena.
+ \end{errlist}
+ ed anche \errval{EISDIR}, \errval{EBADF}, \errval{ENOMEM}, \errval{EFAULT}
+ (se non sono stati allocati correttamente i buffer specificati nei campi
+ \var{iov\_base}), più gli eventuali errori delle funzioni di lettura e
+ scrittura eseguite su \param{fd}.}
+\end{functions}
-In sez.~\ref{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 \itindex{append~mode} \textit{append mode}, quando più processi scrivono
-contemporaneamente sullo stesso file non è possibile determinare la sequenza
-in cui essi opereranno.
+Entrambe le funzioni usano una struttura \struct{iovec}, la cui definizione è
+riportata in fig.~\ref{fig:file_iovec}, che definisce dove i dati devono
+essere letti o scritti ed in che quantità. Il primo campo della struttura,
+\var{iov\_base}, contiene l'indirizzo del buffer ed il secondo,
+\var{iov\_len}, la dimensione dello stesso.
-Questo causa la possibilità di una \itindex{race~condition} \textit{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.
+\begin{figure}[!htb]
+ \footnotesize \centering
+ \begin{minipage}[c]{15cm}
+ \includestruct{listati/iovec.h}
+ \end{minipage}
+ \normalsize
+ \caption{La struttura \structd{iovec}, usata dalle operazioni di I/O
+ vettorizzato.}
+ \label{fig:file_iovec}
+\end{figure}
-In tutti questi casi il \textit{file locking} è la tecnica che permette di
-evitare le \itindex{race~condition} \textit{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.
+La lista dei buffer da utilizzare viene indicata attraverso l'argomento
+\param{vector} che è un vettore di strutture \struct{iovec}, la cui lunghezza
+è specificata dall'argomento \param{count}.\footnote{fino alle libc5, Linux
+ usava \type{size\_t} come tipo dell'argomento \param{count}, una scelta
+ logica, che però è stata dismessa per restare aderenti allo standard
+ POSIX.1-2001.} Ciascuna struttura dovrà essere inizializzata opportunamente
+per indicare i vari buffer da e verso i quali verrà eseguito il trasferimento
+dei dati. Essi verranno letti (o scritti) nell'ordine in cui li si sono
+specificati nel vettore \param{vector}.
+La standardizzazione delle due funzioni all'interno della revisione
+POSIX.1-2001 prevede anche che sia possibile avere un limite al numero di
+elementi del vettore \param{vector}. Qualora questo sussista, esso deve essere
+indicato dal valore dalla costante \const{IOV\_MAX}, definita come le altre
+costanti analoghe (vedi sez.~\ref{sec:sys_limits}) in \file{limits.h}; lo
+stesso valore deve essere ottenibile in esecuzione tramite la funzione
+\func{sysconf} richiedendo l'argomento \const{\_SC\_IOV\_MAX} (vedi
+sez.~\ref{sec:sys_sysconf}).
+Nel caso di Linux il limite di sistema è di 1024, però se si usano le
+\acr{glibc} queste forniscono un \textit{wrapper} per le system call che si
+accorge se una operazione supererà il precedente limite, in tal caso i dati
+verranno letti o scritti con le usuali \func{read} e \func{write} usando un
+buffer di dimensioni sufficienti appositamente allocato e sufficiente a
+contenere tutti i dati indicati da \param{vector}. L'operazione avrà successo
+ma si perderà l'atomicità del trasferimento da e verso la destinazione finale.
-\subsection{L'\textit{advisory locking}}
-\label{sec:file_record_locking}
+% TODO verificare cosa succederà a preadv e pwritev o alla nuova niovec
+% vedi http://lwn.net/Articles/164887/
+% inserite nel kernel 2.6.30, vedi http://lwn.net/Articles/326818/
-La prima modalità di \textit{file locking} che è stata implementata nei
-sistemi unix-like è quella che viene usualmente chiamata \textit{advisory
- locking},\footnote{Stevens in \cite{APUE} fa riferimento a questo argomento
- come al \textit{record locking}, dizione utilizzata anche dal manuale delle
- \acr{glibc}; nelle pagine di manuale si parla di \textit{discrectionary file
- lock} per \func{fcntl} e di \textit{advisory locking} per \func{flock},
- mentre questo nome viene usato da Stevens per riferirsi al \textit{file
- locking} POSIX. Dato che la dizione \textit{record locking} è quantomeno
- ambigua, in quanto in un sistema Unix non esiste niente che possa fare
- riferimento al concetto di \textit{record}, alla fine si è scelto di
- mantenere il nome \textit{advisory locking}.} in quanto sono i singoli
-processi, e non il sistema, che si incaricano di asserire e verificare se
-esistono delle condizioni di blocco per l'accesso ai file. Questo significa
-che le funzioni \func{read} o \func{write} vengono eseguite comunque e non
-risentono affatto della presenza di un eventuale \textit{lock}; pertanto è
-sempre compito dei vari processi che intendono usare il file locking,
-controllare esplicitamente lo stato dei file condivisi prima di accedervi,
-utilizzando le relative funzioni.
-
-In generale si distinguono due tipologie di \textit{file lock}:\footnote{di
- seguito ci riferiremo sempre ai blocchi di accesso ai file con la
- nomenclatura inglese di \textit{file lock}, o più brevemente con
- \textit{lock}, per evitare confusioni linguistiche con il blocco di un
- processo (cioè la condizione in cui il processo viene posto in stato di
- \textit{sleep}).} la prima è il cosiddetto \textit{shared lock}, detto anche
-\textit{read lock} in quanto serve a bloccare l'accesso in scrittura su un
-file affinché il suo contenuto non venga modificato mentre lo si legge. Si
-parla appunto di \textsl{blocco condiviso} in quanto più processi possono
-richiedere contemporaneamente uno \textit{shared lock} su un file per
-proteggere il loro accesso in lettura.
-La seconda tipologia è il cosiddetto \textit{exclusive lock}, detto anche
-\textit{write lock} in quanto serve a bloccare l'accesso su un file (sia in
-lettura che in scrittura) da parte di altri processi mentre lo si sta
-scrivendo. Si parla di \textsl{blocco esclusivo} appunto perché un solo
-processo alla volta può richiedere un \textit{exclusive lock} su un file per
-proteggere il suo accesso in scrittura.
+\subsection{L'I/O diretto fra file descriptor: \func{sendfile} e \func{splice}}
+\label{sec:file_sendfile_splice}
+
+Uno dei problemi che si presentano nella gestione dell'I/O è quello in cui si
+devono trasferire grandi quantità di dati da un file descriptor ed un altro;
+questo usualmente comporta la lettura dei dati dal primo file descriptor in un
+buffer in memoria, da cui essi vengono poi scritti sul secondo.
+
+Benché il kernel ottimizzi la gestione di questo processo quando si ha a che
+fare con file normali, in generale quando i dati da trasferire sono molti si
+pone il problema di effettuare trasferimenti di grandi quantità di dati da
+kernel space a user space e all'indietro, quando in realtà potrebbe essere più
+efficiente mantenere tutto in kernel space. Tratteremo in questa sezione
+alcune funzioni specialistiche che permettono di ottimizzare le prestazioni in
+questo tipo di situazioni.
+
+La prima funzione che si pone l'obiettivo di ottimizzare il trasferimento dei
+dati fra due file descriptor è \funcd{sendfile};\footnote{la funzione è stata
+ introdotta con i kernel della serie 2.2, e disponibile dalle \acr{glibc}
+ 2.1.} la funzione è presente in diverse versioni di Unix,\footnote{la si
+ ritrova ad esempio in FreeBSD, HPUX ed altri Unix.} ma non è presente né in
+POSIX.1-2001 né in altri standard,\footnote{pertanto si eviti di utilizzarla
+ se si devono scrivere programmi portabili.} per cui per essa vengono
+utilizzati prototipi e semantiche differenti; nel caso di Linux il suo
+prototipo è:
+\begin{functions}
+ \headdecl{sys/sendfile.h}
+
+ \funcdecl{ssize\_t sendfile(int out\_fd, int in\_fd, off\_t *offset, size\_t
+ count)}
+
+ Copia dei dati da un file descriptor ad un altro.
+
+ \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
+ successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
+ dei valori:
+ \begin{errlist}
+ \item[\errcode{EAGAIN}] si è impostata la modalità non bloccante su
+ \param{out\_fd} e la scrittura si bloccherebbe.
+ \item[\errcode{EINVAL}] i file descriptor non sono validi, o sono bloccati
+ (vedi sez.~\ref{sec:file_locking}), o \func{mmap} non è disponibile per
+ \param{in\_fd}.
+ \item[\errcode{EIO}] si è avuto un errore di lettura da \param{in\_fd}.
+ \item[\errcode{ENOMEM}] non c'è memoria sufficiente per la lettura da
+ \param{in\_fd}.
+ \end{errlist}
+ ed inoltre \errcode{EBADF} e \errcode{EFAULT}.
+ }
+\end{functions}
+
+La funzione copia direttamente \param{count} byte dal file descriptor
+\param{in\_fd} al file descriptor \param{out\_fd}; in caso di successo
+funzione ritorna il numero di byte effettivamente copiati da \param{in\_fd} a
+\param{out\_fd} o $-1$ in caso di errore, come le ordinarie \func{read} e
+\func{write} questo valore può essere inferiore a quanto richiesto con
+\param{count}.
-\begin{table}[htb]
- \centering
- \footnotesize
- \begin{tabular}[c]{|l|c|c|c|}
- \hline
- \textbf{Richiesta} & \multicolumn{3}{|c|}{\textbf{Stato del file}}\\
- \cline{2-4}
- &Nessun lock&\textit{Read lock}&\textit{Write lock}\\
- \hline
- \hline
- \textit{Read lock} & SI & SI & NO \\
- \textit{Write lock}& SI & NO & NO \\
- \hline
- \end{tabular}
- \caption{Tipologie di file locking.}
- \label{tab:file_file_lock}
-\end{table}
+Se il puntatore \param{offset} è nullo la funzione legge i dati a partire
+dalla posizione corrente su \param{in\_fd}, altrimenti verrà usata la
+posizione indicata dal valore puntato da \param{offset}; in questo caso detto
+valore sarà aggiornato, come \textit{value result argument}, per indicare la
+posizione del byte successivo all'ultimo che è stato letto, mentre la
+posizione corrente sul file non sarà modificata. Se invece \param{offset} è
+nullo la posizione corrente sul file sarà aggiornata tenendo conto dei byte
+letti da \param{in\_fd}.
-In Linux sono disponibili due interfacce per utilizzare l'\textit{advisory
- locking}, la prima è quella derivata da BSD, che è basata sulla funzione
-\func{flock}, la seconda è quella standardizzata da POSIX.1 (derivata da
-System V), che è basata sulla funzione \func{fcntl}. I \textit{file lock}
-sono implementati in maniera completamente indipendente nelle due interfacce,
-che pertanto possono coesistere senza interferenze.
+Fino ai kernel della serie 2.4 la funzione è utilizzabile su un qualunque file
+descriptor, e permette di sostituire la invocazione successiva di una
+\func{read} e una \func{write} (e l'allocazione del relativo buffer) con una
+sola chiamata a \funcd{sendfile}. In questo modo si può diminuire il numero di
+chiamate al sistema e risparmiare in trasferimenti di dati da kernel space a
+user space e viceversa. La massima utilità della funzione si ha comunque per
+il trasferimento di dati da un file su disco ad un socket di
+rete,\footnote{questo è il caso classico del lavoro eseguito da un server web,
+ ed infatti Apache ha una opzione per il supporto esplicito di questa
+ funzione.} dato che in questo caso diventa possibile effettuare il
+trasferimento diretto via DMA dal controller del disco alla scheda di rete,
+senza neanche allocare un buffer nel kernel,\footnote{il meccanismo è detto
+ \textit{zerocopy} in quanto i dati non vengono mai copiati dal kernel, che
+ si limita a programmare solo le operazioni di lettura e scrittura via DMA.}
+ottenendo la massima efficienza possibile senza pesare neanche sul processore.
-Entrambe le interfacce prevedono la stessa procedura di funzionamento: si
-inizia sempre con il richiedere l'opportuno \textit{file lock} (un
-\textit{exclusive lock} per una scrittura, uno \textit{shared lock} per una
-lettura) prima di eseguire l'accesso ad un file. Se il lock viene acquisito
-il processo prosegue l'esecuzione, altrimenti (a meno di non aver richiesto un
-comportamento non bloccante) viene posto in stato di sleep. Una volta finite
-le operazioni sul file si deve provvedere a rimuovere il lock. La situazione
-delle varie possibilità è riassunta in tab.~\ref{tab:file_file_lock}, dove si
-sono riportati, per le varie tipologie di lock presenti su un file, il
-risultato che si ha in corrispondenza alle due tipologie di \textit{file lock}
-menzionate, nel successo della richiesta.
+In seguito però ci si è accorti che, fatta eccezione per il trasferimento
+diretto da file a socket, non sempre \func{sendfile} comportava miglioramenti
+significativi delle prestazioni rispetto all'uso in sequenza di \func{read} e
+\func{write},\footnote{nel caso generico infatti il kernel deve comunque
+ allocare un buffer ed effettuare la copia dei dati, e in tal caso spesso il
+ guadagno ottenibile nel ridurre il numero di chiamate al sistema non
+ compensa le ottimizzazioni che possono essere fatte da una applicazione in
+ user space che ha una conoscenza diretta su come questi sono strutturati.} e
+che anzi in certi casi si potevano avere anche dei peggioramenti. Questo ha
+portato, per i kernel della serie 2.6,\footnote{per alcune motivazioni di
+ questa scelta si può fare riferimento a quanto illustrato da Linus Torvalds
+ in \href{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}
+ {\textsf{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}}.}
+alla decisione di consentire l'uso della funzione soltanto quando il file da
+cui si legge supporta le operazioni di \textit{memory mapping} (vale a dire
+non è un socket) e quello su cui si scrive è un socket; in tutti gli altri
+casi l'uso di \func{sendfile} darà luogo ad un errore di \errcode{EINVAL}.
-Si tenga presente infine che il controllo di accesso e la gestione dei
-permessi viene effettuata quando si apre un file, l'unico controllo residuo
-che si può avere riguardo il \textit{file locking} è che il tipo di lock che
-si vuole ottenere su un file deve essere compatibile con le modalità di
-apertura dello stesso (in lettura per un read lock e in scrittura per un write
-lock).
+Nonostante ci possano essere casi in cui \func{sendfile} non migliora le
+prestazioni, le motivazioni addotte non convincono del tutto e resta il dubbio
+se la scelta di disabilitarla sempre per il trasferimento di dati fra file di
+dati sia davvero corretta. Se ci sono peggioramenti di prestazioni infatti si
+può sempre fare ricorso all'uso successivo di, ma lasciare a disposizione la
+funzione consentirebbe se non altro, anche in assenza di guadagni di
+prestazioni, di semplificare la gestione della copia dei dati fra file,
+evitando di dover gestire l'allocazione di un buffer temporaneo per il loro
+trasferimento; inoltre si avrebbe comunque il vantaggio di evitare inutili
+trasferimenti di dati da kernel space a user space e viceversa.
-%% Si ricordi che
-%% la condizione per acquisire uno \textit{shared lock} è che il file non abbia
-%% già un \textit{exclusive lock} attivo, mentre per acquisire un
-%% \textit{exclusive lock} non deve essere presente nessun tipo di blocco.
+Questo dubbio si può comunque ritenere superato con l'introduzione, avvenuto a
+partire dal kernel 2.6.17, della nuova system call \func{splice}. Lo scopo di
+questa funzione è quello di fornire un meccanismo generico per il
+trasferimento di dati da o verso un file utilizzando un buffer gestito
+internamente dal kernel. Descritta in questi termini \func{splice} sembra
+semplicemente un ``\textsl{dimezzamento}'' di \func{sendfile}.\footnote{nel
+ senso che un trasferimento di dati fra due file con \func{sendfile} non
+ sarebbe altro che la lettura degli stessi su un buffer seguita dalla
+ relativa scrittura, cosa che in questo caso si dovrebbe eseguire con due
+ chiamate a \func{splice}.} In realtà le due system call sono profondamente
+diverse nel loro meccanismo di funzionamento;\footnote{questo fino al kernel
+ 2.6.23, dove \func{sendfile} è stata reimplementata in termini di
+ \func{splice}, pur mantenendo disponibile la stessa interfaccia verso l'user
+ space.} \func{sendfile} infatti, come accennato, non necessita di avere a
+disposizione un buffer interno, perché esegue un trasferimento diretto di
+dati; questo la rende in generale più efficiente, ma anche limitata nelle sue
+applicazioni, dato che questo tipo di trasferimento è possibile solo in casi
+specifici.\footnote{e nel caso di Linux questi sono anche solo quelli in cui
+ essa può essere effettivamente utilizzata.}
+Il concetto che sta dietro a \func{splice} invece è diverso,\footnote{in
+ realtà la proposta originale di Larry Mc Voy non differisce poi tanto negli
+ scopi da \func{sendfile}, quello che rende \func{splice} davvero diversa è
+ stata la reinterpretazione che ne è stata fatta nell'implementazione su
+ Linux realizzata da Jens Anxboe, concetti che sono esposti sinteticamente
+ dallo stesso Linus Torvalds in \href{http://kerneltrap.org/node/6505}
+ {\textsf{http://kerneltrap.org/node/6505}}.} si tratta semplicemente di una
+funzione che consente di fare in maniera del tutto generica delle operazioni
+di trasferimento di dati fra un file e un buffer gestito interamente in kernel
+space. In questo caso il cuore della funzione (e delle affini \func{vmsplice}
+e \func{tee}, che tratteremo più avanti) è appunto l'uso di un buffer in
+kernel space, e questo è anche quello che ne ha semplificato l'adozione,
+perché l'infrastruttura per la gestione di un tale buffer è presente fin dagli
+albori di Unix per la realizzazione delle \textit{pipe} (vedi
+sez.~\ref{sec:ipc_unix}). Dal punto di vista concettuale allora \func{splice}
+non è altro che una diversa interfaccia (rispetto alle \textit{pipe}) con cui
+utilizzare in user space l'oggetto ``\textsl{buffer in kernel space}''.
-\subsection{La funzione \func{flock}}
-\label{sec:file_flock}
+Così se per una \textit{pipe} o una \textit{fifo} il buffer viene utilizzato
+come area di memoria (vedi fig.~\ref{fig:ipc_pipe_singular}) dove appoggiare i
+dati che vengono trasferiti da un capo all'altro della stessa per creare un
+meccanismo di comunicazione fra processi, nel caso di \func{splice} il buffer
+viene usato o come fonte dei dati che saranno scritti su un file, o come
+destinazione dei dati che vengono letti da un file. La funzione \funcd{splice}
+fornisce quindi una interfaccia generica che consente di trasferire dati da un
+buffer ad un file o viceversa; il suo prototipo, accessibile solo dopo aver
+definito la macro \macro{\_GNU\_SOURCE},\footnote{si ricordi che questa
+ funzione non è contemplata da nessuno standard, è presente solo su Linux, e
+ pertanto deve essere evitata se si vogliono scrivere programmi portabili.}
+è il seguente:
+\begin{functions}
+ \headdecl{fcntl.h}
-La prima interfaccia per il file locking, quella derivata da BSD, permette di
-eseguire un blocco solo su un intero file; la funzione usata per richiedere e
-rimuovere un \textit{file lock} è \funcd{flock}, ed il suo prototipo è:
-\begin{prototype}{sys/file.h}{int flock(int fd, int operation)}
-
- Applica o rimuove un \textit{file lock} sul file \param{fd}.
+ \funcdecl{long splice(int fd\_in, off\_t *off\_in, int fd\_out, off\_t
+ *off\_out, size\_t len, unsigned int flags)}
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
- errore, nel qual caso \var{errno} assumerà uno dei valori:
+ Trasferisce dati da un file verso una pipe o viceversa.
+
+ \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
+ successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
+ dei valori:
\begin{errlist}
- \item[\errcode{EWOULDBLOCK}] il file ha già un blocco attivo, e si è
- specificato \const{LOCK\_NB}.
+ \item[\errcode{EBADF}] uno o entrambi fra \param{fd\_in} e \param{fd\_out}
+ non sono file descriptor validi o, rispettivamente, non sono stati
+ aperti in lettura o scrittura.
+ \item[\errcode{EINVAL}] il filesystem su cui si opera non supporta
+ \func{splice}, oppure nessuno dei file descriptor è una pipe, oppure si
+ è dato un valore a \param{off\_in} o \param{off\_out} ma il
+ corrispondente file è un dispositivo che non supporta la funzione
+ \func{seek}.
+ \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione
+ richiesta.
+ \item[\errcode{ESPIPE}] o \param{off\_in} o \param{off\_out} non sono
+ \const{NULL} ma il corrispondente file descriptor è una \textit{pipe}.
\end{errlist}
}
-\end{prototype}
+\end{functions}
-La funzione può essere usata per acquisire o rilasciare un \textit{file lock}
-a seconda di quanto specificato tramite il valore dell'argomento
-\param{operation}, questo viene interpretato come maschera binaria, e deve
-essere passato utilizzando le costanti riportate in
-tab.~\ref{tab:file_flock_operation}.
+La funzione esegue un trasferimento di \param{len} byte dal file descriptor
+\param{fd\_in} al file descriptor \param{fd\_out}, uno dei quali deve essere
+una \textit{pipe}; l'altro file descriptor può essere
+qualunque.\footnote{questo significa che può essere, oltre che un file di
+ dati, anche un altra \textit{pipe}, o un socket.} Come accennato una
+\textit{pipe} non è altro che un buffer in kernel space, per cui a seconda che
+essa sia usata per \param{fd\_in} o \param{fd\_out} si avrà rispettivamente la
+copia dei dati dal buffer al file o viceversa.
+
+In caso di successo la funzione ritorna il numero di byte trasferiti, che può
+essere, come per le normali funzioni di lettura e scrittura su file, inferiore
+a quelli richiesti; un valore negativo indicherà un errore mentre un valore
+nullo indicherà che non ci sono dati da trasferire (ad esempio si è giunti
+alla fine del file in lettura). Si tenga presente che, a seconda del verso del
+trasferimento dei dati, la funzione si comporta nei confronti del file
+descriptor che fa riferimento al file ordinario, come \func{read} o
+\func{write}, e pertanto potrà anche bloccarsi (a meno che non si sia aperto
+il suddetto file in modalità non bloccante).
+
+I due argomenti \param{off\_in} e \param{off\_out} consentono di specificare,
+come per l'analogo \param{offset} di \func{sendfile}, la posizione all'interno
+del file da cui partire per il trasferimento dei dati. Come per
+\func{sendfile} un valore nullo indica di usare la posizione corrente sul
+file, ed essa sarà aggiornata automaticamente secondo il numero di byte
+trasferiti. Un valore non nullo invece deve essere un puntatore ad una
+variabile intera che indica la posizione da usare; questa verrà aggiornata, al
+ritorno della funzione, al byte successivo all'ultimo byte trasferito.
+Ovviamente soltanto uno di questi due argomenti, e più precisamente quello che
+fa riferimento al file descriptor non associato alla \textit{pipe}, può essere
+specificato come valore non nullo.
+
+Infine l'argomento \param{flags} consente di controllare alcune
+caratteristiche del funzionamento della funzione; il contenuto è una maschera
+binaria e deve essere specificato come OR aritmetico dei valori riportati in
+tab.~\ref{tab:splice_flag}. Alcuni di questi valori vengono utilizzati anche
+dalle funzioni \func{vmsplice} e \func{tee} per cui la tabella riporta le
+descrizioni complete di tutti i valori possibili anche quando, come per
+\const{SPLICE\_F\_GIFT}, questi non hanno effetto su \func{splice}.
\begin{table}[htb]
\centering
\footnotesize
- \begin{tabular}[c]{|l|l|}
+ \begin{tabular}[c]{|l|p{10cm}|}
\hline
\textbf{Valore} & \textbf{Significato} \\
\hline
\hline
- \const{LOCK\_SH} & Asserisce uno \textit{shared lock} sul file.\\
- \const{LOCK\_EX} & Asserisce un \textit{esclusive lock} sul file.\\
- \const{LOCK\_UN} & Rilascia il \textit{file lock}.\\
- \const{LOCK\_NB} & Impedisce che la funzione si blocchi nella
- richiesta di un \textit{file lock}.\\
- \hline
+ \const{SPLICE\_F\_MOVE} & Suggerisce al kernel di spostare le pagine
+ di memoria contenenti i dati invece di
+ copiarle;\footnotemark viene usato soltanto
+ da \func{splice}.\\
+ \const{SPLICE\_F\_NONBLOCK}& Richiede di operare in modalità non
+ bloccante; questo flag influisce solo sulle
+ operazioni che riguardano l'I/O da e verso la
+ \textit{pipe}. Nel caso di \func{splice}
+ questo significa che la funzione potrà
+ comunque bloccarsi nell'accesso agli altri
+ file descriptor (a meno che anch'essi non
+ siano stati aperti in modalità non
+ bloccante).\\
+ \const{SPLICE\_F\_MORE} & Indica al kernel che ci sarà l'invio di
+ ulteriori dati in una \func{splice}
+ successiva, questo è un suggerimento utile
+ che viene usato quando \param{fd\_out} è un
+ socket.\footnotemark Attualmente viene usato
+ solo da \func{splice}, potrà essere
+ implementato in futuro anche per
+ \func{vmsplice} e \func{tee}.\\
+ \const{SPLICE\_F\_GIFT} & Le pagine di memoria utente sono
+ ``\textsl{donate}'' al kernel;\footnotemark
+ se impostato una seguente \func{splice} che
+ usa \const{SPLICE\_F\_MOVE} potrà spostare le
+ pagine con successo, altrimenti esse dovranno
+ essere copiate; per usare questa opzione i
+ dati dovranno essere opportunamente allineati
+ in posizione ed in dimensione alle pagine di
+ memoria. Viene usato soltanto da
+ \func{vmsplice}.\\
+ \hline
\end{tabular}
- \caption{Valori dell'argomento \param{operation} di \func{flock}.}
- \label{tab:file_flock_operation}
+ \caption{Le costanti che identificano i bit della maschera binaria
+ dell'argomento \param{flags} di \func{splice}, \func{vmsplice} e
+ \func{tee}.}
+ \label{tab:splice_flag}
\end{table}
-I primi due valori, \const{LOCK\_SH} e \const{LOCK\_EX} permettono di
-richiedere un \textit{file lock}, ed ovviamente devono essere usati in maniera
-alternativa. Se si specifica anche \const{LOCK\_NB} la funzione non si
-bloccherà qualora il lock non possa essere acquisito, ma ritornerà subito con
-un errore di \errcode{EWOULDBLOCK}. Per rilasciare un lock si dovrà invece
-usare \const{LOCK\_UN}.
+\footnotetext{per una maggiore efficienza \func{splice} usa quando possibile i
+ meccanismi della memoria virtuale per eseguire i trasferimenti di dati (in
+ maniera analoga a \func{mmap}), qualora le pagine non possano essere
+ spostate dalla pipe o il buffer non corrisponda a pagine intere esse saranno
+ comunque copiate.}
+
+\footnotetext{questa opzione consente di utilizzare delle opzioni di gestione
+ dei socket che permettono di ottimizzare le trasmissioni via rete, si veda
+ la descrizione di \const{TCP\_CORK} in sez.~\ref{sec:sock_tcp_udp_options} e
+ quella di \const{MSG\_MORE} in sez.~\ref{sec:net_sendmsg}.}
-La semantica del file locking di BSD è diversa da quella del file locking
-POSIX, in particolare per quanto riguarda il comportamento dei lock nei
-confronti delle due funzioni \func{dup} e \func{fork}. Per capire queste
-differenze occorre descrivere con maggiore dettaglio come viene realizzato il
-file locking nel kernel in entrambe le interfacce.
+\footnotetext{questo significa che la cache delle pagine e i dati su disco
+ potranno differire, e che l'applicazione non potrà modificare quest'area di
+ memoria.}
-In fig.~\ref{fig:file_flock_struct} si è riportato uno schema essenziale
-dell'implementazione del file locking in stile BSD in Linux; il punto
-fondamentale da capire è che un lock, qualunque sia l'interfaccia che si usa,
-anche se richiesto attraverso un file descriptor, agisce sempre su un file;
-perciò le informazioni relative agli eventuali \textit{file lock} sono
-mantenute a livello di inode\index{inode},\footnote{in particolare, come
- accennato in fig.~\ref{fig:file_flock_struct}, i \textit{file lock} sono
- mantenuti in una \itindex{linked~list} \textit{linked list} di strutture
- \struct{file\_lock}. La lista è referenziata dall'indirizzo di partenza
- mantenuto dal campo \var{i\_flock} della struttura \struct{inode} (per le
- definizioni esatte si faccia riferimento al file \file{fs.h} nei sorgenti
- del kernel). Un bit del campo \var{fl\_flags} di specifica se si tratta di
- un lock in semantica BSD (\const{FL\_FLOCK}) o POSIX (\const{FL\_POSIX}).}
-dato che questo è l'unico riferimento in comune che possono avere due processi
-diversi che aprono lo stesso file.
+Per capire meglio il funzionamento di \func{splice} vediamo un esempio con un
+semplice programma che usa questa funzione per effettuare la copia di un file
+su un altro senza utilizzare buffer in user space. Il programma si chiama
+\texttt{splicecp.c} ed il codice completo è disponibile coi sorgenti allegati
+alla guida, il corpo principale del programma, che non contiene la sezione di
+gestione delle opzioni e le funzioni di ausilio è riportato in
+fig.~\ref{fig:splice_example}.
+
+Lo scopo del programma è quello di eseguire la copia dei con \func{splice},
+questo significa che si dovrà usare la funzione due volte, prima per leggere i
+dati e poi per scriverli, appoggiandosi ad un buffer in kernel space (vale a
+dire ad una \textit{pipe}); lo schema del flusso dei dati è illustrato in
+fig.~\ref{fig:splicecp_data_flux}.
\begin{figure}[htb]
\centering
- \includegraphics[width=14cm]{img/file_flock}
- \caption{Schema dell'architettura del file locking, nel caso particolare
- del suo utilizzo da parte dalla funzione \func{flock}.}
- \label{fig:file_flock_struct}
+ \includegraphics[height=6cm]{img/splice_copy}
+ \caption{Struttura del flusso di dati usato dal programma \texttt{splicecp}.}
+ \label{fig:splicecp_data_flux}
\end{figure}
-La richiesta di un file lock prevede una scansione della lista per determinare
-se l'acquisizione è possibile, ed in caso positivo l'aggiunta di un nuovo
-elemento.\footnote{cioè una nuova struttura \struct{file\_lock}.} Nel caso
-dei lock creati con \func{flock} la semantica della funzione prevede che sia
-\func{dup} che \func{fork} non creino ulteriori istanze di un file lock quanto
-piuttosto degli ulteriori riferimenti allo stesso. Questo viene realizzato dal
-kernel secondo lo schema di fig.~\ref{fig:file_flock_struct}, associando ad
-ogni nuovo \textit{file lock} un puntatore\footnote{il puntatore è mantenuto
- nel campo \var{fl\_file} di \struct{file\_lock}, e viene utilizzato solo per
- i lock creati con la semantica BSD.} alla voce nella \itindex{file~table}
-\textit{file table} da cui si è richiesto il lock, che così ne identifica il
-titolare.
-
-Questa struttura prevede che, quando si richiede la rimozione di un file lock,
-il kernel acconsenta solo se la richiesta proviene da un file descriptor che
-fa riferimento ad una voce nella \itindex{file~table} \textit{file table}
-corrispondente a quella registrata nel lock. Allora se ricordiamo quanto
-visto in sez.~\ref{sec:file_dup} e sez.~\ref{sec:file_sharing}, e cioè che i
-file descriptor duplicati e quelli ereditati in un processo figlio puntano
-sempre alla stessa voce nella \itindex{file~table} \textit{file table}, si può
-capire immediatamente quali sono le conseguenze nei confronti delle funzioni
-\func{dup} e \func{fork}.
-
-Sarà così possibile rimuovere un file lock attraverso uno qualunque dei file
-descriptor che fanno riferimento alla stessa voce nella \itindex{file~table}
-\textit{file table}, anche se questo è diverso da quello con cui lo si è
-creato,\footnote{attenzione, questo non vale se il file descriptor fa
- riferimento allo stesso file, ma attraverso una voce diversa della
- \itindex{file~table} \textit{file table}, come accade tutte le volte che si
- apre più volte lo stesso file.} o se si esegue la rimozione in un processo
-figlio; inoltre una volta tolto un file lock, la rimozione avrà effetto su
-tutti i file descriptor che condividono la stessa voce nella
-\itindex{file~table} \textit{file table}, e quindi, nel caso di file
-descriptor ereditati attraverso una \func{fork}, anche su processi diversi.
-
-Infine, per evitare che la terminazione imprevista di un processo lasci attivi
-dei file lock, quando un file viene chiuso il kernel provveda anche a
-rimuovere tutti i lock ad esso associati. Anche in questo caso occorre tenere
-presente cosa succede quando si hanno file descriptor duplicati; in tal caso
-infatti il file non verrà effettivamente chiuso (ed il lock rimosso) fintanto
-che non viene rilasciata la relativa voce nella \itindex{file~table}
-\textit{file table}; e questo avverrà solo quando tutti i file descriptor che
-fanno riferimento alla stessa voce sono stati chiusi. Quindi, nel caso ci
-siano duplicati o processi figli che mantengono ancora aperto un file
-descriptor, il lock non viene rilasciato.
-
-Si tenga presente infine che \func{flock} non è in grado di funzionare per i
-file mantenuti su NFS, in questo caso, se si ha la necessità di eseguire il
-\textit{file locking}, occorre usare l'interfaccia basata su \func{fcntl} che
-può funzionare anche attraverso NFS, a condizione che sia il client che il
-server supportino questa funzionalità.
-
-
-\subsection{Il file locking POSIX}
-\label{sec:file_posix_lock}
-
-La seconda interfaccia per l'\textit{advisory locking} disponibile in Linux è
-quella standardizzata da POSIX, basata sulla funzione \func{fcntl}. Abbiamo
-già trattato questa funzione nelle sue molteplici possibilità di utilizzo in
-sez.~\ref{sec:file_fcntl}. Quando la si impiega per il \textit{file locking}
-essa viene usata solo secondo il prototipo:
-\begin{prototype}{fcntl.h}{int fcntl(int fd, int cmd, struct flock *lock)}
-
- Applica o rimuove un \textit{file lock} sul file \param{fd}.
-
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
- errore, nel qual caso \var{errno} assumerà uno dei valori:
- \begin{errlist}
- \item[\errcode{EACCES}] l'operazione è proibita per la presenza di
- \textit{file lock} da parte di altri processi.
- \item[\errcode{ENOLCK}] il sistema non ha le risorse per il locking: ci
- sono troppi segmenti di lock aperti, si è esaurita la tabella dei lock,
- o il protocollo per il locking remoto è fallito.
- \item[\errcode{EDEADLK}] si è richiesto un lock su una regione bloccata da
- un altro processo che è a sua volta in attesa dello sblocco di un lock
- mantenuto dal processo corrente; si avrebbe pertanto un
- \itindex{deadlock} \textit{deadlock}. Non è garantito che il sistema
- riconosca sempre questa situazione.
- \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima
- di poter acquisire un lock.
- \end{errlist}
- ed inoltre \errval{EBADF}, \errval{EFAULT}.
- }
-\end{prototype}
-
-Al contrario di quanto avviene con l'interfaccia basata su \func{flock} con
-\func{fcntl} è possibile bloccare anche delle singole sezioni di un file, fino
-al singolo byte. Inoltre la funzione permette di ottenere alcune informazioni
-relative agli eventuali lock preesistenti. Per poter fare tutto questo la
-funzione utilizza come terzo argomento una apposita struttura \struct{flock}
-(la cui definizione è riportata in fig.~\ref{fig:struct_flock}) nella quale
-inserire tutti i dati relativi ad un determinato lock. Si tenga presente poi
-che un lock fa sempre riferimento ad una regione, per cui si potrà avere un
-conflitto anche se c'è soltanto una sovrapposizione parziale con un'altra
-regione bloccata.
+Una volta trattate le opzioni il programma verifica che restino
+(\texttt{\small 13--16}) i due argomenti che indicano il file sorgente ed il
+file destinazione. Il passo successivo è aprire il file sorgente
+(\texttt{\small 18--22}), quello di destinazione (\texttt{\small 23--27}) ed
+infine (\texttt{\small 28--31}) la \textit{pipe} che verrà usata come buffer.
-\begin{figure}[!bht]
+\begin{figure}[!phtb]
\footnotesize \centering
\begin{minipage}[c]{15cm}
- \includestruct{listati/flock.h}
- \end{minipage}
- \normalsize
- \caption{La struttura \structd{flock}, usata da \func{fcntl} per il file
- locking.}
- \label{fig:struct_flock}
+ \includecodesample{listati/splicecp.c}
+ \end{minipage}
+ \normalsize
+ \caption{Esempio di codice che usa \func{splice} per effettuare la copia di
+ un file.}
+ \label{fig:splice_example}
\end{figure}
+Il ciclo principale (\texttt{\small 33--58}) inizia con la lettura dal file
+sorgente tramite la prima \func{splice} (\texttt{\small 34--35}), in questo
+caso si è usato come primo argomento il file descriptor del file sorgente e
+come terzo quello del capo in scrittura della \textit{pipe} (il funzionamento
+delle \textit{pipe} e l'uso della coppia di file descriptor ad esse associati
+è trattato in dettaglio in sez.~\ref{sec:ipc_unix}; non ne parleremo qui dato
+che nell'ottica dell'uso di \func{splice} questa operazione corrisponde
+semplicemente al trasferimento dei dati dal file al buffer).
-I primi tre campi della struttura, \var{l\_whence}, \var{l\_start} e
-\var{l\_len}, servono a specificare la sezione del file a cui fa riferimento
-il lock: \var{l\_start} specifica il byte di partenza, \var{l\_len} la
-lunghezza della sezione e infine \var{l\_whence} imposta il riferimento da cui
-contare \var{l\_start}. Il valore di \var{l\_whence} segue la stessa semantica
-dell'omonimo argomento di \func{lseek}, coi tre possibili valori
-\const{SEEK\_SET}, \const{SEEK\_CUR} e \const{SEEK\_END}, (si vedano le
-relative descrizioni in sez.~\ref{sec:file_lseek}).
+La lettura viene eseguita in blocchi pari alla dimensione specificata
+dall'opzione \texttt{-s} (il default è 4096); essendo in questo caso
+\func{splice} equivalente ad una \func{read} sul file, se ne controlla il
+valore di uscita in \var{nread} che indica quanti byte sono stati letti, se
+detto valore è nullo (\texttt{\small 36}) questo significa che si è giunti
+alla fine del file sorgente e pertanto l'operazione di copia è conclusa e si
+può uscire dal ciclo arrivando alla conclusione del programma (\texttt{\small
+ 59}). In caso di valore negativo (\texttt{\small 37--44}) c'è stato un
+errore ed allora si ripete la lettura (\texttt{\small 36}) se questo è dovuto
+ad una interruzione, o altrimenti si esce con un messaggio di errore
+(\texttt{\small 41--43}).
-Si tenga presente che un lock può essere richiesto anche per una regione al di
-là della corrente fine del file, così che una eventuale estensione dello
-stesso resti coperta dal blocco. Inoltre se si specifica un valore nullo per
-\var{l\_len} il blocco si considera esteso fino alla dimensione massima del
-file; in questo modo è possibile bloccare una qualunque regione a partire da
-un certo punto fino alla fine del file, coprendo automaticamente quanto
-eventualmente aggiunto in coda allo stesso.
+Una volta completata con successo la lettura si avvia il ciclo di scrittura
+(\texttt{\small 45--57}); questo inizia (\texttt{\small 46--47}) con la
+seconda \func{splice} che cerca di scrivere gli \var{nread} byte letti, si
+noti come in questo caso il primo argomento faccia di nuovo riferimento alla
+\textit{pipe} (in questo caso si usa il capo in lettura, per i dettagli si
+veda al solito sez.~\ref{sec:ipc_unix}) mentre il terzo sia il file descriptor
+del file di destinazione.
-\begin{table}[htb]
- \centering
- \footnotesize
- \begin{tabular}[c]{|l|l|}
- \hline
- \textbf{Valore} & \textbf{Significato} \\
- \hline
- \hline
- \const{F\_RDLCK} & Richiede un blocco condiviso (\textit{read lock}).\\
- \const{F\_WRLCK} & Richiede un blocco esclusivo (\textit{write lock}).\\
- \const{F\_UNLCK} & Richiede l'eliminazione di un file lock.\\
- \hline
- \end{tabular}
- \caption{Valori possibili per il campo \var{l\_type} di \struct{flock}.}
- \label{tab:file_flock_type}
-\end{table}
+Di nuovo si controlla il numero di byte effettivamente scritti restituito in
+\var{nwrite} e in caso di errore al solito si ripete la scrittura se questo è
+dovuto a una interruzione o si esce con un messaggio negli altri casi
+(\texttt{\small 48--55}). Infine si chiude il ciclo di scrittura sottraendo
+(\texttt{\small 57}) il numero di byte scritti a quelli di cui è richiesta la
+scrittura,\footnote{in questa parte del ciclo \var{nread}, il cui valore
+ iniziale è dato dai byte letti dalla precedente chiamata a \func{splice},
+ viene ad assumere il significato di byte da scrivere.} così che il ciclo di
+scrittura venga ripetuto fintanto che il valore risultante sia maggiore di
+zero, indice che la chiamata a \func{splice} non ha esaurito tutti i dati
+presenti sul buffer.
-Il tipo di file lock richiesto viene specificato dal campo \var{l\_type}, esso
-può assumere i tre valori definiti dalle costanti riportate in
-tab.~\ref{tab:file_flock_type}, che permettono di richiedere rispettivamente
-uno \textit{shared lock}, un \textit{esclusive lock}, e la rimozione di un
-lock precedentemente acquisito. Infine il campo \var{l\_pid} viene usato solo
-in caso di lettura, quando si chiama \func{fcntl} con \const{F\_GETLK}, e
-riporta il \acr{pid} del processo che detiene il lock.
+Si noti come il programma sia concettualmente identico a quello che si sarebbe
+scritto usando \func{read} al posto della prima \func{splice} e \func{write}
+al posto della seconda, utilizzando un buffer in user space per eseguire la
+copia dei dati, solo che in questo caso non è stato necessario allocare nessun
+buffer e non si è trasferito nessun dato in user space.
-Oltre a quanto richiesto tramite i campi di \struct{flock}, l'operazione
-effettivamente svolta dalla funzione è stabilita dal valore dall'argomento
-\param{cmd} che, come già riportato in sez.~\ref{sec:file_fcntl}, specifica
-l'azione da compiere; i valori relativi al file locking sono tre:
-\begin{basedescript}{\desclabelwidth{2.0cm}}
-\item[\const{F\_GETLK}] verifica se il file lock specificato dalla struttura
- puntata da \param{lock} può essere acquisito: in caso negativo sovrascrive
- la struttura \param{flock} con i valori relativi al lock già esistente che
- ne blocca l'acquisizione, altrimenti si limita a impostarne il campo
- \var{l\_type} con il valore \const{F\_UNLCK}.
-\item[\const{F\_SETLK}] se il campo \var{l\_type} della struttura puntata da
- \param{lock} è \const{F\_RDLCK} o \const{F\_WRLCK} richiede il
- corrispondente file lock, se è \const{F\_UNLCK} lo rilascia. Nel caso la
- richiesta non possa essere soddisfatta a causa di un lock preesistente la
- funzione ritorna immediatamente con un errore di \errcode{EACCES} o di
- \errcode{EAGAIN}.
-\item[\const{F\_SETLKW}] è identica a \const{F\_SETLK}, ma se la richiesta di
- non può essere soddisfatta per la presenza di un altro lock, mette il
- processo in stato di attesa fintanto che il lock precedente non viene
- rilasciato. Se l'attesa viene interrotta da un segnale la funzione ritorna
- con un errore di \errcode{EINTR}.
-\end{basedescript}
+Si noti anche come si sia usata la combinazione \texttt{SPLICE\_F\_MOVE |
+ SPLICE\_F\_MORE } per l'argomento \param{flags} di \func{splice}, infatti
+anche se un valore nullo avrebbe dato gli stessi risultati, l'uso di questi
+flag, che si ricordi servono solo a dare suggerimenti al kernel, permette in
+genere di migliorare le prestazioni.
-Si noti che per quanto detto il comando \const{F\_GETLK} non serve a rilevare
-una presenza generica di lock su un file, perché se ne esistono altri
-compatibili con quello richiesto, la funzione ritorna comunque impostando
-\var{l\_type} a \const{F\_UNLCK}. Inoltre a seconda del valore di
-\var{l\_type} si potrà controllare o l'esistenza di un qualunque tipo di lock
-(se è \const{F\_WRLCK}) o di write lock (se è \const{F\_RDLCK}). Si consideri
-poi che può esserci più di un lock che impedisce l'acquisizione di quello
-richiesto (basta che le regioni si sovrappongano), ma la funzione ne riporterà
-sempre soltanto uno, impostando \var{l\_whence} a \const{SEEK\_SET} ed i
-valori \var{l\_start} e \var{l\_len} per indicare quale è la regione bloccata.
+Come accennato con l'introduzione di \func{splice} sono state realizzate altre
+due system call, \func{vmsplice} e \func{tee}, che utilizzano la stessa
+infrastruttura e si basano sullo stesso concetto di manipolazione e
+trasferimento di dati attraverso un buffer in kernel space; benché queste non
+attengono strettamente ad operazioni di trasferimento dati fra file
+descriptor, le tratteremo qui.
-Infine si tenga presente che effettuare un controllo con il comando
-\const{F\_GETLK} e poi tentare l'acquisizione con \const{F\_SETLK} non è una
-operazione atomica (un altro processo potrebbe acquisire un lock fra le due
-chiamate) per cui si deve sempre verificare il codice di ritorno di
-\func{fcntl}\footnote{controllare il codice di ritorno delle funzioni invocate
- è comunque una buona norma di programmazione, che permette di evitare un
- sacco di errori difficili da tracciare proprio perché non vengono rilevati.}
-quando la si invoca con \const{F\_SETLK}, per controllare che il lock sia
-stato effettivamente acquisito.
+La prima funzione, \funcd{vmsplice}, è la più simile a \func{splice} e come
+indica il suo nome consente di trasferire i dati dalla memoria di un processo
+verso una \textit{pipe}, il suo prototipo è:
+\begin{functions}
+ \headdecl{fcntl.h}
+ \headdecl{sys/uio.h}
-\begin{figure}[htb]
- \centering \includegraphics[width=9cm]{img/file_lock_dead}
- \caption{Schema di una situazione di \itindex{deadlock} \textit{deadlock}.}
- \label{fig:file_flock_dead}
-\end{figure}
+ \funcdecl{long vmsplice(int fd, const struct iovec *iov, unsigned long
+ nr\_segs, unsigned int flags)}
+
+ Trasferisce dati dalla memoria di un processo verso una \textit{pipe}.
-Non operando a livello di interi file, il file locking POSIX introduce
-un'ulteriore complicazione; consideriamo la situazione illustrata in
-fig.~\ref{fig:file_flock_dead}, in cui il processo A blocca la regione 1 e il
-processo B la regione 2. Supponiamo che successivamente il processo A richieda
-un lock sulla regione 2 che non può essere acquisito per il preesistente lock
-del processo 2; il processo 1 si bloccherà fintanto che il processo 2 non
-rilasci il blocco. Ma cosa accade se il processo 2 nel frattempo tenta a sua
-volta di ottenere un lock sulla regione A? Questa è una tipica situazione che
-porta ad un \itindex{deadlock} \textit{deadlock}, dato che a quel punto anche
-il processo 2 si bloccherebbe, e niente potrebbe sbloccare l'altro processo.
-Per questo motivo il kernel si incarica di rilevare situazioni di questo tipo,
-ed impedirle restituendo un errore di \errcode{EDEADLK} alla funzione che
-cerca di acquisire un lock che porterebbe ad un \itindex{deadlock}
-\textit{deadlock}.
+ \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
+ successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
+ dei valori:
+ \begin{errlist}
+ \item[\errcode{EBADF}] o \param{fd} non è un file descriptor valido o non
+ fa riferimento ad una \textit{pipe}.
+ \item[\errcode{EINVAL}] si è usato un valore nullo per \param{nr\_segs}
+ oppure si è usato \const{SPLICE\_F\_GIFT} ma la memoria non è allineata.
+ \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione
+ richiesta.
+ \end{errlist}
+ }
+\end{functions}
+La \textit{pipe} dovrà essere specificata tramite il file descriptor
+corrispondente al suo capo aperto in scrittura (di nuovo si faccia riferimento
+a sez.~\ref{sec:ipc_unix}), mentre per indicare quali zone di memoria devono
+essere trasferita si deve utilizzare un vettore di strutture \struct{iovec}
+(vedi fig.~\ref{fig:file_iovec}), con le stesse con cui le si usano per l'I/O
+vettorizzato; le dimensioni del suddetto vettore devono essere passate
+nell'argomento \param{nr\_segs} che indica il numero di segmenti di memoria da
+trasferire. Sia per il vettore che per il valore massimo di \param{nr\_segs}
+valgono le stesse limitazioni illustrate in sez.~\ref{sec:file_multiple_io}.
-Per capire meglio il funzionamento del file locking in semantica POSIX (che
-differisce alquanto rispetto da quello di BSD, visto
-sez.~\ref{sec:file_flock}) esaminiamo più in dettaglio come viene gestito dal
-kernel. Lo schema delle strutture utilizzate è riportato in
-fig.~\ref{fig:file_posix_lock}; come si vede esso è molto simile all'analogo
-di fig.~\ref{fig:file_flock_struct}:\footnote{in questo caso nella figura si
- sono evidenziati solo i campi di \struct{file\_lock} significativi per la
- semantica POSIX, in particolare adesso ciascuna struttura contiene, oltre al
- \acr{pid} del processo in \var{fl\_pid}, la sezione di file che viene
- bloccata grazie ai campi \var{fl\_start} e \var{fl\_end}. La struttura è
- comunque la stessa, solo che in questo caso nel campo \var{fl\_flags} è
- impostato il bit \const{FL\_POSIX} ed il campo \var{fl\_file} non viene
- usato.} il lock è sempre associato \index{inode} all'inode, solo che in
-questo caso la titolarità non viene identificata con il riferimento ad una
-voce nella \itindex{file~table} \textit{file table}, ma con il valore del
-\acr{pid} del processo.
+In caso di successo la funzione ritorna il numero di byte trasferiti sulla
+pipe, in generale (se i dati una volta creati non devono essere riutilizzati)
+è opportuno utilizzare il flag \const{SPLICE\_F\_GIFT}; questo fa si che il
+kernel possa rimuovere le relative pagine dallo spazio degli indirizzi del
+processo, e scaricarle nella cache, così che queste possono essere utilizzate
+immediatamente senza necessità di eseguire una copia dei dati che contengono.
-\begin{figure}[!bht]
- \centering \includegraphics[width=13cm]{img/file_posix_lock}
- \caption{Schema dell'architettura del file locking, nel caso particolare
- del suo utilizzo secondo l'interfaccia standard POSIX.}
- \label{fig:file_posix_lock}
-\end{figure}
+La seconda funzione aggiunta insieme a \func{splice} è \func{tee}, che deve il
+suo nome all'omonimo comando in user space, perché in analogia con questo
+permette di duplicare i dati in ingresso su una \textit{pipe} su un'altra
+\textit{pipe}. In sostanza, sempre nell'ottica della manipolazione dei dati su
+dei buffer in kernel space, la funzione consente di eseguire una copia del
+contenuto del buffer stesso. Il prototipo di \funcd{tee} è il seguente:
+\begin{functions}
+ \headdecl{fcntl.h}
-Quando si richiede un lock il kernel effettua una scansione di tutti i lock
-presenti sul file\footnote{scandisce cioè la \itindex{linked~list}
- \textit{linked list} delle strutture \struct{file\_lock}, scartando
- automaticamente quelle per cui \var{fl\_flags} non è \const{FL\_POSIX}, così
- che le due interfacce restano ben separate.} per verificare se la regione
-richiesta non si sovrappone ad una già bloccata, in caso affermativo decide in
-base al tipo di lock, in caso negativo il nuovo lock viene comunque acquisito
-ed aggiunto alla lista.
+ \funcdecl{long tee(int fd\_in, int fd\_out, size\_t len, unsigned int
+ flags)}
+
+ Duplica \param{len} byte da una \textit{pipe} ad un'altra.
-Nel caso di rimozione invece questa viene effettuata controllando che il
-\acr{pid} del processo richiedente corrisponda a quello contenuto nel lock.
-Questa diversa modalità ha delle conseguenze precise riguardo il comportamento
-dei lock POSIX. La prima conseguenza è che un lock POSIX non viene mai
-ereditato attraverso una \func{fork}, dato che il processo figlio avrà un
-\acr{pid} diverso, mentre passa indenne attraverso una \func{exec} in quanto
-il \acr{pid} resta lo stesso. Questo comporta che, al contrario di quanto
-avveniva con la semantica BSD, quando processo termina tutti i file lock da
-esso detenuti vengono immediatamente rilasciati.
+ \bodydesc{La funzione restituisce il numero di byte copiati in caso di
+ successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
+ dei valori:
+ \begin{errlist}
+ \item[\errcode{EINVAL}] o uno fra \param{fd\_in} e \param{fd\_out} non fa
+ riferimento ad una \textit{pipe} o entrambi fanno riferimento alla
+ stessa \textit{pipe}.
+ \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione
+ richiesta.
+ \end{errlist}
+ }
+\end{functions}
-La seconda conseguenza è che qualunque file descriptor che faccia riferimento
-allo stesso file (che sia stato ottenuto con una \func{dup} o con una
-\func{open} in questo caso non fa differenza) può essere usato per rimuovere
-un lock, dato che quello che conta è solo il \acr{pid} del processo. Da questo
-deriva una ulteriore sottile differenza di comportamento: dato che alla
-chiusura di un file i lock ad esso associati vengono rimossi, nella semantica
-POSIX basterà chiudere un file descriptor qualunque per cancellare tutti i
-lock relativi al file cui esso faceva riferimento, anche se questi fossero
-stati creati usando altri file descriptor che restano aperti.
-
-Dato che il controllo sull'accesso ai lock viene eseguito sulla base del
-\acr{pid} del processo, possiamo anche prendere in considerazione un altro
-degli aspetti meno chiari di questa interfaccia e cioè cosa succede quando si
-richiedono dei lock su regioni che si sovrappongono fra loro all'interno
-stesso processo. Siccome il controllo, come nel caso della rimozione, si basa
-solo sul \acr{pid} del processo che chiama la funzione, queste richieste
-avranno sempre successo.
+La funzione copia \param{len} byte del contenuto di una \textit{pipe} su di
+un'altra; \param{fd\_in} deve essere il capo in lettura della \textit{pipe}
+sorgente e \param{fd\_out} il capo in scrittura della \textit{pipe}
+destinazione; a differenza di quanto avviene con \func{read} i dati letti con
+\func{tee} da \func{fd\_in} non vengono \textsl{consumati} e restano
+disponibili sulla \textit{pipe} per una successiva lettura (di nuovo per il
+comportamento delle \textit{pipe} si veda sez.~\ref{sec:ipc_unix}).
-Nel caso della semantica BSD, essendo i lock relativi a tutto un file e non
-accumulandosi,\footnote{questa ultima caratteristica è vera in generale, se
- cioè si richiede più volte lo stesso file lock, o più lock sulla stessa
- sezione di file, le richieste non si cumulano e basta una sola richiesta di
- rilascio per cancellare il lock.} la cosa non ha alcun effetto; la funzione
-ritorna con successo, senza che il kernel debba modificare la lista dei lock.
-In questo caso invece si possono avere una serie di situazioni diverse: ad
-esempio è possibile rimuovere con una sola chiamata più lock distinti
-(indicando in una regione che si sovrapponga completamente a quelle di questi
-ultimi), o rimuovere solo una parte di un lock preesistente (indicando una
-regione contenuta in quella di un altro lock), creando un buco, o coprire con
-un nuovo lock altri lock già ottenuti, e così via, a secondo di come si
-sovrappongono le regioni richieste e del tipo di operazione richiesta. Il
-comportamento seguito in questo caso che la funzione ha successo ed esegue
-l'operazione richiesta sulla regione indicata; è compito del kernel
-preoccuparsi di accorpare o dividere le voci nella lista dei lock per far si
-che le regioni bloccate da essa risultanti siano coerenti con quanto
-necessario a soddisfare l'operazione richiesta.
+La funzione restituisce il numero di byte copiati da una \textit{pipe}
+all'altra (o $-1$ in caso di errore), un valore nullo indica che non ci sono
+byte disponibili da copiare e che il capo in scrittura della pipe è stato
+chiuso.\footnote{si tenga presente però che questo non avviene se si è
+ impostato il flag \const{SPLICE\_F\_NONBLOCK}, in tal caso infatti si
+ avrebbe un errore di \errcode{EAGAIN}.} Un esempio di realizzazione del
+comando \texttt{tee} usando questa funzione, ripreso da quello fornito nella
+pagina di manuale e dall'esempio allegato al patch originale, è riportato in
+fig.~\ref{fig:tee_example}. Il programma consente di copiare il contenuto
+dello standard input sullo standard output e su un file specificato come
+argomento, il codice completo si trova nel file \texttt{tee.c} dei sorgenti
+allegati alla guida.
-\begin{figure}[!htb]
+\begin{figure}[!htbp]
\footnotesize \centering
\begin{minipage}[c]{15cm}
- \includecodesample{listati/Flock.c}
- \end{minipage}
- \normalsize
- \caption{Sezione principale del codice del programma \file{Flock.c}.}
- \label{fig:file_flock_code}
+ \includecodesample{listati/tee.c}
+ \end{minipage}
+ \normalsize
+ \caption{Esempio di codice che usa \func{tee} per copiare i dati dello
+ standard input sullo standard output e su un file.}
+ \label{fig:tee_example}
\end{figure}
-Per fare qualche esempio sul file locking si è scritto un programma che
-permette di bloccare una sezione di un file usando la semantica POSIX, o un
-intero file usando la semantica BSD; in fig.~\ref{fig:file_flock_code} è
-riportata il corpo principale del codice del programma, (il testo completo è
-allegato nella directory dei sorgenti).
-
-La sezione relativa alla gestione delle opzioni al solito si è omessa, come la
-funzione che stampa le istruzioni per l'uso del programma, essa si cura di
-impostare le variabili \var{type}, \var{start} e \var{len}; queste ultime due
-vengono inizializzate al valore numerico fornito rispettivamente tramite gli
-switch \code{-s} e \cmd{-l}, mentre il valore della prima viene impostato con
-le opzioni \cmd{-w} e \cmd{-r} si richiede rispettivamente o un write lock o
-read lock (i due valori sono esclusivi, la variabile assumerà quello che si è
-specificato per ultimo). Oltre a queste tre vengono pure impostate la
-variabile \var{bsd}, che abilita la semantica omonima quando si invoca
-l'opzione \cmd{-f} (il valore preimpostato è nullo, ad indicare la semantica
-POSIX), e la variabile \var{cmd} che specifica la modalità di richiesta del
-lock (bloccante o meno), a seconda dell'opzione \cmd{-b}.
-
-Il programma inizia col controllare (\texttt{\small 11--14}) che venga passato
-un argomento (il file da bloccare), che sia stato scelto (\texttt{\small
- 15--18}) il tipo di lock, dopo di che apre (\texttt{\small 19}) il file,
-uscendo (\texttt{\small 20--23}) in caso di errore. A questo punto il
-comportamento dipende dalla semantica scelta; nel caso sia BSD occorre
-reimpostare il valore di \var{cmd} per l'uso con \func{flock}; infatti il
-valore preimpostato fa riferimento alla semantica POSIX e vale rispettivamente
-\const{F\_SETLKW} o \const{F\_SETLK} a seconda che si sia impostato o meno la
-modalità bloccante.
-
-Nel caso si sia scelta la semantica BSD (\texttt{\small 25--34}) prima si
-controlla (\texttt{\small 27--31}) il valore di \var{cmd} per determinare se
-si vuole effettuare una chiamata bloccante o meno, reimpostandone il valore
-opportunamente, dopo di che a seconda del tipo di lock al valore viene
-aggiunta la relativa opzione (con un OR aritmetico, dato che \func{flock}
-vuole un argomento \param{operation} in forma di maschera binaria. Nel caso
-invece che si sia scelta la semantica POSIX le operazioni sono molto più
-immediate, si prepara (\texttt{\small 36--40}) la struttura per il lock, e lo
-esegue (\texttt{\small 41}).
-
-In entrambi i casi dopo aver richiesto il lock viene controllato il risultato
-uscendo (\texttt{\small 44--46}) in caso di errore, o stampando un messaggio
-(\texttt{\small 47--49}) in caso di successo. Infine il programma si pone in
-attesa (\texttt{\small 50}) finché un segnale (ad esempio un \cmd{C-c} dato da
-tastiera) non lo interrompa; in questo caso il programma termina, e tutti i
-lock vengono rilasciati.
-
-Con il programma possiamo fare varie verifiche sul funzionamento del file
-locking; cominciamo con l'eseguire un read lock su un file, ad esempio usando
-all'interno di un terminale il seguente comando:
-
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -r Flock.c
-Lock acquired
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-il programma segnalerà di aver acquisito un lock e si bloccherà; in questo
-caso si è usato il file locking POSIX e non avendo specificato niente riguardo
-alla sezione che si vuole bloccare sono stati usati i valori preimpostati che
-bloccano tutto il file. A questo punto se proviamo ad eseguire lo stesso
-comando in un altro terminale, e avremo lo stesso risultato. Se invece
-proviamo ad eseguire un write lock avremo:
-
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -w Flock.c
-Failed lock: Resource temporarily unavailable
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-come ci aspettiamo il programma terminerà segnalando l'indisponibilità del
-lock, dato che il file è bloccato dal precedente read lock. Si noti che il
-risultato è lo stesso anche se si richiede il blocco su una sola parte del
-file con il comando:
+La prima parte del programma (\texttt{\small 10--35}) si cura semplicemente di
+controllare (\texttt{\small 11--14}) che sia stato fornito almeno un argomento
+(il nome del file su cui scrivere), di aprirlo ({\small 15--19}) e che sia lo
+standard input (\texttt{\small 20--27}) che lo standard output (\texttt{\small
+ 28--35}) corrispondano ad una \textit{pipe}.
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -w -s0 -l10 Flock.c
-Failed lock: Resource temporarily unavailable
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-se invece blocchiamo una regione con:
+Il ciclo principale (\texttt{\small 37--58}) inizia con la chiamata a
+\func{tee} che duplica il contenuto dello standard input sullo standard output
+(\texttt{\small 39}), questa parte è del tutto analoga ad una lettura ed
+infatti come nell'esempio di fig.~\ref{fig:splice_example} si controlla il
+valore di ritorno della funzione in \var{len}; se questo è nullo significa che
+non ci sono più dati da leggere e si chiude il ciclo (\texttt{\small 40}), se
+è negativo c'è stato un errore, ed allora si ripete la chiamata se questo è
+dovuto ad una interruzione (\texttt{\small 42--44}) o si stampa un messaggio
+di errore e si esce negli altri casi (\texttt{\small 44--47}).
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -r -s0 -l10 Flock.c
-Lock acquired
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-una volta che riproviamo ad acquisire il write lock i risultati dipenderanno
-dalla regione richiesta; ad esempio nel caso in cui le due regioni si
-sovrappongono avremo che:
+Una volta completata la copia dei dati sullo standard output si possono
+estrarre dalla standard input e scrivere sul file, di nuovo su usa un ciclo di
+scrittura (\texttt{\small 50--58}) in cui si ripete una chiamata a
+\func{splice} (\texttt{\small 51}) fintanto che non si sono scritti tutti i
+\var{len} byte copiati in precedenza con \func{tee} (il funzionamento è
+identico all'analogo ciclo di scrittura del precedente esempio di
+fig.~\ref{fig:splice_example}).
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -w -s5 -l15 Flock.c
-Failed lock: Resource temporarily unavailable
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-ed il lock viene rifiutato, ma se invece si richiede una regione distinta
-avremo che:
+Infine una nota finale riguardo \func{splice}, \func{vmsplice} e \func{tee}:
+occorre sottolineare che benché finora si sia parlato di trasferimenti o copie
+di dati in realtà nella implementazione di queste system call non è affatto
+detto che i dati vengono effettivamente spostati o copiati, il kernel infatti
+realizza le \textit{pipe} come un insieme di puntatori\footnote{per essere
+ precisi si tratta di un semplice buffer circolare, un buon articolo sul tema
+ si trova su \href{http://lwn.net/Articles/118750/}
+ {\textsf{http://lwn.net/Articles/118750/}}.} alle pagine di memoria interna
+che contengono i dati, per questo una volta che i dati sono presenti nella
+memoria del kernel tutto quello che viene fatto è creare i suddetti puntatori
+ed aumentare il numero di referenze; questo significa che anche con \func{tee}
+non viene mai copiato nessun byte, vengono semplicemente copiati i puntatori.
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -w -s11 -l15 Flock.c
-Lock acquired
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-ed il lock viene acquisito. Se a questo punto si prova ad eseguire un read
-lock che comprende la nuova regione bloccata in scrittura:
+% TODO?? dal 2.6.25 splice ha ottenuto il supporto per la ricezione su rete
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -r -s10 -l20 Flock.c
-Failed lock: Resource temporarily unavailable
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-come ci aspettiamo questo non sarà consentito.
-Il programma di norma esegue il tentativo di acquisire il lock in modalità non
-bloccante, se però usiamo l'opzione \cmd{-b} possiamo impostare la modalità
-bloccante, riproviamo allora a ripetere le prove precedenti con questa
-opzione:
+\subsection{Gestione avanzata dell'accesso ai dati dei file}
+\label{sec:file_fadvise}
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -r -b -s0 -l10 Flock.c Lock acquired
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-il primo comando acquisisce subito un read lock, e quindi non cambia nulla, ma
-se proviamo adesso a richiedere un write lock che non potrà essere acquisito
-otterremo:
+Nell'uso generico dell'interfaccia per l'accesso al contenuto dei file le
+operazioni di lettura e scrittura non necessitano di nessun intervento di
+supervisione da parte dei programmi, si eseguirà una \func{read} o una
+\func{write}, i dati verranno passati al kernel che provvederà ad effettuare
+tutte le operazioni (e a gestire il \textit{caching} dei dati) per portarle a
+termine in quello che ritiene essere il modo più efficiente.
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -w -s0 -l10 Flock.c
-\end{verbatim}%$
-\end{minipage}\vspace{1mm}
-\par\noindent
-il programma cioè si bloccherà nella chiamata a \func{fcntl}; se a questo
-punto rilasciamo il precedente lock (terminando il primo comando un
-\texttt{C-c} sul terminale) potremo verificare che sull'altro terminale il
-lock viene acquisito, con la comparsa di una nuova riga:
+Il problema è che il concetto di migliore efficienza impiegato dal kernel è
+relativo all'uso generico, mentre esistono molti casi in cui ci sono esigenze
+specifiche dei singoli programmi, che avendo una conoscenza diretta di come
+verranno usati i file, possono necessitare di effettuare delle ottimizzazioni
+specifiche, relative alle proprie modalità di I/O sugli stessi. Tratteremo in
+questa sezione una serie funzioni che consentono ai programmi di ottimizzare
+il loro accesso ai dati dei file e controllare la gestione del relativo
+\textit{caching}.
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[piccardi@gont sources]$ ./flock -w -s0 -l10 Flock.c
-Lock acquired
-\end{verbatim}%$
-\end{minipage}\vspace{3mm}
-\par\noindent
+Una prima funzione che può essere utilizzata per modificare la gestione
+ordinaria dell'I/O su un file è \funcd{readahead},\footnote{questa è una
+ funzione specifica di Linux, introdotta con il kernel 2.4.13, e non deve
+ essere usata se si vogliono scrivere programmi portabili.} che consente di
+richiedere una lettura anticipata del contenuto dello stesso in cache, così
+che le seguenti operazioni di lettura non debbano subire il ritardo dovuto
+all'accesso al disco; il suo prototipo è:
+\begin{functions}
+ \headdecl{fcntl.h}
-Un'altra cosa che si può controllare con il nostro programma è l'interazione
-fra i due tipi di lock; se ripartiamo dal primo comando con cui si è ottenuto
-un lock in lettura sull'intero file, possiamo verificare cosa succede quando
-si cerca di ottenere un lock in scrittura con la semantica BSD:
+ \funcdecl{ssize\_t readahead(int fd, off64\_t *offset, size\_t count)}
+
+ Esegue una lettura preventiva del contenuto di un file in cache.
-\vspace{1mm}
-\begin{minipage}[c]{12cm}
-\begin{verbatim}
-[root@gont sources]# ./flock -f -w Flock.c
-Lock acquired
-\end{verbatim}
-\end{minipage}\vspace{1mm}
-\par\noindent
-che ci mostra come i due tipi di lock siano assolutamente indipendenti; per
-questo motivo occorre sempre tenere presente quale fra le due semantiche
-disponibili stanno usando i programmi con cui si interagisce, dato che i lock
-applicati con l'altra non avrebbero nessun effetto.
+ \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di
+ errore, nel qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor
+ valido o non è aperto in lettura.
+ \item[\errcode{EINVAL}] l'argomento \param{fd} si riferisce ad un tipo di
+ file che non supporta l'operazione (come una pipe o un socket).
+ \end{errlist}
+ }
+\end{functions}
+La funzione richiede che venga letto in anticipo il contenuto del file
+\param{fd} a partire dalla posizione \param{offset} e per un ammontare di
+\param{count} byte, in modo da portarlo in cache. La funzione usa la
+\index{memoria~virtuale} memoria virtuale ed il meccanismo della
+\index{paginazione} paginazione per cui la lettura viene eseguita in blocchi
+corrispondenti alle dimensioni delle pagine di memoria, ed i valori di
+\param{offset} e \param{count} arrotondati di conseguenza.
+La funzione estende quello che è un comportamento normale del
+kernel\footnote{per ottimizzare gli accessi al disco il kernel quando si legge
+ un file, aspettandosi che l'accesso prosegua, esegue sempre una lettura
+ anticipata di una certa quantità di dati; questo meccanismo viene chiamato
+ \textit{readahead}, da cui deriva il nome della funzione.} effettuando la
+lettura in cache della sezione richiesta e bloccandosi fintanto che questa non
+viene completata. La posizione corrente sul file non viene modificata ed
+indipendentemente da quanto indicato con \param{count} la lettura dei dati si
+interrompe una volta raggiunta la fine del file.
-\subsection{La funzione \func{lockf}}
-\label{sec:file_lockf}
+Si può utilizzare questa funzione per velocizzare le operazioni di lettura
+all'interno del programma tutte le volte che si conosce in anticipo quanti
+dati saranno necessari in seguito. Si potrà così concentrare in un unico
+momento (ad esempio in fase di inizializzazione) la lettura, così da ottenere
+una migliore risposta nelle operazioni successive.
-Abbiamo visto come l'interfaccia POSIX per il file locking sia molto più
-potente e flessibile di quella di BSD, questo comporta anche una maggiore
-complessità per via delle varie opzioni da passare a \func{fcntl}. Per questo
-motivo è disponibile anche una interfaccia semplificata (ripresa da System V)
-che utilizza la funzione \funcd{lockf}, il cui prototipo è:
-\begin{prototype}{sys/file.h}{int lockf(int fd, int cmd, off\_t len)}
-
- Applica, controlla o rimuove un \textit{file lock} sul file \param{fd}.
+Il concetto di \func{readahead} viene generalizzato nello standard
+POSIX.1-2001 dalla funzione \funcd{posix\_fadvise},\footnote{anche se
+ l'argomento \param{len} è stato modificato da \ctyp{size\_t} a \ctyp{off\_t}
+ nella revisione POSIX.1-2003 TC5.} che consente di ``\textsl{avvisare}'' il
+kernel sulle modalità con cui si intende accedere nel futuro ad una certa
+porzione di un file,\footnote{la funzione però è stata introdotta su Linux
+ solo a partire dal kernel 2.5.60.} così che esso possa provvedere le
+opportune ottimizzazioni; il suo prototipo, che può è disponibile solo se si
+definisce la macro \macro{\_XOPEN\_SOURCE} ad almeno 600, è:
+\begin{functions}
+ \headdecl{fcntl.h}
+
+ \funcdecl{int posix\_fadvise(int fd, off\_t offset, off\_t len, int advice)}
- \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di
+ Dichiara al kernel le future modalità di accesso ad un file.
+
+ \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di
errore, nel qual caso \var{errno} assumerà uno dei valori:
\begin{errlist}
- \item[\errcode{EWOULDBLOCK}] non è possibile acquisire il lock, e si è
- selezionato \const{LOCK\_NB}, oppure l'operazione è proibita perché il
- file è mappato in memoria.
- \item[\errcode{ENOLCK}] il sistema non ha le risorse per il locking: ci
- sono troppi segmenti di lock aperti, si è esaurita la tabella dei lock.
+ \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor
+ valido.
+ \item[\errcode{EINVAL}] il valore di \param{advice} non è valido o
+ \param{fd} si riferisce ad un tipo di file che non supporta l'operazione
+ (come una pipe o un socket).
+ \item[\errcode{ESPIPE}] previsto dallo standard se \param{fd} è una pipe o
+ un socket (ma su Linux viene restituito \errcode{EINVAL}).
\end{errlist}
- ed inoltre \errval{EBADF}, \errval{EINVAL}.
}
-\end{prototype}
+\end{functions}
-Il comportamento della funzione dipende dal valore dell'argomento \param{cmd},
-che specifica quale azione eseguire; i valori possibili sono riportati in
-tab.~\ref{tab:file_lockf_type}.
+La funzione dichiara al kernel le modalità con cui intende accedere alla
+regione del file indicato da \param{fd} che inizia alla posizione
+\param{offset} e si estende per \param{len} byte. Se per \param{len} si usa un
+valore nullo la regione coperta sarà da \param{offset} alla fine del
+file.\footnote{questo è vero solo per le versioni più recenti, fino al kernel
+ 2.6.6 il valore nullo veniva interpretato letteralmente.} Le modalità sono
+indicate dall'argomento \param{advice} che è una maschera binaria dei valori
+illustrati in tab.~\ref{tab:posix_fadvise_flag}. Si tenga presente comunque
+che la funzione dà soltanto un avvertimento, non esiste nessun vincolo per il
+kernel, che utilizza semplicemente l'informazione.
\begin{table}[htb]
\centering
\footnotesize
- \begin{tabular}[c]{|l|p{7cm}|}
+ \begin{tabular}[c]{|l|p{10cm}|}
\hline
\textbf{Valore} & \textbf{Significato} \\
\hline
\hline
- \const{LOCK\_SH}& Richiede uno \textit{shared lock}. Più processi possono
- mantenere un lock condiviso sullo stesso file.\\
- \const{LOCK\_EX}& Richiede un \textit{exclusive lock}. Un solo processo
- alla volta può mantenere un lock esclusivo su un file.\\
- \const{LOCK\_UN}& Sblocca il file.\\
- \const{LOCK\_NB}& Non blocca la funzione quando il lock non è disponibile,
- si specifica sempre insieme ad una delle altre operazioni
- con un OR aritmetico dei valori.\\
- \hline
+ \const{POSIX\_FADV\_NORMAL} & Non ci sono avvisi specifici da fare
+ riguardo le modalità di accesso, il
+ comportamento sarà identico a quello che si
+ avrebbe senza nessun avviso.\\
+ \const{POSIX\_FADV\_SEQUENTIAL}& L'applicazione si aspetta di accedere di
+ accedere ai dati specificati in maniera
+ sequenziale, a partire dalle posizioni più
+ basse.\\
+ \const{POSIX\_FADV\_RANDOM} & I dati saranno letti in maniera
+ completamente causale.\\
+ \const{POSIX\_FADV\_NOREUSE} & I dati saranno acceduti una sola volta.\\
+ \const{POSIX\_FADV\_WILLNEED}& I dati saranno acceduti a breve.\\
+ \const{POSIX\_FADV\_DONTNEED}& I dati non saranno acceduti a breve.\\
+ \hline
\end{tabular}
- \caption{Valori possibili per l'argomento \param{cmd} di \func{lockf}.}
- \label{tab:file_lockf_type}
+ \caption{Valori dei bit dell'argomento \param{advice} di
+ \func{posix\_fadvise} che indicano la modalità con cui si intende accedere
+ ad un file.}
+ \label{tab:posix_fadvise_flag}
\end{table}
-Qualora il lock non possa essere acquisito, a meno di non aver specificato
-\const{LOCK\_NB}, la funzione si blocca fino alla disponibilità dello stesso.
-Dato che la funzione è implementata utilizzando \func{fcntl} la semantica
-delle operazioni è la stessa di quest'ultima (pertanto la funzione non è
-affatto equivalente a \func{flock}).
+Anche \func{posix\_fadvise} si appoggia al sistema della memoria virtuale ed
+al meccanismo standard del \textit{readahead} utilizzato dal kernel; in
+particolare con \const{POSIX\_FADV\_SEQUENTIAL} si raddoppia la dimensione
+dell'ammontare di dati letti preventivamente rispetto al default, aspettandosi
+appunto una lettura sequenziale che li utilizzerà, mentre con
+\const{POSIX\_FADV\_RANDOM} si disabilita del tutto il suddetto meccanismo,
+dato che con un accesso del tutto casuale è inutile mettersi a leggere i dati
+immediatamente successivi gli attuali; infine l'uso di
+\const{POSIX\_FADV\_NORMAL} consente di riportarsi al comportamento di
+default.
+Le due modalità \const{POSIX\_FADV\_NOREUSE} e \const{POSIX\_FADV\_WILLNEED}
+danno invece inizio ad una lettura in cache della regione del file indicata.
+La quantità di dati che verranno letti è ovviamente limitata in base al carico
+che si viene a creare sul sistema della memoria virtuale, ma in genere una
+lettura di qualche megabyte viene sempre soddisfatta (ed un valore superiore è
+solo raramente di qualche utilità). In particolare l'uso di
+\const{POSIX\_FADV\_WILLNEED} si può considerare l'equivalente POSIX di
+\func{readahead}.
+Infine con \const{POSIX\_FADV\_DONTNEED} si dice al kernel di liberare le
+pagine di cache occupate dai dati presenti nella regione di file indicata.
+Questa è una indicazione utile che permette di alleggerire il carico sulla
+cache, ed un programma può utilizzare periodicamente questa funzione per
+liberare pagine di memoria da dati che non sono più utilizzati per far posto a
+nuovi dati utili.\footnote{la pagina di manuale riporta l'esempio dello
+ streaming di file di grosse dimensioni, dove le pagine occupate dai dati già
+ inviati possono essere tranquillamente scartate.}
-\subsection{Il \textit{mandatory locking}}
-\label{sec:file_mand_locking}
+Sia \func{posix\_fadvise} che \func{readahead} attengono alla ottimizzazione
+dell'accesso in lettura; lo standard POSIX.1-2001 prevede anche una funzione
+specifica per le operazioni di scrittura, \func{posix\_fallocate},\footnote{la
+ funzione è stata introdotta a partire dalle glibc 2.1.94.} che consente di
+preallocare dello spazio disco per assicurarsi che una seguente scrittura non
+fallisca, il suo prototipo, anch'esso disponibile solo se si definisce la
+macro \macro{\_XOPEN\_SOURCE} ad almeno 600, è:
+\begin{functions}
+ \headdecl{fcntl.h}
-\itindbeg{mandatory~locking|(}
+ \funcdecl{int posix\_fallocate(int fd, off\_t offset, off\_t len)}
+
+ Richiede la allocazione di spazio disco per un file.
-Il \textit{mandatory locking} è una opzione introdotta inizialmente in SVr4,
-per introdurre un file locking che, come dice il nome, fosse effettivo
-indipendentemente dai controlli eseguiti da un processo. Con il
-\textit{mandatory locking} infatti è possibile far eseguire il blocco del file
-direttamente al sistema, così che, anche qualora non si predisponessero le
-opportune verifiche nei processi, questo verrebbe comunque rispettato.
+ \bodydesc{La funzione restituisce 0 in caso di successo e direttamente un
+ codice di errore, in caso di fallimento, in questo caso \var{errno} non
+ viene impostata, ma sarà restituito direttamente uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor
+ valido o non è aperto in scrittura.
+ \item[\errcode{EINVAL}] o \param{offset} o \param{len} sono minori di
+ zero.
+ \item[\errcode{EFBIG}] il valore di (\param{offset} + \param{len}) eccede
+ la dimensione massima consentita per un file.
+ \item[\errcode{ENODEV}] l'argomento \param{fd} non fa riferimento ad un
+ file regolare.
+ \item[\errcode{ENOSPC}] non c'è sufficiente spazio disco per eseguire
+ l'operazione.
+ \item[\errcode{ESPIPE}] l'argomento \param{fd} è una pipe.
+ \end{errlist}
+ }
+\end{functions}
-Per poter utilizzare il \textit{mandatory locking} è stato introdotto un
-utilizzo particolare del bit \itindex{sgid~bit} \acr{sgid}. Se si ricorda
-quanto esposto in sez.~\ref{sec:file_special_perm}), esso viene di norma
-utilizzato per cambiare il group-ID effettivo con cui viene eseguito un
-programma, ed è pertanto sempre associato alla presenza del permesso di
-esecuzione per il gruppo. Impostando questo bit su un file senza permesso di
-esecuzione in un sistema che supporta il \textit{mandatory locking}, fa sì che
-quest'ultimo venga attivato per il file in questione. In questo modo una
-combinazione dei permessi originariamente non contemplata, in quanto senza
-significato, diventa l'indicazione della presenza o meno del \textit{mandatory
- locking}.\footnote{un lettore attento potrebbe ricordare quanto detto in
- sez.~\ref{sec:file_perm_management} e cioè che il bit \acr{sgid} viene
- cancellato (come misura di sicurezza) quando di scrive su un file, questo
- non vale quando esso viene utilizzato per attivare il \textit{mandatory
- locking}.}
+La funzione si assicura che venga allocato sufficiente spazio disco perché sia
+possibile scrivere sul file indicato dall'argomento \param{fd} nella regione
+che inizia dalla posizione \param{offset} e si estende per \param{len} byte;
+se questa si estende oltre la fine del file le dimensioni di quest'ultimo
+saranno incrementate di conseguenza. Dopo aver eseguito con successo la
+funzione è garantito che una scrittura nella regione indicata non fallirà per
+mancanza di spazio disco.
-L'uso del \textit{mandatory locking} presenta vari aspetti delicati, dato che
-neanche l'amministratore può passare sopra ad un lock; pertanto un processo
-che blocchi un file cruciale può renderlo completamente inaccessibile,
-rendendo completamente inutilizzabile il sistema\footnote{il problema si
- potrebbe risolvere rimuovendo il bit \itindex{sgid~bit} \acr{sgid}, ma non è
- detto che sia così facile fare questa operazione con un sistema bloccato.}
-inoltre con il \textit{mandatory locking} si può bloccare completamente un
-server NFS richiedendo una lettura su un file su cui è attivo un lock. Per
-questo motivo l'abilitazione del mandatory locking è di norma disabilitata, e
-deve essere attivata filesystem per filesystem in fase di montaggio
-(specificando l'apposita opzione di \func{mount} riportata in
-tab.~\ref{tab:sys_mount_flags}, o con l'opzione \code{-o mand} per il comando
-omonimo).
+% TODO controllare la trattazione della nuova funzionalità di preallocazione
-Si tenga presente inoltre che il \textit{mandatory locking} funziona solo
-sull'interfaccia POSIX di \func{fcntl}. Questo ha due conseguenze: che non si
-ha nessun effetto sui lock richiesti con l'interfaccia di \func{flock}, e che
-la granularità del lock è quella del singolo byte, come per \func{fcntl}.
+% 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
-La sintassi di acquisizione dei lock è esattamente la stessa vista in
-precedenza per \func{fcntl} e \func{lockf}, la differenza è che in caso di
-mandatory lock attivato non è più necessario controllare la disponibilità di
-accesso al file, ma si potranno usare direttamente le ordinarie funzioni di
-lettura e scrittura e sarà compito del kernel gestire direttamente il file
-locking.
-
-Questo significa che in caso di read lock la lettura dal file potrà avvenire
-normalmente con \func{read}, mentre una \func{write} si bloccherà fino al
-rilascio del lock, a meno di non aver aperto il file con \const{O\_NONBLOCK},
-nel qual caso essa ritornerà immediatamente con un errore di \errcode{EAGAIN}.
-
-Se invece si è acquisito un write lock tutti i tentativi di leggere o scrivere
-sulla regione del file bloccata fermeranno il processo fino al rilascio del
-lock, a meno che il file non sia stato aperto con \const{O\_NONBLOCK}, nel
-qual caso di nuovo si otterrà un ritorno immediato con l'errore di
-\errcode{EAGAIN}.
+% TODO documentare \func{fallocate}, introdotta con il 2.6.23
+% vedi http://lwn.net/Articles/226710/ e http://lwn.net/Articles/240571/
+% http://kernelnewbies.org/Linux_2_6_23
+% \func{fallocate} con il 2.6.25 supporta pure XFS
-Infine occorre ricordare che le funzioni di lettura e scrittura non sono le
-sole ad operare sui contenuti di un file, e che sia \func{creat} che
-\func{open} (quando chiamata con \const{O\_TRUNC}) effettuano dei cambiamenti,
-così come \func{truncate}, riducendone le dimensioni (a zero nei primi due
-casi, a quanto specificato nel secondo). Queste operazioni sono assimilate a
-degli accessi in scrittura e pertanto non potranno essere eseguite (fallendo
-con un errore di \errcode{EAGAIN}) su un file su cui sia presente un qualunque
-lock (le prime due sempre, la terza solo nel caso che la riduzione delle
-dimensioni del file vada a sovrapporsi ad una regione bloccata).
-L'ultimo aspetto della interazione del \textit{mandatory locking} con le
-funzioni di accesso ai file è quello relativo ai file mappati in memoria (che
-abbiamo trattato in sez.~\ref{sec:file_memory_map}); anche in tal caso infatti,
-quando si esegue la mappatura con l'opzione \const{MAP\_SHARED}, si ha un
-accesso al contenuto del file. Lo standard SVID prevede che sia impossibile
-eseguire il memory mapping di un file su cui sono presenti dei
-lock\footnote{alcuni sistemi, come HP-UX, sono ancora più restrittivi e lo
- impediscono anche in caso di \textit{advisory locking}, anche se questo
- comportamento non ha molto senso, dato che comunque qualunque accesso
- diretto al file è consentito.} in Linux è stata però fatta la scelta
-implementativa\footnote{per i dettagli si possono leggere le note relative
- all'implementazione, mantenute insieme ai sorgenti del kernel nel file
- \file{Documentation/mandatory.txt}.} di seguire questo comportamento
-soltanto quando si chiama \func{mmap} con l'opzione \const{MAP\_SHARED} (nel
-qual caso la funzione fallisce con il solito \errcode{EAGAIN}) che comporta la
-possibilità di modificare il file.
+%\subsection{L'utilizzo delle porte di I/O}
+%\label{sec:file_io_port}
+%
+% TODO l'I/O sulle porte di I/O
+% consultare le manpage di ioperm, iopl e outb
+
-\index{file!locking|)}
-\itindend{mandatory~locking|(}
% LocalWords: dell'I locking multiplexing cap dell' sez system call socket BSD
% LocalWords: splice result argument DMA controller zerocopy Linus Larry Voy
% LocalWords: Jens Anxboe vmsplice seek ESPIPE GIFT TCP CORK MSG splicecp nr
% LocalWords: nwrite segs patch readahead posix fadvise TC advice FADV NORMAL
+% LocalWords: SEQUENTIAL NOREUSE WILLNEED DONTNEED streaming fallocate EFBIG
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "gapil"
%%% End:
-% LocalWords: SEQUENTIAL NOREUSE WILLNEED DONTNEED streaming fallocate EFBIG