X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=fileadv.tex;h=8b979cb3780e0ebae471a271497d36bc3d67db62;hp=87d5ba7319a6f3f52561e3f0aa539e98ebc2e714;hb=beece18eba2dcc2a9b915dab61277df8685a3da6;hpb=0e076776e7518bf19a03be39bdfb45cd76040cba diff --git a/fileadv.tex b/fileadv.tex index 87d5ba7..8b979cb 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -1,6 +1,6 @@ %% fileadv.tex %% -%% Copyright (C) 2000-2007 Simone Piccardi. Permission is granted to +%% Copyright (C) 2000-2014 Simone Piccardi. Permission is granted to %% copy, distribute and/or modify this document under the terms of the GNU Free %% Documentation License, Version 1.1 or any later version published by the %% Free Software Foundation; with the Invariant Sections being "Un preambolo", @@ -8,4431 +8,5428 @@ %% license is included in the section entitled "GNU Free Documentation %% License". %% + \chapter{La gestione avanzata dei file} \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}. - - -\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. +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. -\subsection{La problematica dell'\textit{I/O multiplexing}} -\label{sec:file_noblocking} +\section{Il \textit{file locking}} +\label{sec:file_locking} -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 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}. +\itindbeg{file~locking} -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. +In sez.~\ref{sec:file_shared_access} abbiamo preso in esame le modalità in cui +un sistema unix-like gestisce l'accesso concorrente ai 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. -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}. +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. +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. -\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} +\subsection{L'\textit{advisory locking}} +\label{sec:file_record_locking} -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}. +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. -\itindbeg{file~descriptor~set} +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. -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). +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. - \funcdecl{void \macro{FD\_SET}(int fd, fd\_set *set)} - Inserisce il file descriptor \param{fd} nell'insieme. +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. - \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} +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 recepita dallo standard POSIX.1 (che è +derivata dall'interfaccia usata in 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. -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. +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 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}). +La situazione delle varie possibilità che si possono verificare è riassunta in +tab.~\ref{tab:file_file_lock}, dove si sono riportati, a seconda delle varie +tipologie di blocco già presenti su un file, il risultato che si avrebbe in +corrispondenza di una ulteriore richiesta da parte di un processo di un blocco +nelle due tipologie di \textit{file lock} menzionate, con un successo o meno +della richiesta. -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 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. +\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} -\itindend{file~descriptor~set} +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}). -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. +%% 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 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. +\subsection{La funzione \func{flock}} +\label{sec:file_flock} -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)} +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)} - Attende che uno dei file descriptor degli insiemi specificati diventi - attivo. + Applica o rimuove un \textit{file lock} sul file \param{fd}. - \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}.} + \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 è sostanzialmente identica a \func{select}, solo che usa una -struttura \struct{timespec} (vedi fig.~\ref{fig:sys_timeval_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. +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}. -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. +\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} -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. +I primi due valori, \const{LOCK\_SH} e \const{LOCK\_EX} permettono di +richiedere un \textit{file lock} rispettivamente condiviso o esclusivo, ed +ovviamente non possono essere usati insieme. Se con essi 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 direttamente const{LOCK\_UN}. + +Si tenga presente che non esiste 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. + +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 di un file; perciò le informazioni relative agli eventuali +\textit{file lock} sono mantenute dal kernel a livello di +inode\itindex{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 + \kstruct{file\_lock}. La lista è referenziata dall'indirizzo di partenza + mantenuto dal campo \var{i\_flock} della struttura \kstruct{inode} (per le + definizioni esatte si faccia riferimento al file \file{include/linux/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 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. +\begin{figure}[!htb] + \centering + \includegraphics[width=15.5cm]{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} +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 \kstruct{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 + \kstruct{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_shared_access}, 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} su un file, 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 per processi +diversi. -\subsection{Le funzioni \func{poll} e \func{ppoll}} -\label{sec:file_poll} +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. + -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)} +\subsection{Il \textit{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_ioctl}. 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] \footnotesize \centering - \begin{minipage}[c]{15cm} - \includestruct{listati/pollfd.h} + \begin{minipage}[c]{\textwidth} + \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.} +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 \ids{PID} del processo che detiene il +\textit{file lock}. -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}. +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_ioctl}, +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} -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.} +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. -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}. +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. -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}. +\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} -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} +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}. -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} +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 \kstruct{file\_lock} significativi per la + semantica POSIX, in particolare adesso ciascuna struttura contiene, oltre al + \ids{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 \itindex{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 +\ids{PID} del processo. -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. +\begin{figure}[!htb] + \centering \includegraphics[width=12cm]{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} +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 + \kstruct{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. -\subsection{L'interfaccia di \textit{epoll}} -\label{sec:file_epoll} +Nel caso di rimozione invece questa viene effettuata controllando che il +\ids{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 \ids{PID} diverso, mentre passa indenne attraverso una +\func{exec} in quanto il \ids{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. + +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 \ids{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 +\ids{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 \ids{PID} del processo che chiama la funzione, queste richieste +avranno sempre successo. -\itindbeg{epoll} +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. -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à. +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/Flock.c} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del programma \file{Flock.c}.} + \label{fig:file_flock_code} +\end{figure} -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. +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, nel file \texttt{Flock.c}). -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à. +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}. -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}. +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 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 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}). -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}. +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. -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} +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: -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. - -Una volta ottenuto un file descriptor per \textit{epoll} il passo successivo è -indicare quali file descriptor mettere sotto osservazione e quali operazioni -controllare, per questo si deve usare la seconda funzione dell'interfaccia, -\funcd{epoll\_ctl}, il cui prototipo è: -\begin{prototype}{sys/epoll.h} - {int epoll\_ctl(int epfd, int op, int fd, struct epoll\_event *event)} - - Esegue le operazioni di controllo di \textit{epoll}. - - \bodydesc{La funzione restituisce $0$ in caso di successo o $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: - \begin{errlist} - \item[\errcode{EBADF}] il file descriptor \param{epfd} o \param{fd} non sono - validi. - \item[\errcode{EEXIST}] l'operazione richiesta è \const{EPOLL\_CTL\_ADD} ma - \param{fd} è già stato inserito in \param{epfd}. - \item[\errcode{EINVAL}] il file descriptor \param{epfd} non è stato ottenuto - con \func{epoll\_create}, o \param{fd} è lo stesso \param{epfd} o - l'operazione richiesta con \param{op} non è supportata. - \item[\errcode{ENOENT}] l'operazione richiesta è \const{EPOLL\_CTL\_MOD} o - \const{EPOLL\_CTL\_DEL} ma \param{fd} non è inserito in \param{epfd}. - \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel gestire - l'operazione richiesta. - \item[\errcode{EPERM}] il file \param{fd} non supporta \textit{epoll}. - \end{errlist} -} -\end{prototype} +\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: -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. +\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: -\begin{table}[htb] - \centering - \footnotesize - \begin{tabular}[c]{|l|p{8cm}|} - \hline - \textbf{Valore} & \textbf{Significato} \\ - \hline - \hline - \const{EPOLL\_CTL\_ADD}& Aggiunge un nuovo file descriptor da osservare - \param{fd} alla lista dei file descriptor - controllati tramite \param{epfd}, 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{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} +\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: -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}. +\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: -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}.} +\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. -\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} +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: -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}. +\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: -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}. +\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)} + + Applica, controlla 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}] 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 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{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\\ + \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{Costanti che identificano i bit del campo \param{events} di - \struct{epoll\_event}.} - \label{tab:epoll_events} + \caption{Valori possibili per l'argomento \param{cmd} di \func{lockf}.} + \label{tab:file_lockf_type} \end{table} -\footnotetext{questa modalità è disponibile solo a partire dal kernel 2.6.2.} - -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. +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}). -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. -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}. -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}.} +\subsection{Il \textit{mandatory locking}} +\label{sec:file_mand_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}. +\itindbeg{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} +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. -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}. +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 \ids{GID} 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 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. +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 sez.~\ref{sec:filesystem_mounting}), o con l'opzione +\code{-o mand} per il comando omonimo). -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 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}. + +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}. -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. +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). -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. - -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}. +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. +\itindend{file~locking} -\itindend{epoll} +\itindend{mandatory~locking} +\section{L'\textit{I/O multiplexing}} +\label{sec:file_multiplexing} -\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. +Uno dei problemi che si presentano quando si deve operare contemporaneamente +su molti file usando le funzioni illustrate in +sez.~\ref{sec:file_unix_interface} e sez.~\ref{sec:files_std_interface} è che +si può essere bloccati nelle operazioni su un file mentre un altro potrebbe +essere disponibile. L'\textit{I/O multiplexing} nasce risposta a questo +problema. In questa sezione forniremo una introduzione a questa problematica +ed analizzeremo le varie funzioni usate per implementare questa modalità di +I/O. -\subsection{Il \textit{Signal driven I/O}} -\label{sec:file_asyncronous_operation} +\subsection{La problematica dell'\textit{I/O multiplexing}} +\label{sec:file_noblocking} -\itindbeg{signal~driven~I/O} +Abbiamo visto in sez.~\ref{sec:sig_gen_beha}, affrontando la suddivisione fra +\textit{fast} e \textit{slow} \textit{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 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 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}). - -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.} - -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. - -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. - -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. +Abbiamo già accennato in sez.~\ref{sec:file_open_close} 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 \textit{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. -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}). +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}. -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 asincrono, ed indica appunto che il - segnale è stato generato a causa di attività nell'I/O asincrono.} di -\struct{siginfo\_t}, troverà nel campo \var{si\_fd} il valore del file -descriptor che ha generato il segnale. - -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. -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}.} +\subsection{Le funzioni \func{select} e \func{pselect}} +\label{sec:file_select} -% TODO fare esempio che usa O_ASYNC +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} -\itindend{signal~driven~I/O} +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}. +\itindbeg{file~descriptor~set} +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). -\subsection{I meccanismi di notifica asincrona.} -\label{sec:file_asyncronous_lease} + \funcdecl{void \macro{FD\_SET}(int fd, fd\_set *set)} + Inserisce il file descriptor \param{fd} nell'insieme. -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. + \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} -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. +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 \headfile{sys/select.h}, ed è pari a 1024.} -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}. +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. -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 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}). -\index{file!lease|(} +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}, 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. + +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 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}. +\itindend{file~descriptor~set} -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. +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, 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.} + +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 \headfile{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 \headfile{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 \textit{system call} di Linux + aggiorna il valore al tempo rimanente, ma la funzione fornita dalle + \acr{glibc} modifica questo comportamento passando alla \textit{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 +\index{maschera~dei~segnali} 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 \index{variabili!globali} variabile +globale e controllare questa nel corpo principale del programma; abbiamo visto +in sez.~\ref{sec:sig_example} come questo lasci spazio a possibili +\itindex{race~condition} \textit{race condition}, per cui diventa essenziale +utilizzare \func{sigprocmask} per disabilitare la ricezione del segnale prima +di eseguire il controllo e riabilitarlo dopo l'esecuzione delle relative +operazioni, onde evitare l'arrivo di un segnale immediatamente dopo il +controllo, che andrebbe perso. + +Nel nostro caso il problema si pone quando oltre al segnale si devono tenere +sotto controllo anche dei file descriptor con \func{select}, in questo caso si +può fare conto sul fatto che all'arrivo di un segnale essa verrebbe interrotta +e si potrebbero eseguire di conseguenza le operazioni relative al segnale e +alla gestione dati con un ciclo del tipo: +\includecodesnip{listati/select_race.c} +qui però emerge una \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. + +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 \textit{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. + + +\subsection{Le funzioni \func{poll} e \func{ppoll}} +\label{sec:file_poll} + +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 + \const{RLIMIT\_NOFILE}. + \end{errlist} + ed inoltre \errval{EFAULT} e \errval{ENOMEM}.} +\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. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\textwidth} + \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. - -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. +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. -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}. +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 \headfile{sys/poll.h} non è + sufficiente.} -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. +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}. +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.} -\index{file!dnotify|(} +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}. -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. +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}. -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}. +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 + \const{RLIMIT\_NOFILE}. + \end{errlist} + ed inoltre \errval{EFAULT} e \errval{ENOMEM}.} +\end{prototype} -\index{file!lease|)} +La funzione ha lo stesso comportamento di \func{poll}, solo che si può +specificare, con l'argomento \param{sigmask}, il puntatore ad una +\index{maschera~dei~segnali} 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} -\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} +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.} -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. +\subsection{L'interfaccia di \textit{epoll}} +\label{sec:file_epoll} -\index{file!inotify|(} +\itindbeg{epoll} -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. +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à. -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. +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. -\index{file!dnotify|)} +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à. -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)} +Le modalità con cui avviene la notifica sono due, la prima è quella classica +(quella usata da \func{poll} e \func{select}) che viene chiamata \textit{level + triggered}.\footnote{la nomenclatura è stata introdotta da Jonathan Lemon in + un articolo su \texttt{kqueue} al BSDCON 2000, e deriva da quella usata + nell'elettronica digitale.} In questa modalità vengono notificati i file +descriptor che sono \textsl{pronti} per l'operazione richiesta, e questo +avviene indipendentemente dalle operazioni che possono essere state fatte su +di essi a partire dalla precedente notifica. Per chiarire meglio il concetto +ricorriamo ad un esempio: se su un file descriptor sono diventati disponibili +in lettura 2000 byte ma dopo la notifica ne sono letti solo 1000 (ed è quindi +possibile eseguire una ulteriore lettura dei restanti 1000), in modalità +\textit{level triggered} questo sarà nuovamente notificato come +\textsl{pronto}. + +La seconda modalità, è detta \textit{edge triggered}, e prevede che invece +vengano notificati solo i file descriptor che hanno subito una transizione da +\textsl{non pronti} a \textsl{pronti}. Questo significa che in modalità +\textit{edge triggered} nel caso del precedente esempio il file descriptor +diventato pronto da cui si sono letti solo 1000 byte non verrà nuovamente +notificato come pronto, nonostante siano ancora disponibili in lettura 1000 +byte. Solo una volta che si saranno esauriti tutti i dati 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 di apposite \textit{system call}. Il +primo passo per usare l'interfaccia di \textit{epoll} è pertanto quello +ottenere detto file descriptor chiamando una delle funzioni +\funcd{epoll\_create} e \funcd{epoll\_create1},\footnote{l'interfaccia di + \textit{epoll} è stata inserita nel kernel a partire dalla versione 2.5.44, + ed il supporto è stato aggiunto alle \acr{glibc} 2.3.2.} i cui prototipi +sono: +\begin{functions} + \headdecl{sys/epoll.h} + + \funcdecl{int epoll\_create(int size)} + \funcdecl{int epoll\_create1(int flags)} - 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: + \bodydesc{Le funzioni restituiscono un file descriptor per \textit{epoll} 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 + \item[\errcode{EINVAL}] si è specificato un valore di \param{size} non + positivo o non valido per \param{flags}. + \item[\errcode{ENFILE}] si è raggiunto il massimo di file descriptor aperti nel sistema. - \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel per creare + \item[\errcode{EMFILE}] si è raggiunto il limite sul numero massimo di + istanze di \textit{epoll} per utente stabilito da + \sysctlfile{fs/epoll/max\_user\_instances}. + \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel per creare l'istanza. \end{errlist} } -\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}. +\end{functions} - \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: +Entrambe le funzioni restituiscono 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. Nel caso di +\func{epoll\_create} l'argomento \param{size} serviva a dare l'indicazione del +numero di file descriptor che si vorranno tenere sotto controllo, e costituiva +solo un suggerimento per semplificare l'allocazione di risorse sufficienti, +non un valore massimo.\footnote{ma a partire dal kernel 2.6.8 esso viene + totalmente ignorato e l'allocazione è sempre dinamica.} + +La seconda versione della funzione, \func{epoll\_create1} è stata +introdotta\footnote{è disponibile solo a partire dal kernel 2.6.27.} come +estensione della precedente, per poter passare dei flag di controllo come +maschera binaria in fase di creazione del file descriptor. Al momento l'unico +valore legale per \param{flags} (a parte lo zero) è \const{EPOLL\_CLOEXEC}, +che consente di impostare in maniera atomica sul file descriptor il flag di +\itindex{close-on-exec} \textit{close-on-exec} (si veda il significato di +\const{O\_CLOEXEC} in sez.~\ref{sec:file_open_close}), senza che sia +necessaria una successiva chiamata a \func{fcntl}. + +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. + \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}. + \item[\errcode{ENOSPC}] si è raggiunto il limite massimo di registrazioni + per utente di file descriptor da osservare imposto da + \sysctlfile{fs/epoll/max\_user\_watches}. \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. +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|c|p{10cm}|} + \begin{tabular}[c]{|l|p{8cm}|} \hline - \textbf{Valore} & & \textbf{Significato} \\ + \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.\\ + \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 il - tipo di evento da tenere sotto osservazione.} - \label{tab:inotify_event_watch} + \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} -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. +% aggiunta EPOLL_CTL_DISABLE con il kernel 3.7, vedi +% http://lwn.net/Articles/520012/ e http://lwn.net/Articles/520198/ + +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}. + +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 \val{NULL} ma se si + vuole mantenere la compatibilità con le versioni precedenti occorre usare un + puntatore valido.} + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\textwidth} + \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} + +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 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}, è una \direct{union} +che serve a identificare il file descriptor a cui si intende fare riferimento, +ed in astratto può contenere un valore qualsiasi (specificabile in diverse +forme) che ne permetta una indicazione univoca. Il modo più comune di usarlo +però è quello in cui si specifica il terzo argomento di \func{epoll\_ctl} +nella forma \var{event.data.fd}, assegnando come valore di questo campo lo +stesso valore dell'argomento \param{fd}, cosa che permette una immediata +identificazione del file descriptor. \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{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}).\footnotemark\\ + \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; questa + condizione viene comunque riportata in uscita, e non + è necessaria impostarla in ingresso.\\ + \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 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{Costanti che identificano i bit del campo \param{events} di + \struct{epoll\_event}.} + \label{tab:epoll_events} \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. +\footnotetext{questa modalità è disponibile solo a partire dal kernel 2.6.17, + ed è utile per riconoscere la chiusura di una connessione dall'altro capo + quando si lavora in modalità \textit{edge triggered}.} -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. +\footnotetext[48]{questa modalità è disponibile solo a partire dal kernel + 2.6.2.} -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. +% TODO aggiunto EPOLLWAKEUP con il 3.5 -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)} +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 \code{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. - Rimuove un \textsl{osservatore} da una coda di notifica. +% TODO verificare se prima o poi epoll_ctlv verrà introdotta + +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. + +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}. + +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}.} + +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}. + +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)} - \bodydesc{La funzione restituisce 0 in caso di successo, o $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: + 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}] 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. + \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 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 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}. -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. +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. -\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} +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.} -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. - -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}. +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. + +Si tenga presente infine che con l'uso della modalità \textit{edge triggered} +il ritorno di \func{epoll\_wait} indica che 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 per il fatto che sono stati +restituiti meno dati di quelli richiesti. + +Come già per \func{select} e \func{poll} anche per l'interfaccia di +\textit{epoll} si pone il problema di gestire l'attesa di segnali e di dati +contemporaneamente per le osservazioni fatte in sez.~\ref{sec:file_select}, +per fare questo di nuovo è necessaria una variante della funzione di attesa +che consenta di reimpostare all'uscita una \index{maschera~dei~segnali} +maschera di segnali, analoga alle estensioni \func{pselect} e \func{ppoll} che +abbiamo visto in precedenza per \func{select} e \func{poll}; in questo caso la +funzione si chiama \funcd{epoll\_pwait}\footnote{la funziona è stata + introdotta a partire dal kernel 2.6.19, ed è come tutta l'interfaccia di + \textit{epoll}, specifica di Linux.} ed il suo prototipo è: +\begin{prototype}{sys/epoll.h} + {int epoll\_pwait(int epfd, struct epoll\_event * events, int maxevents, + int timeout, const sigset\_t *sigmask)} + + Attende che uno dei file descriptor osservati sia pronto, mascherando i + segnali. + + \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 già visti con \funcd{epoll\_wait}. +} +\end{prototype} + +La funzione è del tutto analoga \funcd{epoll\_wait}, soltanto che alla sua +uscita viene ripristinata la \index{maschera~dei~segnali} maschera di segnali +originale, sostituita durante l'esecuzione da quella impostata con +l'argomento \param{sigmask}; in sostanza la chiamata a questa funzione è +equivalente al seguente codice, eseguito però in maniera atomica: +\includecodesnip{listati/epoll_pwait_means.c} + +Si tenga presente che come le precedenti funzioni di \textit{I/O multiplexing} +anche 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 anche in +questo caso 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_serv_epoll}. + +\itindend{epoll} + + +\subsection{La notifica di eventi tramite file descriptor} +\label{sec:sig_signalfd_eventfd} + +Abbiamo visto in sez.~\ref{sec:file_select} come il meccanismo classico delle +notifiche di eventi tramite i segnali, presente da sempre nei sistemi +unix-like, porti a notevoli problemi nell'interazione con le funzioni per +l'\textit{I/O multiplexing}, tanto che per evitare possibili +\itindex{race~condition} \textit{race condition} sono state introdotte +estensioni dello standard POSIX e funzioni apposite come \func{pselect}, +\func{ppoll} e \funcd{epoll\_pwait}. + +Benché i segnali siano il meccanismo più usato per effettuare notifiche ai +processi, la loro interfaccia di programmazione, che comporta l'esecuzione di +una funzione di gestione in maniera asincrona e totalmente scorrelata +dall'ordinario flusso di esecuzione del processo, si è però dimostrata quasi +subito assai problematica. Oltre ai limiti relativi ai limiti al cosa si può +fare all'interno della funzione del gestore di segnali (quelli illustrati in +sez.~\ref{sec:sig_signal_handler}), c'è il problema più generale consistente +nel fatto che questa modalità di funzionamento cozza con altre interfacce di +programmazione previste dal sistema in cui si opera in maniera +\textsl{sincrona}, come quelle dell'I/O multiplexing appena illustrate. + +In questo tipo di interfacce infatti ci si aspetta che il processo gestisca +gli eventi a cui vuole rispondere in maniera sincrona generando le opportune +risposte, mentre con l'arrivo di un segnale si possono avere interruzioni +asincrone in qualunque momento. Questo comporta la necessità di dover +gestire, quando si deve tener conto di entrambi i tipi di eventi, le +interruzioni delle funzioni di attesa sincrone, ed evitare possibili +\itindex{race~condition} \textit{race conditions}.\footnote{in sostanza se non + fossero per i segnali non ci sarebbe da doversi preoccupare, fintanto che si + effettuano operazioni all'interno di un processo, della non atomicità delle + \index{system~call~lente} \textit{system call} lente che vengono interrotte + e devono essere riavviate.} + +Abbiamo visto però in sez.~\ref{sec:sig_real_time} che insieme ai segnali +\textit{real-time} sono state introdotte anche delle interfacce di gestione +sincrona dei segnali con la funzione \func{sigwait} e le sue affini. Queste +funzioni consentono di gestire i segnali bloccando un processo fino alla +avvenuta ricezione e disabilitando l'esecuzione asincrona rispetto al resto +del programma del gestore del segnale. Questo consente di risolvere i problemi +di atomicità nella gestione degli eventi associati ai segnali, avendo tutto il +controllo nel flusso principale del programma, ottenendo così una gestione +simile a quella dell'\textit{I/O multiplexing}, ma non risolve i problemi +delle interazioni con quest'ultimo, perché o si aspetta la ricezione di un +segnale o si aspetta che un file descriptor sia accessibile e nessuna delle +rispettive funzioni consente di fare contemporaneamente entrambe le cose. + +Per risolvere questo problema nello sviluppo del kernel si è pensato di +introdurre un meccanismo alternativo per la notifica dei segnali (esteso anche +ad altri eventi generici) che, ispirandosi di nuovo alla filosofia di Unix per +cui tutto è un file, consentisse di eseguire la notifica con l'uso di +opportuni file descriptor.\footnote{ovviamente si tratta di una funzionalità + specifica di Linux, non presente in altri sistemi unix-like, e non prevista + da nessuno standard, per cui va evitata se si ha a cuore la portabilità.} + +In sostanza, come per \func{sigwait}, si può disabilitare l'esecuzione di un +gestore in occasione dell'arrivo di un segnale, e rilevarne l'avvenuta +ricezione leggendone la notifica tramite l'uso di uno speciale file +descriptor. Trattandosi di un file descriptor questo potrà essere tenuto sotto +osservazione con le ordinarie funzioni dell'\textit{I/O multiplexing} (vale a +dire con le solite \func{select}, \func{poll} e \funcd{epoll\_wait}) allo +stesso modo di quelli associati a file o socket, per cui alla fine si potrà +attendere in contemporanea sia l'arrivo del segnale che la disponibilità di +accesso ai dati relativi a questi ultimi. + +La funzione che permette di abilitare la ricezione dei segnali tramite file +descriptor è \funcd{signalfd},\footnote{in realtà quella riportata è + l'interfaccia alla funzione fornita dalle \acr{glibc}, esistono infatti due + versioni diverse della \textit{system call}; una prima versione, + \func{signalfd}, introdotta nel kernel 2.6.22 e disponibile con le + \acr{glibc} 2.8 che non supporta l'argomento \texttt{flags}, ed una seconda + versione, \funcm{signalfd4}, introdotta con il kernel 2.6.27 e che è quella + che viene sempre usata a partire dalle \acr{glibc} 2.9, che prende un + argomento aggiuntivo \code{size\_t sizemask} che indica la dimensione della + \index{maschera~dei~segnali} maschera dei segnali, il cui valore viene + impostato automaticamente dalle \acr{glibc}.} il cui prototipo è: +\begin{prototype}{sys/signalfd.h} + {int signalfd(int fd, const sigset\_t *mask, int flags)} + + Crea o modifica un file descriptor per la ricezione dei segnali. + + \bodydesc{La funzione restituisce un numero di 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{EBADF}] il valore \param{fd} non indica un file descriptor. + \item[\errcode{EINVAL}] il file descriptor \param{fd} non è stato ottenuto + con \func{signalfd} o il valore di \param{flags} non è valido. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per creare un nuovo file + descriptor di \func{signalfd}. + \item[\errcode{ENODEV}] il kernel non può montare internamente il + dispositivo per la gestione anonima degli inode associati al file + descriptor. + \end{errlist} + ed inoltre \errval{EMFILE} e \errval{ENFILE}. +} +\end{prototype} + +La funzione consente di creare o modificare le caratteristiche di un file +descriptor speciale su cui ricevere le notifiche della ricezione di +segnali. Per creare ex-novo uno di questi file descriptor è necessario passare +$-1$ come valore per l'argomento \param{fd}, ogni altro valore positivo verrà +invece interpretato come il numero del file descriptor (che deve esser stato +precedentemente creato sempre con \func{signalfd}) di cui si vogliono +modificare le caratteristiche. Nel primo caso la funzione ritornerà il valore +del nuovo file descriptor e nel secondo caso il valore indicato +con \param{fd}, in caso di errore invece verrà restituito $-1$. + +L'elenco dei segnali che si vogliono gestire con \func{signalfd} deve essere +specificato tramite l'argomento \param{mask}. Questo deve essere passato come +puntatore ad una \index{maschera~dei~segnali} maschera di segnali creata con +l'uso delle apposite macro già illustrate in sez.~\ref{sec:sig_sigset}. La +maschera deve indicare su quali segnali si intende operare con +\func{signalfd}; l'elenco può essere modificato con una successiva chiamata a +\func{signalfd}. Dato che \signal{SIGKILL} e \signal{SIGSTOP} non possono +essere intercettati (e non prevedono neanche la possibilità di un gestore) un +loro inserimento nella maschera verrà ignorato senza generare errori. + +L'argomento \param{flags} consente di impostare direttamente in fase di +creazione due flag per il file descriptor analoghi a quelli che si possono +impostare con una creazione ordinaria con \func{open}, evitando una +impostazione successiva con \func{fcntl}.\footnote{questo è un argomento + aggiuntivo, introdotto con la versione fornita a partire dal kernel 2.6.27, + per kernel precedenti il valore deve essere nullo.} L'argomento deve essere +specificato come maschera binaria dei valori riportati in +tab.~\ref{tab:signalfd_flags}. \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{SFD\_NONBLOCK}& imposta sul file descriptor il flag di + \const{O\_NONBLOCK} per renderlo non bloccante.\\ + \const{SFD\_CLOEXEC}& imposta il flag di \const{O\_CLOEXEC} per la + chiusura automatica del file descriptor nella + esecuzione di \func{exec}.\\ \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{Valori dell'argomento \param{flags} per la funzione \func{signalfd} + che consentono di impostare i flag del file descriptor.} + \label{tab:signalfd_flags} \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}.} - -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}. +Si tenga presente che la chiamata a \func{signalfd} non disabilita la gestione +ordinaria dei segnali indicati da \param{mask}; questa, se si vuole effettuare +la ricezione tramite il file descriptor, dovrà essere disabilitata +esplicitamente bloccando gli stessi segnali con \func{sigprocmask}, altrimenti +verranno comunque eseguite le azioni di default (o un eventuale gestore +installato in precedenza).\footnote{il blocco non ha invece nessun effetto sul + file descriptor restituito da \func{signalfd}, dal quale sarà possibile + pertanto ricevere qualunque segnale, anche se questo risultasse bloccato.} +Si tenga presente inoltre che la lettura di una struttura +\struct{signalfd\_siginfo} relativa ad un segnale pendente è equivalente alla +esecuzione di un gestore, vale a dire che una volta letta il segnale non sarà +più pendente e non potrà essere ricevuto, qualora si ripristino le normali +condizioni di gestione, né da un gestore né dalla funzione \func{sigwaitinfo}. + +Come anticipato, essendo questo lo scopo principale della nuova interfaccia, +il file descriptor può essere tenuto sotto osservazione tramite le funzioni +dell'\textit{I/O multiplexing} (vale a dire con le solite \func{select}, +\func{poll} e \funcd{epoll\_wait}), e risulterà accessibile in lettura quando +uno o più dei segnali indicati tramite \param{mask} sarà pendente. + +La funzione può essere chiamata più volte dallo stesso processo, consentendo +così di tenere sotto osservazione segnali diversi tramite file descriptor +diversi. Inoltre è anche possibile tenere sotto osservazione lo stesso segnale +con più file descriptor, anche se la pratica è sconsigliata; in tal caso la +ricezione del segnale potrà essere effettuata con una lettura da uno qualunque +dei file descriptor a cui è associato, ma questa potrà essere eseguita +soltanto una volta.\footnote{questo significa che tutti i file descriptor su + cui è presente lo stesso segnale risulteranno pronti in lettura per le + funzioni di \textit{I/O multiplexing}, ma una volta eseguita la lettura su + uno di essi il segnale sarà considerato ricevuto ed i relativi dati non + saranno più disponibili sugli altri file descriptor, che (a meno di una + ulteriore occorrenza del segnale nel frattempo) di non saranno più pronti.} + +Quando il file descriptor per la ricezione dei segnali non serve più potrà +essere chiuso con \func{close} liberando tutte le risorse da esso allocate. In +tal caso qualora vi fossero segnali pendenti questi resteranno tali, e +potranno essere ricevuti normalmente una volta che si rimuova il blocco +imposto con \func{sigprocmask}. + +Oltre che con le funzioni dell'\textit{I/O multiplexing} l'uso del file +descriptor restituito da \func{signalfd} cerca di seguire la semantica di un +sistema unix-like anche con altre \textit{system call}; in particolare esso +resta aperto (come ogni altro file descriptor) attraverso una chiamata ad +\func{exec}, a meno che non lo si sia creato con il flag di +\const{SFD\_CLOEXEC} o si sia successivamente impostato il +\textit{close-on-exec} con \func{fcntl}. Questo comportamento corrisponde +anche alla ordinaria semantica relativa ai segnali bloccati, che restano +pendenti attraverso una \func{exec}. + +Analogamente il file descriptor resta sempre disponibile attraverso una +\func{fork} per il processo figlio, che ne riceve una copia; in tal caso però +il figlio potrà leggere dallo stesso soltanto i dati relativi ai segnali +ricevuti da lui stesso. Nel caso di \textit{thread} viene nuovamente seguita +la semantica ordinaria dei segnali, che prevede che un singolo \textit{thread} +possa ricevere dal file descriptor solo le notifiche di segnali inviati +direttamente a lui o al processo in generale, e non quelli relativi ad altri +\textit{thread} appartenenti allo stesso processo. + +L'interfaccia fornita da \func{signalfd} prevede che la ricezione dei segnali +sia eseguita leggendo i dati relativi ai segnali pendenti dal file descriptor +restituito dalla funzione con una normalissima \func{read}. Qualora non vi +siano segnali pendenti la \func{read} si bloccherà a meno di non aver +impostato la modalità di I/O non bloccante sul file descriptor, o direttamente +in fase di creazione con il flag \const{SFD\_NONBLOCK}, o in un momento +successivo con \func{fcntl}. -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}. +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\textwidth} + \includestruct{listati/signalfd_siginfo.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{signalfd\_siginfo}, restituita in lettura da + un file descriptor creato con \func{signalfd}.} + \label{fig:signalfd_siginfo} +\end{figure} -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}. +I dati letti dal file descriptor vengono scritti sul buffer indicato come +secondo argomento di \func{read} nella forma di una sequenza di una o più +strutture \struct{signalfd\_siginfo} (la cui definizione si è riportata in +fig.~\ref{fig:signalfd_siginfo}) a seconda sia della dimensione del buffer che +del numero di segnali pendenti. Per questo motivo il buffer deve essere almeno +di dimensione pari a quella di \struct{signalfd\_siginfo}, qualora sia di +dimensione maggiore potranno essere letti in unica soluzione i dati relativi +ad eventuali più segnali pendenti, fino al numero massimo di strutture +\struct{signalfd\_siginfo} che possono rientrare nel buffer. + +Il contenuto di \struct{signalfd\_siginfo} ricalca da vicino quella della +analoga struttura \struct{siginfo\_t} (illustrata in +fig.~\ref{fig:sig_siginfo_t}) usata dall'interfaccia ordinaria dei segnali, e +restituisce dati simili. Come per \struct{siginfo\_t} i campi che vengono +avvalorati dipendono dal tipo di segnale e ricalcano i valori che abbiamo già +illustrato in sez.~\ref{sec:sig_sigaction}.\footnote{si tenga presente però + che per un bug i kernel fino al 2.6.25 non avvalorano correttamente i campi + \var{ssi\_ptr} e \var{ssi\_int} per segnali inviati con \func{sigqueue}.} + +Come esempio di questa nuova interfaccia ed anche come esempio di applicazione +della interfaccia di \itindex{epoll} \textit{epoll}, si è scritto un programma +elementare che stampi sullo standard output sia quanto viene scritto da terzi +su una \textit{named fifo}, che l'avvenuta ricezione di alcuni segnali. Il +codice completo si trova al solito nei sorgenti allegati alla guida (nel file +\texttt{FifoReporter.c}). + +In fig.~\ref{fig:fiforeporter_code_init} si è riportata la parte iniziale del +programma in cui vengono effettuate le varie inizializzazioni necessarie per +l'uso di \itindex{epoll} \textit{epoll} e \func{signalfd}, a partire +(\texttt{\small 12--16}) dalla definizione delle varie variabili e strutture +necessarie. Al solito si è tralasciata la parte dedicata alla decodifica delle +opzioni che consentono ad esempio di cambiare il nome del file associato alla +fifo. \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} + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/FifoReporter-init.c} + \end{minipage} + \normalsize + \caption{Sezione di inizializzazione del codice del programma + \file{FifoReporter.c}.} + \label{fig:fiforeporter_code_init} \end{figure} -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). +Il primo passo (\texttt{\small 19--20}) è la crezione di un file descriptor +\texttt{epfd} di \itindex{epoll} \textit{epoll} con \func{epoll\_create} che è +quello che useremo per il controllo degli altri. É poi necessario +disabilitare la ricezione dei segnali (nel caso \signal{SIGINT}, +\signal{SIGQUIT} e \signal{SIGTERM}) per i quali si vuole la notifica tramite +file descriptor. Per questo prima li si inseriscono (\texttt{\small 22--25}) +in una \index{maschera~dei~segnali} maschera di segnali \texttt{sigmask} che +useremo con (\texttt{\small 26}) \func{sigprocmask} per disabilitarli. Con la +stessa maschera si potrà per passare all'uso (\texttt{\small 28--29}) di +\func{signalfd} per abilitare la notifica sul file descriptor +\var{sigfd}. Questo poi (\texttt{\small 30--33}) dovrà essere aggiunto con +\func{epoll\_ctl} all'elenco di file descriptor controllati con \texttt{epfd}. + +Occorrerà infine (\texttt{\small 35--38}) creare la \textit{named fifo} se +questa non esiste ed aprirla per la lettura (\texttt{\small 39--40}); una +volta fatto questo sarà necessario aggiungere il relativo file descriptor +(\var{fifofd}) a quelli osservati da \itindex{epoll} \textit{epoll} in maniera +del tutto analoga a quanto fatto con quello relativo alla notifica dei +segnali. -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}). +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/FifoReporter-main.c} + \end{minipage} + \normalsize + \caption{Ciclo principale del codice del programma \file{FifoReporter.c}.} + \label{fig:fiforeporter_code_body} +\end{figure} -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. +Una volta completata l'inizializzazione verrà eseguito indefinitamente il +ciclo principale del programma (\texttt{\small 2--45}) che si è riportato in +fig.~\ref{fig:fiforeporter_code_body}, fintanto che questo non riceva un +segnale di \signal{SIGINT} (ad esempio con la pressione di \texttt{C-c}). Il +ciclo prevede che si attenda (\texttt{\small 2--3}) la presenza di un file +descriptor pronto in lettura con \func{epoll\_wait},\footnote{si ricordi che + entrambi i file descriptor \var{fifofd} e \var{sigfd} sono stati posti in + osservazioni per eventi di tipo \const{EPOLLIN}.} che si bloccherà fintanto +che non siano stati scritti dati sulla fifo o che non sia arrivato un +segnale.\footnote{per semplificare il codice non si è trattato il caso in cui + \func{epoll\_wait} viene interrotta da un segnale, assumendo che tutti + quelli che possano interessare siano stati predisposti per la notifica + tramite file descriptor, per gli altri si otterrà semplicemente l'uscita dal + programma.} + +Anche se in questo caso i file descriptor pronti possono essere al più due, si +è comunque adottato un approccio generico in cui questi verranno letti +all'interno di un opportuno ciclo (\texttt{\small 5--44}) sul numero +restituito da \func{epoll\_wait}, esaminando i risultati presenti nel vettore +\var{events} all'interno di una catena di condizionali alternativi sul valore +del file descriptor riconosciuto come pronto.\footnote{controllando cioè a + quale dei due file descriptor possibili corrisponde il campo relativo, + \var{events[i].data.fd}.} + +Il primo condizionale (\texttt{\small 6--24}) è relativo al caso che si sia +ricevuto un segnale e che il file descriptor pronto corrisponda +(\texttt{\small 6}) a \var{sigfd}. Dato che in generale si possono ricevere +anche notifiche relativi a più di un singolo segnale, si è scelto di leggere +una struttura \struct{signalfd\_siginfo} alla volta, eseguendo la lettura +all'interno di un ciclo (\texttt{\small 8--24}) che prosegue fintanto che vi +siano dati da leggere. + +Per questo ad ogni lettura si esamina (\texttt{\small 9--14}) se il valore di +ritorno della funzione \func{read} è negativo, uscendo dal programma +(\texttt{\small 11}) in caso di errore reale, o terminando il ciclo +(\texttt{\small 13}) con un \texttt{break} qualora si ottenga un errore di +\errcode{EAGAIN} per via dell'esaurimento dei dati.\footnote{si ricordi come + sia la fifo che il file descriptor per i segnali siano stati aperti in + modalità non-bloccante, come previsto per l’\textit{I/O multiplexing}, + pertanto ci si aspetta di ricevere un errore di \errcode{EAGAIN} quando non + vi saranno più dati da leggere.} + +In presenza di dati invece il programma proseguirà l'esecuzione stampando +(\texttt{\small 19--20}) il nome del segnale ottenuto all'interno della +struttura \struct{signalfd\_siginfo} letta in \var{siginf}\footnote{per la + stampa si è usato il vettore \var{sig\_names} a ciascun elemento del quale + corrisponde il nome del segnale avente il numero corrispondente, la cui + definizione si è omessa dal codice di fig.~\ref{fig:fiforeporter_code_init} + per brevità.} ed il \textit{pid} del processo da cui lo ha ricevuto; inoltre +(\texttt{\small 21--24}) si controllerà anche se il segnale ricevuto è +\signal{SIGINT}, che si è preso come segnale da utilizzare per la terminazione +del programma, che verrà eseguita dopo aver rimosso il file della \textit{name + fifo}. + +Il secondo condizionale (\texttt{\small 26--39}) è invece relativo al caso in +cui ci siano dati pronti in lettura sulla fifo e che il file descriptor pronto +corrisponda (\texttt{\small 26}) a \var{fifofd}. Di nuovo si effettueranno le +letture in un ciclo (\texttt{\small 28--39}) ripetendole fin tanto che la +funzione \func{read} non resituisce un errore di \errcode{EAGAIN} +(\texttt{\small 29--35}).\footnote{il procedimento è lo stesso adottato per il + file descriptor associato al segnale, in cui si esce dal programma in caso + di errore reale, in questo caso però alla fine dei dati prima di uscire si + stampa anche (\texttt{\small 32}) un messaggio di chiusura.} Se invece vi +sono dati validi letti dalla fifo si inserirà (\texttt{\small 36}) una +terminazione di stringa sul buffer e si stamperà il tutto (\texttt{\small + 37--38}) sullo \textit{standard output}. L'ultimo condizionale +(\texttt{\small 40--44}) è semplicemente una condizione di cattura per una +eventualità che comunque non dovrebbe mai verificarsi, e che porta alla uscita +dal programma con una opportuna segnalazione di errore. + +A questo punto si potrà eseguire il comando lanciandolo su un terminale, ed +osservarne le reazioni agli eventi generati da un altro terminale; lanciando +il programma otterremo qualcosa del tipo: +\begin{Verbatim} +piccardi@hain:~/gapil/sources$ ./a.out +FifoReporter starting, pid 4568 +\end{Verbatim} +%$ +e scrivendo qualcosa sull'altro terminale con: +\begin{Verbatim} +root@hain:~# echo prova > /tmp/reporter.fifo +\end{Verbatim} +si otterrà: +\begin{Verbatim} +Message from fifo: +prova +end message +\end{Verbatim} +mentre inviando un segnale: +\begin{Verbatim} +root@hain:~# kill 4568 +\end{Verbatim} +si avrà: +\begin{Verbatim} +Signal received: +Got SIGTERM +From pid 3361 +\end{Verbatim} +ed infine premendo \texttt{C-\bslash} sul terminale in cui è in esecuzione si +vedrà: +\begin{Verbatim} +^\Signal received: +Got SIGQUIT +From pid 0 +\end{Verbatim} +e si potrà far uscire il programma con \texttt{C-c} ottenendo: +\begin{Verbatim} +^CSignal received: +Got SIGINT +From pid 0 +SIGINT means exit +\end{Verbatim} + + +Lo stesso paradigma di notifica tramite file descriptor usato per i segnali è +stato adottato anche per i timer. In questo caso, rispetto a quanto visto in +sez.~\ref{sec:sig_timer_adv}, la scadenza di un timer potrà essere letta da un +file descriptor senza dover ricorrere ad altri meccanismi di notifica come un +segnale o un \textit{thread}. Di nuovo questo ha il vantaggio di poter +utilizzare le funzioni dell'\textit{I/O multiplexing} per attendere allo +stesso tempo la disponibilità di dati o la ricezione della scadenza di un +timer.\footnote{in realtà per questo sarebbe già sufficiente \func{signalfd} + per ricevere i segnali associati ai timer, ma la nuova interfaccia + semplifica notevolmente la gestione e consente di fare tutto con una sola + \textit{system call}.} + +Le funzioni di questa nuova interfaccia ricalcano da vicino la struttura delle +analoghe versioni ordinarie introdotte con lo standard POSIX.1-2001, che +abbiamo già illustrato in sez.~\ref{sec:sig_timer_adv}.\footnote{questa + interfaccia è stata introdotta in forma considerata difettosa con il kernel + 2.6.22, per cui è stata immediatamente tolta nel successivo 2.6.23 e + reintrodotta in una forma considerata adeguata nel kernel 2.6.25, il + supporto nelle \acr{glibc} è stato introdotto a partire dalla versione + 2.8.6, la versione del kernel 2.6.22, presente solo su questo kernel, non è + supportata e non deve essere usata.} La prima funzione prevista, quella che +consente di creare un timer, è \funcd{timerfd\_create}, il cui prototipo è: +\begin{prototype}{sys/timerfd.h} + {int timerfd\_create(int clockid, int flags)} + + Crea un timer associato ad un file descriptor per la notifica. + + \bodydesc{La funzione restituisce un numero di 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}] l'argomento \param{clockid} non è + \const{CLOCK\_MONOTONIC} o \const{CLOCK\_REALTIME}, o + l'argomento \param{flag} non è valido, o è diverso da zero per kernel + precedenti il 2.6.27. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per creare un nuovo file + descriptor di \func{signalfd}. + \item[\errcode{ENODEV}] il kernel non può montare internamente il + dispositivo per la gestione anonima degli inode associati al file + descriptor. + \end{errlist} + ed inoltre \errval{EMFILE} e \errval{ENFILE}. +} +\end{prototype} -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 prende come primo argomento un intero che indica il tipo di +orologio a cui il timer deve fare riferimento, i valori sono gli stessi delle +funzioni dello standard POSIX-1.2001 già illustrati in +tab.~\ref{tab:sig_timer_clockid_types}, ma al momento i soli utilizzabili sono +\const{CLOCK\_REALTIME} e \const{CLOCK\_MONOTONIC}. L'argomento \param{flags}, +come l'analogo di \func{signalfd}, consente di impostare i flag per l'I/O non +bloccante ed il \textit{close-on-exec} sul file descriptor +restituito,\footnote{esso è stato introdotto a partire dal kernel 2.6.27, per + le versioni precedenti deve essere passato un valore nullo.} e deve essere +specificato come una maschera binaria delle costanti riportate in +tab.~\ref{tab:timerfd_flags}. -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. +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{8cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{TFD\_NONBLOCK}& imposta sul file descriptor il flag di + \const{O\_NONBLOCK} per renderlo non bloccante.\\ + \const{TFD\_CLOEXEC}& imposta il flag di \const{O\_CLOEXEC} per la + chiusura automatica del file descriptor nella + esecuzione di \func{exec}.\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{flags} per la funzione + \func{timerfd\_create} che consentono di impostare i flag del file + descriptor.} + \label{tab:timerfd_flags} +\end{table} -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. +In caso di successo la funzione restituisce un file descriptor sul quale +verranno notificate le scadenze dei timer. Come per quelli restituiti da +\func{signalfd} anche questo file descriptor segue la semantica dei sistemi +unix-like, in particolare resta aperto attraverso una \func{exec},\footnote{a + meno che non si sia impostato il flag di \textit{close-on exec} con + \const{TFD\_CLOEXEC}.} e viene duplicato attraverso una \func{fork}; questa +ultima caratteristica comporta però che anche il figlio può utilizzare i dati +di un timer creato nel padre, a differenza di quanto avviene invece con i +timer impostati con le funzioni ordinarie.\footnote{si ricordi infatti che, + come illustrato in sez.~\ref{sec:proc_fork}, allarmi, timer e segnali + pendenti nel padre vengono cancellati per il figlio dopo una \func{fork}.} + +Una volta creato il timer con \func{timerfd\_create} per poterlo utilizzare +occorre \textsl{armarlo} impostandone un tempo di scadenza ed una eventuale +periodicità di ripetizione, per farlo si usa la funzione omologa di +\func{timer\_settime} per la nuova interfaccia; questa è +\funcd{timerfd\_settime} ed il suo prototipo è: +\begin{prototype}{sys/timerfd.h} + {int timerfd\_settime(int fd, int flags, + const struct itimerspec *new\_value, + struct itimerspec *old\_value)} + + Crea un timer associato ad un file descriptor per la notifica. + + \bodydesc{La funzione restituisce un numero di 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{EBADF}] l'argomento \param{fd} non corrisponde ad un file + descriptor. + \item[\errcode{EINVAL}] il file descriptor \param{fd} non è stato ottenuto + con \func{timerfd\_create}, o i valori di \param{flag} o dei campi + \var{tv\_nsec} in \param{new\_value} non sono validi. + \item[\errcode{EFAULT}] o \param{new\_value} o \param{old\_value} non sono + puntatori validi. + \end{errlist} +} +\end{prototype} -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} +In questo caso occorre indicare su quale timer si intende operare specificando +come primo argomento il file descriptor ad esso associato, che deve essere +stato ottenuto da una precedente chiamata a \func{timerfd\_create}. I restanti +argomenti sono del tutto analoghi a quelli della omologa funzione +\func{timer\_settime}, e prevedono l'uso di strutture \struct{itimerspec} +(vedi fig.~\ref{fig:struct_itimerspec}) per le indicazioni di temporizzazione. + +I valori ed il significato di questi argomenti sono gli stessi che sono già +stati illustrati in dettaglio in sez.~\ref{sec:sig_timer_adv} e non staremo a +ripetere quanto detto in quell'occasione;\footnote{per brevità si ricordi che + con \param{new\_value.it\_value} si indica la prima scadenza del timer e + con \param{new\_value.it\_interval} la sua periodicità.} l'unica differenza +riguarda l'argomento \param{flags} che serve sempre ad indicare se il tempo di +scadenza del timer è da considerarsi relativo o assoluto rispetto al valore +corrente dell'orologio associato al timer, ma che in questo caso ha come +valori possibili rispettivamente soltanto $0$ e +\const{TFD\_TIMER\_ABSTIME}.\footnote{anche questo valore, che è l'analogo di + \const{TIMER\_ABSTIME} è l'unico attualmente possibile per \param{flags}.} + +L'ultima funzione prevista dalla nuova interfaccia è \funcd{timerfd\_gettime}, +che è l'analoga di \func{timer\_gettime}, il suo prototipo è: +\begin{prototype}{sys/timerfd.h} + {int timerfd\_gettime(int fd, struct itimerspec *curr\_value)} + + Crea un timer associato ad un file descriptor per la notifica. + + \bodydesc{La funzione restituisce un numero di 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{EBADF}] l'argomento \param{fd} non corrisponde ad un file + descriptor. + \item[\errcode{EINVAL}] il file descriptor \param{fd} non è stato ottenuto + con \func{timerfd\_create}. + \item[\errcode{EFAULT}] o \param{curr\_value} non è un puntatore valido. + \end{errlist} +} +\end{prototype} -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. -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}. -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|)} +Questo infatti diverrà pronto in lettura per tutte le varie funzioni dell'I/O +multiplexing in presenza di una o più scadenze del timer ad esso associato. -\subsection{L'interfaccia POSIX per l'I/O asincrono} -\label{sec:file_asyncronous_io} +Inoltre sarà possibile ottenere il numero di volte che il timer è scaduto +dalla ultima impostazione -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. +che può essere +usato per leggere le notifiche delle scadenze dei timer. Queste possono essere +ottenute leggendo in maniera ordinaria il file descriptor con una \func{read}, -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. -In generale questa interfaccia è completamente astratta e può essere -implementata sia direttamente nel kernel, che in user space attraverso l'uso -di 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. -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. -\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} +% TODO trattare qui eventfd, timerfd introdotte con il 2.6.22 +% timerfd è stata tolta nel 2.6.23 e rifatta per bene nel 2.6.25 +% vedi: http://lwn.net/Articles/233462/ +% http://lwn.net/Articles/245533/ +% http://lwn.net/Articles/267331/ -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. -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. +\section{L'accesso \textsl{asincrono} ai file} +\label{sec:file_asyncronous_operation} -\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} +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. -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 - 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} -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} +\subsection{Il \textit{Signal driven I/O}} +\label{sec:signal_driven_io} - \funcdecl{int aio\_read(struct aiocb *aiocbp)} - Richiede una lettura asincrona secondo quanto specificato con \param{aiocbp}. +\itindbeg{signal~driven~I/O} - \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} +Abbiamo accennato in sez.~\ref{sec:file_open_close} che è definito un flag +\const{O\_ASYNC}, che consentirebbe di aprire un file in modalità asincrona, +anche se in realtà è opportuno 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_ioctl}).\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.} 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 è che per tutti i file posti in questa modalità\footnote{si + tenga presente però che essa non è utilizzabile con i file ordinari ma solo + con socket, file di terminale o pseudo terminale, ed anche, a partire dal + kernel 2.6, anche per fifo e pipe.} il sistema genera un apposito segnale, +\signal{SIGIO}, tutte le volte che diventa possibile leggere o scrivere dal +file descriptor che si è posto in questa modalità. Inoltre è possibile, come +illustrato in sez.~\ref{sec:file_fcntl_ioctl}, selezionare con il comando +\const{F\_SETOWN} di \func{fcntl} quale processo o quale gruppo di processi +dovrà ricevere il segnale. In questo modo diventa possibile effettuare le +operazioni di I/O in risposta alla ricezione del segnale, e non ci sarà più la +necessità di restare bloccati in attesa della disponibilità di accesso ai +file. + +% TODO: per i thread l'uso di F_SETOWN ha un significato diverso + +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. + +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. -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}. +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}). -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}. +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 è \signal{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. + +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. -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)} +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 \signal{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 + \sysctlfile{kernel/rtsig-max} allo stesso valore del contenuto di + \sysctlfile{fs/file-max}.} - 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} +% TODO fare esempio che usa O_ASYNC -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}. +\itindend{signal~driven~I/O} -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} -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. +\subsection{I meccanismi di notifica asincrona.} +\label{sec:file_asyncronous_lease} -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. +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 + \signal{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. -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)} +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. -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} - -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}). - -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. +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}. -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)} +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_ioctl}), che divengono disponibili soltanto se si è +definita la macro \macro{\_GNU\_SOURCE} prima di includere \headfile{fcntl.h}. -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} +\itindbeg{file~lease} -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} +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 \signal{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 \signal{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}. -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. +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 in lettura; in quest'ultimo caso però il \textit{lease} +può essere ottenuto solo se nessun altro processo ha aperto lo stesso file. + +Come accennato in sez.~\ref{sec:file_fcntl_ioctl} 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}. -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} +\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} -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. +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}. -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} +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 +\ids{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 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. +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. -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}. +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}. +Se il \textit{lease holder} non provvede a rilasciare il \textit{lease} entro +il numero di secondi specificato dal parametro di sistema mantenuto in +\sysctlfile{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. -\section{Altre modalità di I/O avanzato} -\label{sec:file_advanced_io} +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,\footnote{questa funzionalità venne aggiunta + principalmente ad uso di Samba per poter facilitare l'emulazione del + comportamento di Windows sui file, ma ad oggi viene considerata una + interfaccia mal progettata ed il suo uso è fortemente sconsigliato a favore + di \textit{inotify}.} che è quanto necessario ad esempio ai programma di +gestione dei file dei vari desktop grafici. -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. +\itindbeg{dnotify} +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 \signal{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}. -\subsection{File mappati in memoria} -\label{sec:file_memory_map} +\itindend{file~lease} -\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] +\begin{table}[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} + \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} -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}. +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}. -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. +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. -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. +\itindbeg{inotify} -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. +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. -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} +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 ed il suo uso oggi è fortemente sconsigliato. - \funcdecl{void * mmap(void * start, size\_t length, int prot, int flags, int - fd, off\_t offset)} +\itindend{dnotify} + +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)} - Esegue la mappatura in memoria della sezione specificata del file \param{fd}. + Inizializza una istanza di \textit{inotify}. - \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} + \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} + +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 + \sysctlfile{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.} -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. +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:signal_driven_io}.} +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}. + + \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{EACCES}] 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 + \sysctlfile{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|l|} + \begin{tabular}[c]{|l|c|p{10cm}|} \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{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{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 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} -Il valore dell'argomento \param{prot} indica la protezione\footnote{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 - che si chiama una \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}. +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|p{11cm}|} - \hline - \textbf{Valore} & \textbf{Significato} \\ + \begin{tabular}[c]{|l|p{10cm}|} \hline + \textbf{Valore} & \textbf{Significato} \\ \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} 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 + \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{Valori possibili dell'argomento \param{flag} di \func{mmap}.} - \label{tab:file_mmap_flag} + \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} +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. -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 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)} + + 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} + +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]{\textwidth} + \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} + +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_fcntl_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. + +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} -È 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. +\footnotetext{la coda di notifica ha una dimensione massima specificata dal + parametro di sistema \sysctlfile{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[68]{dato che tutti faranno riferimento alle stesse pagine di - memoria.} +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}. -\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}.} +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 \itindsub{pathname}{relativo} \textit{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}. -\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.} +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}. -\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} +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \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} +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). -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. +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}). -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. +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. -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. +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 \textit{system call}, nel qual caso (\texttt{\small 36}) si +ripete la lettura. -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}. +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. -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. +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. -\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} +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} -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. +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. -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}. +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}. -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 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. -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. +\itindend{inotify} -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} +% TODO trattare fanotify, vedi http://lwn.net/Articles/339399/ e +% http://lwn.net/Articles/343346/ (incluso nel 2.6.36) - \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} -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. +\subsection{L'interfaccia POSIX per l'I/O asincrono} +\label{sec:file_asyncronous_io} -\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} +% vedere anche http://davmac.org/davpage/linux/async-io.html e +% http://www.ibm.com/developerworks/linux/library/l-async/ -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 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} +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. - \funcdecl{int munmap(void *start, size\_t length)} - - Rilascia la mappatura sulla sezione di memoria specificata. +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} \textit{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. - \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} +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. -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. +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 +\headfile{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. -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]{\textwidth} + \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} - \funcdecl{int mprotect(const void *addr, size\_t len, int prot)} - - Modifica le protezioni delle pagine di memoria comprese nell'intervallo - specificato. +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. - \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} +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. +Infine il campo \var{aio\_sigevent} è una struttura di tipo \struct{sigevent} +(illustrata in in fig.~\ref{fig:struct_sigevent}) che serve a specificare il +modo in cui si vuole che venga effettuata la notifica del completamento delle +operazioni richieste; per la trattazione delle modalità di utilizzo della +stessa si veda quanto già visto in proposito in sez.~\ref{sec:sig_timer_adv}. -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. +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} -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} + \funcdecl{int aio\_read(struct aiocb *aiocbp)} + Richiede una lettura asincrona secondo quanto specificato con \param{aiocbp}. - \funcdecl{void * mremap(void *old\_address, size\_t old\_size , size\_t - new\_size, unsigned long flags)} + \funcdecl{int aio\_write(struct aiocb *aiocbp)} + Richiede una scrittura asincrona secondo quanto specificato con + \param{aiocbp}. - Restringe o allarga una mappatura in memoria di un file. - - \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} - } + \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} -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}. - -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. - -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. - -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}. +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_close}), nel qual caso le scritture vengono +effettuate comunque alla fine de file, nell'ordine delle chiamate a +\func{aio\_write}. -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}. +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} +\index{variabili!automatiche} 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}. -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} +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)} - \funcdecl{int remap\_file\_pages(void *start, size\_t size, int prot, - ssize\_t pgoff, int flags)} + Determina lo stato di errore delle operazioni di I/O associate a + \param{aiocbp}. - Permette di rimappare non linearmente un precedente \textit{memory mapping}. + \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} - \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} +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 \textit{system call} \func{read}, +\func{write} e \func{fsync}. -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. +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)} -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. +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} -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}. +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. -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}. +La funzione restituisce il valore di ritorno relativo all'operazione eseguita, +così come ricavato dalla sottostante \textit{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. -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}. +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)} -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.} +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} -\itindend{memory~mapping} +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}). -\subsection{I/O vettorizzato: \func{readv} e \func{writev}} -\label{sec:file_multiple_io} +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. -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. +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)} -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} +Richiede la cancellazione delle operazioni sul file \param{fildes} specificate +da \param{aiocbp}. - \funcdecl{int readv(int fd, const struct iovec *vector, int count)} - \funcdecl{int writev(int fd, const struct iovec *vector, int count)} +\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} - Eseguono rispettivamente una lettura o una scrittura vettorizzata. +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 +\headfile{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, - \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} - -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. +\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} -\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} +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. -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}. +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 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}). +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. -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. +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} -% TODO verificare cosa succederà a preadv e pwritev o alla nuova niovec -% vedi http://lwn.net/Articles/164887/ +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}. -\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. +\section{Altre modalità di I/O avanzato} +\label{sec:file_advanced_io} -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. +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_unix_interface}. 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. -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. +\subsection{File mappati in memoria} +\label{sec:file_memory_map} - \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} +\itindbeg{memory~mapping} +Una modalità alternativa di I/O, che usa una interfaccia completamente diversa +rispetto a quella classica vista in sez.~\ref{sec: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. -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{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} -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}. +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}. -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. +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. -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}. +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. -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}''. - -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} +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. - \funcdecl{long splice(int fd\_in, off\_t *off\_in, int fd\_out, off\_t - *off\_out, size\_t len, unsigned int flags)} +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} - Trasferisce dati da un file verso una pipe o viceversa. + \headdecl{unistd.h} + \headdecl{sys/mman.h} - \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: + \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}] 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{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} -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 richiede di mappare in memoria la sezione del file \param{fd} a +partire da \param{offset} per \param{length} byte, preferibilmente +all'indirizzo \param{start}. Il valore di \param{offset} deve essere un +multiplo della dimensione di una pagina di memoria. -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). +\begin{table}[htb] + \centering + \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} -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. +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 + \signal{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. -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}. +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 \signal{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 2Gb 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.\\ +% \const{MAP\_HUGETLB}& da trattare.\\ +% TODO trattare MAP_HUGETLB introdotto con il kernel 2.6.32, e modifiche +% introdotte con il 3.8 per le dimensioni variabili delle huge pages -\begin{table}[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} + \caption{Valori possibili dell'argomento \param{flag} di \func{mmap}.} + \label{tab:file_mmap_flag} \end{table} -\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[68]{dato che tutti faranno riferimento alle stesse pagine di + memoria.} -\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}.} +\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}.} -\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.} +\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 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}. +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 (\signal{SIGSEGV}), +dato che i permessi sul segmento di memoria relativo non consentono questo +tipo di accesso. -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}. +È 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 +\index{paginazione} 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. -\begin{figure}[htb] +\begin{figure}[!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} + \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} -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). - -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 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. -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. +In questo caso è possibile accedere a quella zona di memoria che eccede le +dimensioni specificate da \param{length}, senza ottenere un \signal{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. -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. +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. -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. +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à \signal{SIGSEGV}, ma +\signal{SIGBUS}, come illustrato in fig.~\ref{fig:file_mmap_exceed}. -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. +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. -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. +\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 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} +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. - \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}. +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}. - \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} +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 dei file di sez.~\ref{sec: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. -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}. +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. -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: +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{fcntl.h} - - \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. - - \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 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}). - -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}[!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} -\end{figure} + \headdecl{unistd.h} + \headdecl{sys/mman.h} -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}. + \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} -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}). +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. -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}). -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. +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{11cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{MS\_SYNC} & richiede una sincronizzazione e ritorna soltanto + quando questa è stata completata.\\ + \const{MS\_ASYNC} & richiede una sincronizzazione, ma ritorna subito + non attendendo che questa sia finita.\\ + \const{MS\_INVALIDATE} & invalida le pagine per tutte le mappature + in memoria così da rendere necessaria una + rilettura immediata delle stesse.\\ + \hline + \end{tabular} + \caption{Valori possibili dell'argomento \param{flag} di \func{msync}.} + \label{tab:file_mmap_msync} +\end{table} +L'argomento \param{flag} è specificato come maschera binaria composta da un OR +dei valori riportati in tab.~\ref{tab:file_mmap_msync}, 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 sì che vengano invalidate, per tutte le mappature dello stesso file, +le pagine di cui si è richiesta la sincronizzazione, così che esse possano +essere immediatamente aggiornate con i nuovi valori. +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} -\subsection{Gestione avanzata dell'accesso ai dati dei file} -\label{sec:file_fadvise} + \funcdecl{int munmap(void *start, size\_t length)} + + Rilascia la mappatura sulla sezione di memoria specificata. -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. + \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} -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}. +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. -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} +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} - \funcdecl{ssize\_t readahead(int fd, off64\_t *offset, size\_t count)} + \funcdecl{int mprotect(const void *addr, size\_t len, int prot)} - Esegue una lettura preventiva del contenuto di un file in cache. + Modifica le protezioni delle pagine di memoria comprese nell'intervallo + specificato. - \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}] il valore di \param{addr} non è valido o non è un + multiplo di \const{PAGE\_SIZE}. + \item[\errcode{EACCES}] 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 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. - -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. -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, è: +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 \headfile{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. - -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}. +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. -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.} +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}. -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, è: +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}. + +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 \textit{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. +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}. + +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}. + +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.} + +Per i vantaggi illustrati all'inizio del paragrafo l'interfaccia del +\textit{memory mapped I/O} viene usata da una grande varietà di programmi, +spesso con esigenze molto diverse fra di loro riguardo le modalità con cui +verranno eseguiti gli accessi ad un file; è ad esempio molto comune per i +database effettuare accessi ai dati in maniera pressoché casuale, mentre un +riproduttore audio o video eseguirà per lo più letture sequenziali. + +Per migliorare le prestazioni a seconda di queste modalità di accesso è +disponibile una apposita funzione, \funcd{madvise},\footnote{tratteremo in + sez.~\ref{sec:file_fadvise} le funzioni che consentono di ottimizzare + l'accesso ai file con l'interfaccia classica.} che consente di fornire al +kernel delle indicazioni su dette modalità, così che possano essere adottate +le opportune strategie di ottimizzazione. Il suo prototipo è: +\begin{functions} + \headdecl{sys/mman.h} -% 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 + \funcdecl{int madvise(void *start, size\_t length, int advice)} + + Fornisce indicazioni sull'uso previsto di un \textit{memory mapping}. + \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}] la mappatura esiste ma non corrisponde ad un file. + \item[\errcode{EINVAL}] \param{start} non è allineato alla dimensione di + una pagina, \param{length} ha un valore negativo, o \param{advice} non è + un valore valido, o si è richiesto il rilascio (con + \const{MADV\_DONTNEED}) di pagine bloccate o condivise. + \item[\errcode{EIO}] la paginazione richiesta eccederebbe i limiti (vedi + sez.~\ref{sec:sys_resource_limit}) sulle pagine residenti in memoria del + processo (solo in caso di \const{MADV\_WILLNEED}). + \item[\errcode{ENOMEM}] gli indirizzi specificati non sono mappati, o, in + caso \const{MADV\_WILLNEED}, non c'è sufficiente memoria per soddisfare + la richiesta. + \end{errlist} + ed inoltre \errval{EAGAIN} e \errval{ENOSYS}. + } +\end{functions} -%\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 +La sezione di memoria sulla quale si intendono fornire le indicazioni deve +essere indicata con l'indirizzo iniziale \param{start} e l'estensione +\param{length}, il valore di \param{start} deve essere allineato, +mentre \param{length} deve essere un numero positivo.\footnote{la versione di + Linux consente anche un valore nullo per \param{length}, inoltre se una + parte dell'intervallo non è mappato in memoria l'indicazione viene comunque + applicata alle restanti parti, anche se la funzione ritorna un errore di + \errval{ENOMEM}.} L'indicazione viene espressa dall'argomento \param{advice} +che deve essere specificato con uno dei valori\footnote{si tenga presente che + gli ultimi tre valori sono specifici di Linux (introdotti a partire dal + kernel 2.6.16) e non previsti dallo standard POSIX.1b.} riportati in +tab.~\ref{tab:madvise_advice_values}. +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{10 cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{MADV\_NORMAL} & nessuna indicazione specifica, questo è il valore + di default usato quando non si è chiamato + \func{madvise}.\\ + \const{MADV\_RANDOM} & ci si aspetta un accesso casuale all'area + indicata, pertanto l'applicazione di una lettura + anticipata con il meccanismo del + \itindex{read-ahead} \textit{read-ahead} (vedi + sez.~\ref{sec:file_fadvise}) è di + scarsa utilità e verrà disabilitata.\\ + \const{MADV\_SEQUENTIAL}& ci si aspetta un accesso sequenziale al file, + quindi da una parte sarà opportuno eseguire una + lettura anticipata, e dall'altra si potranno + scartare immediatamente le pagine una volta che + queste siano state lette.\\ + \const{MADV\_WILLNEED}& ci si aspetta un accesso nell'immediato futuro, + pertanto l'applicazione del \textit{read-ahead} + deve essere incentivata.\\ + \const{MADV\_DONTNEED}& non ci si aspetta nessun accesso nell'immediato + futuro, pertanto le pagine possono essere + liberate dal kernel non appena necessario; l'area + di memoria resterà accessibile, ma un accesso + richiederà che i dati vengano ricaricati dal file + a cui la mappatura fa riferimento.\\ + \hline + \const{MADV\_REMOVE} & libera un intervallo di pagine di memoria ed il + relativo supporto sottostante; è supportato + soltanto sui filesystem in RAM \textit{tmpfs} e + \textit{shmfs}.\footnotemark\\ + \const{MADV\_DONTFORK}& impedisce che l'intervallo specificato venga + ereditato dal processo figlio dopo una + \func{fork}; questo consente di evitare che il + meccanismo del \itindex{copy~on~write} + \textit{copy on write} effettui la rilocazione + delle pagine quando il padre scrive sull'area + di memoria dopo la \func{fork}, cosa che può + causare problemi per l'hardware che esegue + operazioni in DMA su quelle pagine.\\ + \const{MADV\_DOFORK} & rimuove l'effetto della precedente + \const{MADV\_DONTFORK}.\\ + \const{MADV\_MERGEABLE}& marca la pagina come accorpabile (indicazione + principalmente ad uso dei sistemi di + virtualizzazione).\footnotemark\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{advice} di \func{madvise}.} + \label{tab:madvise_advice_values} +\end{table} +\footnotetext{se usato su altri tipi di filesystem causa un errore di + \errcode{ENOSYS}.} + +\footnotetext{a partire dal kernel 2.6.32 è stato introdotto un meccanismo che + identifica pagine di memoria identiche e le accorpa in una unica pagina + (soggetta al \textit{copy-on-write} per successive modifiche); per evitare + di controllare tutte le pagine solo quelle marcate con questo flag vengono + prese in considerazione per l'accorpamento; in questo modo si possono + migliorare le prestazioni nella gestione delle macchine virtuali diminuendo + la loro occupazione di memoria, ma il meccanismo può essere usato anche in + altre applicazioni in cui sian presenti numerosi processi che usano gli + stessi dati; per maggiori dettagli si veda + \href{http://kernelnewbies.org/Linux_2_6_32\#head-d3f32e41df508090810388a57efce73f52660ccb}{\texttt{http://kernelnewbies.org/Linux\_2\_6\_32}}.} + +La funzione non ha, tranne il caso di \const{MADV\_DONTFORK}, nessun effetto +sul comportamento di un programma, ma può influenzarne le prestazioni fornendo +al kernel indicazioni sulle esigenze dello stesso, così che sia possibile +scegliere le opportune strategie per la gestione del \itindex{read-ahead} +\textit{read-ahead} e del caching dei dati. A differenza da quanto specificato +nello standard POSIX.1b, per il quale l'uso di \func{madvise} è a scopo +puramente indicativo, Linux considera queste richieste come imperative, per +cui ritorna un errore qualora non possa soddisfarle.\footnote{questo + comportamento differisce da quanto specificato nello standard.} +\itindend{memory~mapping} -\section{Il file locking} -\label{sec:file_locking} +\subsection{I/O vettorizzato: \func{readv} e \func{writev}} +\label{sec:file_multiple_io} -\index{file!locking|(} +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 a \func{read} e \func{write}, ci sono casi in cui si vuole poter +contare sulla atomicità delle operazioni. -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. +Per questo motivo fino da BSD 4.2 vennero introdotte delle nuove +\textit{system call} che permettessero di effettuare con una sola chiamata una +serie di letture o scritture su una serie di buffer, con quello che viene +normalmente chiamato \textsl{I/O vettorizzato}. Queste funzioni sono +\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.} ed 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)} -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. + 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 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. +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. +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\textwidth} + \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} +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}. -\subsection{L'\textit{advisory locking}} -\label{sec:file_record_locking} +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 \headfile{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_limits}). + +Nel caso di Linux il limite di sistema è di 1024, però se si usano le +\acr{glibc} queste forniscono un \textit{wrapper} per le \textit{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. + +Si tenga presente infine che queste funzioni operano sui file con +l'interfaccia dei file descriptor, e non è consigliabile mescolarle con +l'interfaccia classica dei \textit{file stream} di +sez.~\ref{sec:files_std_interface}; a causa delle bufferizzazioni interne di +quest'ultima infatti si potrebbero avere risultati indefiniti e non +corrispondenti a quanto aspettato. + +Come per le normali operazioni di lettura e scrittura, anche per l'\textsl{I/O + vettorizzato} si pone il problema di poter effettuare le operazioni in +maniera atomica a partire da un certa posizione sul file. Per questo motivo a +partire dal kernel 2.6.30 sono state introdotte anche per l'\textsl{I/O + vettorizzato} le analoghe delle funzioni \func{pread} e \func{pwrite} (vedi +sez.~\ref{sec:file_read} e \ref{sec:file_write}); le due funzioni sono +\funcd{preadv} e \funcd{pwritev} ed i rispettivi prototipi sono:\footnote{le + due funzioni sono analoghe alle omonime presenti in BSD; le \textit{system + call} usate da Linux (introdotte a partire dalla versione 2.6.30) + utilizzano degli argomenti diversi per problemi collegati al formato a 64 + bit dell'argomento \param{offset}, che varia a seconda delle architetture, + ma queste differenze vengono gestite dalle funzioni di librerie di libreria + che mantengono l'interfaccia delle analoghe tratte da BSD.} +\begin{functions} + \headdecl{sys/uio.h} + + \funcdecl{int preadv(int fd, const struct iovec *vector, int count, off\_t + offset)} + \funcdecl{int pwritev(int fd, const struct iovec *vector, int count, off\_t + offset)} -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. + Eseguono una lettura o una scrittura vettorizzata a partire da una data + posizione sul file. + + \bodydesc{Le funzioni hanno gli stessi valori di ritorno delle + corrispondenti \func{readv} e \func{writev}; anche gli eventuali errori + sono gli stessi già visti in precedenza, ma ad essi si possono aggiungere + per \var{errno} anche i valori: + \begin{errlist} + \item[\errcode{EOVERFLOW}] \param{offset} ha un valore che non può essere + usato come \type{off\_t}. + \item[\errcode{ESPIPE}] \param{fd} è associato ad un socket o una pipe. + \end{errlist} +} +\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. +Le due funzioni eseguono rispettivamente una lettura o una scrittura +vettorizzata a partire dalla posizione \param{offset} sul file indicato +da \param{fd}, la posizione corrente sul file, come vista da eventuali altri +processi che vi facciano riferimento, non viene alterata. A parte la presenza +dell'ulteriore argomento il comportamento delle funzioni è identico alle +precedenti \func{readv} e \func{writev}. -\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} +Con l'uso di queste funzioni si possono evitare eventuali +\itindex{race~condition} \textit{race condition} quando si deve eseguire la +una operazione di lettura e scrittura vettorizzata a partire da una certa +posizione su un file, mentre al contempo si possono avere in concorrenza +processi che utilizzano lo stesso file descriptor (si ricordi quanto visto in +sez.~\ref{sec:file_adv_func}) con delle chiamate a \func{lseek}. -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. -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. -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). +\subsection{L'I/O diretto fra file descriptor: \func{sendfile} e + \func{splice}} +\label{sec:file_sendfile_splice} -%% 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. +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. -\subsection{La funzione \func{flock}} -\label{sec:file_flock} +La prima funzione che è stata ideata per ottimizzare il trasferimento dei dati +fra due file descriptor è \func{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 prototipo +di \funcd{sendfile} è: +\begin{functions} + \headdecl{sys/sendfile.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{ssize\_t sendfile(int out\_fd, int in\_fd, off\_t *offset, size\_t + count)} - \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: + 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{EWOULDBLOCK}] il file ha già un blocco attivo, e si è - specificato \const{LOCK\_NB}. + \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{prototype} - -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}. - -\begin{table}[htb] - \centering - \footnotesize - \begin{tabular}[c]{|l|l|} - \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 - \end{tabular} - \caption{Valori dell'argomento \param{operation} di \func{flock}.} - \label{tab:file_flock_operation} -\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}. - -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. - -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. +\end{functions} -\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} -\end{figure} +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}. -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. +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}. -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. +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. -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à. - +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 \url{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}. -\subsection{Il file locking POSIX} -\label{sec:file_posix_lock} +Nonostante ci possano essere casi in cui \func{sendfile} non migliora le +prestazioni, resta il dubbio se la scelta di disabilitarla sempre per il +trasferimento fra file di dati sia davvero corretta. Se ci sono peggioramenti +di prestazioni infatti si può sempre fare ricorso al metodo ordinario, ma +lasciare a disposizione la funzione consentirebbe se non altro di semplificare +la gestione della copia dei dati fra file, evitando di dover gestire +l'allocazione di un buffer temporaneo per il loro trasferimento. + +Questo dubbio si può comunque ritenere superato con l'introduzione, avvenuta a +partire dal kernel 2.6.17, della nuova \textit{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 \textit{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 \url{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}''. + +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 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}. + \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{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. + \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{lseek}. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione + richiesta. + \item[\errcode{ESPIPE}] o \param{off\_in} o \param{off\_out} non sono + \val{NULL} ma il corrispondente file descriptor è una \textit{pipe}. \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. +\end{functions} -\begin{figure}[!bht] - \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} -\end{figure} +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 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}). +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. -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. +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{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 + \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 possibili per il campo \var{l\_type} di \struct{flock}.} - \label{tab:file_flock_type} + \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} -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. +\footnotetext[120]{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.} -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} +\footnotetext[121]{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}.} -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. +\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.} -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. +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=9cm]{img/file_lock_dead} - \caption{Schema di una situazione di \itindex{deadlock} \textit{deadlock}.} - \label{fig:file_flock_dead} + \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} -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}. +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}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \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} -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. +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). -\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 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}). -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. +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. -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. - -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. +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. -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. +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. -\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} +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. -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). +Come accennato con l'introduzione di \func{splice} sono state realizzate anche +altre due \textit{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, essendo strettamente correlate fra loro. -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}. +La prima funzione, \funcd{vmsplice}, è la più simile a \func{splice} e come +indica il suo nome consente di trasferire i dati dalla memoria virtuale di un +processo (ad esempio per un file mappato in memoria) verso una \textit{pipe}; +il suo prototipo è: +\begin{functions} + \headdecl{fcntl.h} + \headdecl{sys/uio.h} -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. + \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}. -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}). + \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} -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. +La \textit{pipe} indicata da \param{fd} 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 +segmenti della memoria del processo devono essere trasferiti verso di essa si +dovrà utilizzare un vettore di strutture \struct{iovec} (vedi +fig.~\ref{fig:file_iovec}), esattamente con gli stessi criteri con cui le si +usano per l'I/O vettorizzato, indicando gli indirizzi e le dimensioni di +ciascun segmento di memoria su cui si vuole operare; 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}. -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: +In caso di successo la funzione ritorna il numero di byte trasferiti sulla +\textit{pipe}. In generale, se i dati una volta creati non devono essere +riutilizzati (se cioè l'applicazione che chiama \func{vmsplice} non +modificherà più la memoria trasferita), è opportuno utilizzare +per \param{flag} il valore \const{SPLICE\_F\_GIFT}; questo fa sì che il kernel +possa rimuovere le relative pagine dalla cache della memoria virtuale, 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} -\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: + \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. + + \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 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 \param{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}). Al +momento\footnote{quello della stesura di questo paragrafo, avvenuta il Gennaio + 2010, in futuro potrebbe essere implementato anche \const{SPLICE\_F\_MORE}.} +il solo valore utilizzabile per \param{flag}, fra quelli elencati in +tab.~\ref{tab:splice_flag}, è \const{SPLICE\_F\_NONBLOCK} che rende la +funzione non bloccante. + +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}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \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} -\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 \textit{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 \url{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 +\itindbeg{read-ahead} -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: +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} -\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. + \funcdecl{ssize\_t readahead(int fd, off64\_t *offset, size\_t count)} + + Esegue una lettura preventiva del contenuto di un file in cache. + \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} vengono arrotondati di conseguenza. + +La funzione estende quello che è un comportamento normale del kernel che +quando si legge un file, aspettandosi che l'accesso prosegua, esegue sempre +una lettura preventiva di una certa quantità di dati; questo meccanismo di +lettura anticipata viene chiamato \textit{read-ahead}, da cui deriva il nome +della funzione. La funzione \func{readahead}, per ottimizzare gli accessi a +disco, effettua la lettura in cache della sezione richiesta e si blocca +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. + +Si può utilizzare questa funzione per velocizzare le operazioni di lettura +all'interno di un programma tutte le volte che si conosce in anticipo quanti +dati saranno necessari nelle elaborazioni successive. Si potrà così +concentrare in un unico momento (ad esempio in fase di inizializzazione) la +lettura dei dati da disco, così da ottenere una migliore velocità di risposta +nelle operazioni successive. + +\itindend{read-ahead} -\subsection{La funzione \func{lockf}} -\label{sec:file_lockf} +Il concetto di \func{readahead} viene generalizzato nello standard +POSIX.1-2001 dalla funzione \func{posix\_fadvise},\footnote{anche se + l'argomento \param{len} è stato modificato da \type{size\_t} a \type{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 prototipo di \funcd{posix\_fadvise}, che è +disponibile soltanto se è stata definita la macro \macro{\_XOPEN\_SOURCE} ad +valore di almeno 600, è: +\begin{functions} + \headdecl{fcntl.h} -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}. + \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 - errore, nel qual caso \var{errno} assumerà uno dei valori: + 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}, che riprendono il significato +degli analoghi già visti in sez.~\ref{sec:file_memory_map} per +\func{madvise}.\footnote{dato che si tratta dello stesso tipo di funzionalità, + in questo caso applicata direttamente al sistema ai contenuti di un file + invece che alla sua mappatura in memoria.} 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 delle costanti usabili per l'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}). - - +Come \func{madvise} anche \func{posix\_fadvise} si appoggia al sistema della +memoria virtuale ed al meccanismo standard del \textit{read-ahead} utilizzato +dal kernel; in particolare utilizzando il valore +\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} +fino al kernel 2.6.18 erano equivalenti, a partire da questo kernel la prima +viene non ha più alcun effetto, mentre la seconda dà 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}. -\subsection{Il \textit{mandatory locking}} -\label{sec:file_mand_locking} +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.} -\itindbeg{mandatory~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, +\funcd{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} -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. + \funcdecl{int posix\_fallocate(int fd, off\_t offset, off\_t len)} + + Richiede la allocazione di spazio disco per un file. -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}.} + \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} -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). +La funzione 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 regione 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 successiva scrittura nella regione +indicata non fallirà per mancanza di spazio disco. La funzione non ha nessun +effetto né sul contenuto, né sulla posizione corrente del file. + +Ci si può chiedere a cosa possa servire una funzione come +\func{posix\_fallocate} dato che è sempre possibile ottenere l'effetto voluto +eseguendo esplicitamente sul file la scrittura\footnote{usando \funcd{pwrite} + per evitare spostamenti della posizione corrente sul file.} di una serie di +zeri per l'estensione di spazio necessaria qualora il \itindex{sparse~file} +file debba essere esteso o abbia dei \index{file!\textit{hole}} +buchi.\footnote{si ricordi che occorre scrivere per avere l'allocazione e che + l'uso di \func{truncate} per estendere un file creerebbe soltanto uno + \itindex{sparse~file} \textit{sparse file} (vedi sez.~\ref{sec:file_lseek}) + senza una effettiva allocazione dello spazio disco.} In realtà questa è la +modalità con cui la funzione veniva realizzata nella prima versione fornita +dalle \acr{glibc}, per cui la funzione costituiva in sostanza soltanto una +standardizzazione delle modalità di esecuzione di questo tipo di allocazioni. + +Questo metodo, anche se funzionante, comporta però l'effettiva esecuzione una +scrittura su tutto lo spazio disco necessario, da fare al momento della +richiesta di allocazione, pagandone il conseguente prezzo in termini di +prestazioni; il tutto quando in realtà servirebbe solo poter riservare lo +spazio per poi andarci a scrivere, una sola volta, quando il contenuto finale +diventa effettivamente disponibile. + +Per poter fare tutto questo è però necessario il supporto da parte del kernel, +e questo è divenuto disponibile solo a partire dal kernel 2.6.23 in cui è +stata introdotta la nuova \textit{system call} \func{fallocate},\footnote{non + è detto che la funzione sia disponibile per tutti i filesystem, ad esempio + per XFS il supporto è stato introdotto solo a partire dal kernel 2.6.25.} +che consente di realizzare direttamente all'interno del kernel l'allocazione +dello spazio disco così da poter realizzare una versione di +\func{posix\_fallocate} con prestazioni molto più elevate.\footnote{nelle + \acr{glibc} la nuova \textit{system call} viene sfruttata per la + realizzazione di \func{posix\_fallocate} a partire dalla versione 2.10.} + +Trattandosi di una funzione di servizio, ed ovviamente disponibile +esclusivamente su Linux, inizialmente \funcd{fallocate} non era stata definita +come funzione di libreria,\footnote{pertanto poteva essere invocata soltanto + in maniera indiretta con l'ausilio di \func{syscall}, vedi + sez.~\ref{sec:proc_syscall}, come \code{long fallocate(int fd, int mode, + loff\_t offset, loff\_t len)}.} ma a partire dalle \acr{glibc} 2.10 è + stato fornito un supporto esplicito; il suo prototipo è: +\begin{functions} + \headdecl{linux/fcntl.h} -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}. - -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}. + \funcdecl{int fallocate(int fd, int mode, off\_t offset, off\_t len)} -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). + Prealloca dello spazio disco per un file. + + \bodydesc{La funzione ritorna 0 in caso di successo e $-1$ in caso di errore, + nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{EBADF}] \param{fd} non fa riferimento ad un file descriptor + valido aperto in scrittura. + \item[\errcode{EFBIG}] la somma di \param{offset} e \param{len} eccede le + dimensioni massime di un file. + \item[\errcode{EINVAL}] \param{offset} è minore di zero o \param{len} è + minore o uguale a zero. + \item[\errcode{ENODEV}] \param{fd} non fa riferimento ad un file ordinario + o a una directory. + \item[\errcode{ENOSPC}] non c'è spazio disco sufficiente per l'operazione. + \item[\errcode{ENOSYS}] il filesystem contenente il file associato + a \param{fd} non supporta \func{fallocate}. + \item[\errcode{EOPNOTSUPP}] il filesystem contenente il file associato + a \param{fd} non supporta l'operazione \param{mode}. + \end{errlist} + ed inoltre \errval{EINTR}, \errval{EIO}. +} +\end{functions} -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. +La funzione prende gli stessi argomenti di \func{posix\_fallocate} con lo +stesso significato, a cui si aggiunge l'argomento \param{mode} che indica le +modalità di allocazione; al momento quest'ultimo può soltanto essere nullo o +assumere il valore \const{FALLOC\_FL\_KEEP\_SIZE} che richiede che la +dimensione del file\footnote{quella ottenuta nel campo \var{st\_size} di una + struttura \struct{stat} dopo una chiamata a \texttt{fstat}.} non venga +modificata anche quando la somma di \param{offset} e \param{len} eccede la +dimensione corrente. + +Se \param{mode} è nullo invece la dimensione totale del file in caso di +estensione dello stesso viene aggiornata, come richiesto per +\func{posix\_fallocate}, ed invocata in questo modo si può considerare +\func{fallocate} come l'implementazione ottimale di \func{posix\_fallocate} a +livello di kernel. -\index{file!locking|)} +% vedi http://lwn.net/Articles/226710/ e http://lwn.net/Articles/240571/ +% http://kernelnewbies.org/Linux_2_6_23 -\itindend{mandatory~locking|(} +% TODO non so dove trattarli, ma dal 2.6.39 ci sono i file handle, vedi +% http://lwn.net/Articles/432757/ -% LocalWords: dell'I locking multiplexing cap dell' sez system call socket BSD +% LocalWords: dell'I locking multiplexing cap sez system call socket BSD % LocalWords: descriptor client deadlock NONBLOCK EAGAIN polling select kernel % LocalWords: pselect like sys unistd int fd readfds writefds exceptfds struct % LocalWords: timeval errno EBADF EINTR EINVAL ENOMEM sleep tab signal void of @@ -4459,8 +5456,8 @@ possibilit % LocalWords: only ETXTBSY DENYWRITE ENODEV filesystem EPERM EXEC noexec table % LocalWords: ENFILE lenght segment violation SIGSEGV FIXED msync munmap copy % LocalWords: DoS Denial Service EXECUTABLE NORESERVE LOCKED swapping stack fs -% LocalWords: GROWSDOWN ANON GiB POPULATE prefaulting SIGBUS fifo VME fork old -% LocalWords: exec atime ctime mtime mprotect addr EACCESS mremap address new +% LocalWords: GROWSDOWN ANON POPULATE prefaulting SIGBUS fifo VME fork old SFD +% LocalWords: exec atime ctime mtime mprotect addr mremap address new % LocalWords: long MAYMOVE realloc VMA virtual Ingo Molnar remap pages pgoff % LocalWords: dall' fault cache linker prelink advisory discrectionary lock fl % LocalWords: flock shared exclusive operation dup inode linked NFS cmd ENOLCK @@ -4479,10 +5476,17 @@ possibilit % 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 +% LocalWords: POLLRDHUP half close pwait Gb madvise MADV ahead REMOVE tmpfs it +% LocalWords: DONTFORK DOFORK shmfs preadv pwritev syscall linux loff head XFS +% LocalWords: MERGEABLE EOVERFLOW prealloca hole FALLOC KEEP stat fstat union +% LocalWords: conditions sigwait CLOEXEC signalfd sizemask SIGKILL SIGSTOP ssi +% LocalWords: sigwaitinfo FifoReporter Windows ptr sigqueue named timerfd TFD +% LocalWords: clockid CLOCK MONOTONIC REALTIME itimerspec interval +% LocalWords: ABSTIME gettime %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil" %%% End: -% LocalWords: SEQUENTIAL NOREUSE WILLNEED DONTNEED streaming fallocate EFBIG