X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=fileadv.tex;h=1ba5b9990692a8489fe8deefd1f9c54124dd4964;hp=ee49313125b9153e51a80e38b3da8db973d6b55b;hb=47a00595786c34a03266f19dd5163a45da63e29f;hpb=0465908c904030ae51b43e72afa328e8dda27fd9 diff --git a/fileadv.tex b/fileadv.tex index ee49313..1ba5b99 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -107,10 +107,10 @@ Il primo kernel unix-like ad introdurre una interfaccia per l'\textit{I/O 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 + \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 + \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}. @@ -267,10 +267,10 @@ precedenti, ed inoltre aggiunge a \func{select} una nuova funzione 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 + \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 + \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}.} @@ -350,10 +350,10 @@ cui prototipo 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 + \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 + \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}.} @@ -410,13 +410,13 @@ nel campo \var{revents} per notificare delle condizioni di errore. \hline \const{POLLIN} & È possibile la lettura.\\ \const{POLLRDNORM}& Sono disponibili in lettura dati normali.\\ - \const{POLLRDBAND}& Sono disponibili in lettura dati prioritari. \\ + \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. \\ + \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.\\ @@ -496,10 +496,10 @@ prototipo 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 + \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 + \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}.} @@ -668,15 +668,15 @@ delle operazioni cui fanno riferimento. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{EPOLL\_CTL\_ADD}& aggiunge un nuovo file descriptor da osservare + \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 + \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 + \const{EPOLL\_CTL\_DEL}& Rimuove il file descriptor \param{fd} dalla lista dei file controllati tramite \param{epfd}.\\ \hline \end{tabular} @@ -699,7 +699,7 @@ 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 anche un valore \texttt{NULL}.} + partire dal 2.6.9 si può specificare anche un valore \texttt{NULL}.} @@ -741,7 +741,7 @@ si usa come valore lo stesso \param{fd}. (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{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}).\\ @@ -761,7 +761,8 @@ si usa come valore lo stesso \param{fd}. descriptor associato.\footnotemark\\ \hline \end{tabular} - \caption{Valori del campo \param{events} di \struct{epoll\_event}.} + \caption{Costanti che identificano i bit del campo \param{events} di + \struct{epoll\_event}.} \label{tab:epoll_events} \end{table} @@ -847,38 +848,45 @@ 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 $-1$, che indica un'attesa -indefinita. L'argomento \param{maxevents} dovrà invece essere sempre un intero -positivo. + 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. 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}. +stato impostato per il file descriptor per cui si è verificato l'evento quando +questo era stato registrato con le operazioni \const{EPOLL\_CTL\_MOD} o +\const{EPOLL\_CTL\_ADD}, in questo modo il campo \var{data} consente di +identificare il file descriptor.\footnote{ed è per questo che, come accennato, + è consuetudine usare per \var{data} il valore del file descriptor stesso.} Si ricordi che le occasioni per cui \func{epoll\_wait} ritorna dipendono da come si è impostata la modalità di osservazione (se \textit{level triggered} o \textit{edge triggered}) del singolo file descriptor. L'interfaccia assicura che se arrivano più eventi fra due chiamate successive ad \func{epoll\_wait} -questi vengano combinati. Inoltre qualora su di esso 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. +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 un file descriptor è pronto e resterà -tale fintanto che non si sono completamente esaurite le operazioni su di esso, -questo può essere rilevato con un errore di \errcode{EAGAIN} in 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 anche con il fatto che sono stati restituiti -meno dati di quelli richiesti. - -Come per le precedenti \func{select} e \func{poll}, essendo queste funzioni -utiilizzate prevalentemente con i server di rete, tratteremo degli esempi del -loro più avanti, nella trattazione dei socket, ed in particolare in +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}. @@ -988,8 +996,8 @@ diventati attivi. L'unico modo per essere sicuri che questo non avvenga 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 - \texttt{/proc/sys/kernel/rtsig-max} allo stesso valore di quello di - \texttt{/proc/sys/fs/file-max}.} + \procfile{/proc/sys/kernel/rtsig-max} allo stesso valore del contenuto di + \procfile{/proc/sys/fs/file-max}.} % TODO fare esempio che usa O_ASYNC @@ -1003,27 +1011,29 @@ 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 -non prevede alcun meccanismo per cui un processo possa essere +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 convenzione adottata dalla gran parte di detti + \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. Questa scelta è stata fatta perché provvedere un simile meccanismo a livello -generale comporterebbe un notevole aumento di complessità dell'architettura -della gestione dei file, per fornire una funzionalità necessaria soltanto in -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. +generico per qualunque file comporterebbe un notevole aumento di complessità +dell'architettura della gestione dei file, il tutto per fornire una +funzionalità che serve soltanto in alcuni casi particolari. Dato che +all'origine di Unix i soli programmi che potevano avere una tale esigenza +erano i demoni, attenendosi a uno dei criteri base della progettazione, che +era di far fare al kernel solo le operazioni strettamente necessarie e +lasciare tutto il resto a processi in user space, non era stata prevista +nessuna funzionalità di notifica. Visto però il crescente interesse nei confronti di una funzionalità di questo -tipo (molto richiesta specialmente nello sviluppo dei programmi ad interfaccia -grafica) sono state successivamente introdotte delle estensioni che +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}. @@ -1135,7 +1145,7 @@ operazione di lettura, declassando il \textit{lease} a lettura con Se il \textit{lease holder} non provvede a rilasciare il \textit{lease} entro il numero di secondi specificato dal parametro di sistema mantenuto in -\file{/proc/sys/fs/lease-break-time} sarà il kernel stesso a rimuoverlo (o +\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 @@ -1152,13 +1162,19 @@ 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. -Per risolvere questo problema è stata allora creata un'altra interfaccia, -chiamata \textit{dnotify}, che consente di richiedere una notifica quando una -directory, o di 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. Inoltre si potrà -ottenere nel gestore del segnale il file descriptor che è stato modificato -tramite il contenuto della struttura \struct{siginfo\_t}. +Per risolvere questo problema a partire dal kernel 2.4 è stata allora creata +un'altra interfaccia,\footnote{si ricordi che anche questa è una interfaccia + specifica di Linux che deve essere evitata se si vogliono scrivere programmi + portabili, e che le funzionalità illustrate sono disponibili soltanto se è + stata definita la macro \macro{\_GNU\_SOURCE}.} chiamata \textit{dnotify}, +che consente di richiedere una notifica quando una directory, o uno qualunque +dei file in essa contenuti, viene modificato. Come per i \textit{file lease} +la notifica avviene di default attraverso il segnale \const{SIGIO}, ma se ne +può utilizzare un altro.\footnote{e di nuovo, per le ragioni già esposte in + precedenza, è opportuno che si utilizzino dei segnali real-time.} Inoltre, +come in precedenza, si potrà ottenere nel gestore del segnale il file +descriptor che è stato modificato tramite il contenuto della struttura +\struct{siginfo\_t}. \index{file!lease|)} @@ -1188,7 +1204,7 @@ tramite il contenuto della struttura \struct{siginfo\_t}. \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 + \const{DN\_MULTISHOT}& Richiede una notifica permanente di tutti gli eventi.\\ \hline \end{tabular} @@ -1220,30 +1236,34 @@ specificare un valore nullo. 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 un eccesso di file aperti. Inoltre -quando la directory è su un dispositivo rimovibile, mantenere un file -descriptor aperto comporta l'impossibilità di smontare il dispositivo e -rimuoverlo, complicando la gestione. - -Un secondo problema è che l'interfaccia consente solo di tenere sotto -controllo il contenuto di una directory; la modifica di un file viene -segnalata, ma poi devo verificare quale è. 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_control}, e per questo in -generale quella di \textit{dnotify} viene considerata una interfaccia di -usabilità problematica. +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. + +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. \index{file!dnotify|)} -Per questa serie di motivi, a partire dal kernel 2.6.13, è stata introdotta -una nuova interfaccia per l'osservazione delle modifiche a file o directory, -chiamata \textit{inotify}.\footnote{le corrispondenti funzioni di interfaccia - sono state introdotte nelle glibc 2.4.} 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, risolvendo così il principale -problema di \itindex{dnotify} \textit{dnotify}. La coda viene creata -attraverso la funzione \funcd{inotify\_init}, il cui prototipo è: +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)} @@ -1262,35 +1282,41 @@ attraverso la funzione \funcd{inotify\_init}, il cui prototipo } \end{prototype} -La funzione non prende alcun argomento, e restituisce un file descriptor -associato alla coda, attraverso il quale verranno effettuate le operazioni di -notifica. Si tratta di un file descriptor speciale, che non è associato a -nessun file, ma che viene utilizzato per notificare gli eventi che si sono -posti in osservazione all'applicazione che usa l'interfaccia di -\textit{inotify}. Dato che questo file descriptor non è associato a nessun -file o directory, questo consente di evitare l'inconveniente di non poter -smontare un filesystem i cui file sono tenuti sotto osservazione.\footnote{ed - una delle caratteristiche 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 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 siccome gli eventi vengono notificati come dati disponibili in lettura sul -file descriptor, dette funzioni ritorneranno tutte le volte che si avrà un -evento di notifica. Così, invece di dover utilizzare i segnali, si potrà -gestire l'osservazione delle modifiche con l'\textit{I/O multiplexing}, -utilizzando secondo le modalità illustrate in -sez.~\ref{sec:file_multiplexing}. +essere utilizzato come argomento per le funzioni \func{select} e \func{poll} e +con l'interfaccia di \textit{epoll}; 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 sia singoli file, che intere directory; in quest'ultimo caso -l'interfaccia restituirà informazioni sia riguardo alla directory che ai file -che essa contiene. Una volta creata la coda di notifica si devono definire -gli eventi da tenere sotto osservazione; questo viene fatto tramite una -\textsl{lista di osservazione} (o \textit{watch list}) 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 è: +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)} @@ -1301,23 +1327,36 @@ prima di queste \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 filesystem di \textit{inotify}. + 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{evento di osservazione} (un -cosiddetto ``\textit{watch}'') nella lista di una coda di notifica, indicata -specificando il file descriptor ad essa associato nell'argomento \param{fd}. -Il file o la directory da porre sotto osservazione viene invece indicato per -nome, che viene passato nell'argomento \param{pathname}. Infine il terzo -argomento, \param{mask}, indica che tipo di eventi devono essere tenuti sotto -osservazione. Questo deve essere specificato come maschera binaria combinando -i valori delle costanti riportate in tab.~\ref{tab:inotify_event_watch}. 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. +La funzione consente di creare un ``\textsl{osservatore}'' (il cosiddetto +``\textit{watch}'') nella lista di osservazione di una coda di notifica, che +deve essere indicata specificando il file descriptor ad essa associato +nell'argomento \param{fd}.\footnote{questo ovviamente dovrà essere un file + descriptor creato con \func{inotify\_init}.} Il file o la directory da +porre sotto osservazione vengono invece indicati per nome, da passare +nell'argomento \param{pathname}. Infine il terzo argomento, \param{mask}, +indica che tipo di eventi devono essere tenuti sotto osservazione e le +modalità della stessa. L'operazione può essere ripetuta per tutti i file e le +directory che si vogliono tenere sotto osservazione,\footnote{anche in questo + caso c'è un limite massimo che di default è pari a 8192, ed anche questo + valore può essere cambiato con \func{sysctl} o usando il file + \procfile{/proc/sys/fs/inotify/max\_user\_watches}.} e si utilizzerà sempre +un solo file descriptor. + +Il tipo di evento che si vuole osservare deve essere specificato +nell'argomento \param{mask} come maschera binaria, combinando i valori delle +costanti riportate in tab.~\ref{tab:inotify_event_watch} che identificano i +singoli bit della maschera ed il relativo significato. In essa si sono marcati +con un ``$\bullet$'' gli eventi che, quando specificati per una directory, +vengono osservati anche su tutti i file che essa contiene. Nella seconda +parte della tabella si sono poi indicate alcune combinazioni predefinite dei +flag della prima parte. \begin{table}[htb] \centering @@ -1327,49 +1366,122 @@ per una directory, vengono osservati anche su tutti i file che essa contiene. \textbf{Valore} & & \textbf{Significato} \\ \hline \hline - \const{IN\_ACCESS} &$\bullet$& c'è stato accesso al file in + \const{IN\_ACCESS} &$\bullet$& C'è stato accesso al file in lettura.\\ - \const{IN\_ATTRIB} &$\bullet$& ci sono stati cambiamenti sui dati - dell'inode.\\ - \const{IN\_CLOSE\_WRITE} &$\bullet$& è stato chiuso un file aperto in + \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 + \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 + \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 + \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 + \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 + \const{IN\_MOVED\_FROM} &$\bullet$& Un file è stato spostato fuori dalla directory sotto osservazione.\\ - \const{IN\_MOVED\_TO} &$\bullet$& un file è stato spostato nella + \const{IN\_MOVED\_TO} &$\bullet$& Un file è stato spostato nella directory sotto osservazione.\\ - \const{IN\_OPEN} &$\bullet$& un file è stato aperto.\\ + \const{IN\_OPEN} &$\bullet$& Un file è stato aperto.\\ + \hline + \const{IN\_CLOSE} & & Combinazione di + \const{IN\_CLOSE\_WRITE} e + \const{IN\_CLOSE\_NOWRITE}.\\ + \const{IN\_MOVE} & & Combinazione di + \const{IN\_MOVED\_FROM} e + \const{IN\_MOVED\_TO}.\\ + \const{IN\_ALL\_EVENTS} & & Combinazione di tutti i flag + possibili.\\ \hline \end{tabular} - \caption{Le costanti che identificano i valori per la maschera binaria - dell'argomento \param{mask} di \func{inotify\_add\_watch}.} + \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} -Se non esiste nessun \textit{watch} per il file (o la directory) specificata +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{10cm}|} + \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.\\ + \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} +\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. In caso di -successo la funzione ritorna un intero positivo, detto \textit{watch - descriptor} che identifica univocamente l'evento di osservazione. Questo -valore è importante perché è soltanto con esso che si può rimuovere un evento -di osservazione, usando la seconda funzione dell'interfaccia di gestione, -\funcd{inotify\_rm\_watch}, il cui prototipo è: +altrimenti la funzione sovrascriverà le impostazioni precedenti, a meno che +non si sia usato il flag \const{IN\_MASK\_ADD}, nel qual caso gli eventi +specificati saranno aggiunti a quelli già presenti. + +Come accennato quando si tiene sotto osservazione una directory vengono +restituite le informazioni sia riguardo alla directory stessa che ai file che +essa contiene; questo comportamento può essere disabilitato utilizzando il +flag \const{IN\_ONLYDIR}, che richiede di riportare soltanto gli eventi +relativi alla directory stessa. Si tenga presente inoltre che quando si +osserva una directory vengono riportati solo gli eventi sui file che essa +contiene direttamente, non quelli relativi a file contenuti in eventuali +sottodirectory; se si vogliono osservare anche questi sarà necessario creare +ulteriori \textit{watch} per ciascuna sottodirectory. + +Infine usando il flag \const{IN\_ONESHOT} è possibile richiedere una notifica +singola;\footnote{questa funzionalità però è disponibile soltanto a partire dal + kernel 2.6.16.} una volta verificatosi uno qualunque fra gli eventi +richiesti con \func{inotify\_add\_watch} l'\textsl{osservatore} verrà +automaticamente rimosso dalla lista di osservazione e nessun ulteriore evento +sarà più notificato. + +In caso di successo \func{inotify\_add\_watch} ritorna un intero positivo, +detto \textit{watch descriptor}, che identifica univocamente un +\textsl{osservatore} su una coda di notifica; esso viene usato per farvi +riferimento sia riguardo i risultati restituiti da \textit{inotify}, che per +la eventuale rimozione dello stesso. + +La seconda funzione per la gestione delle code di notifica, che permette di +rimuovere un \textsl{osservatore}, è \funcd{inotify\_rm\_watch}, ed il suo +prototipo è: \begin{prototype}{sys/inotify.h} {int inotify\_rm\_watch(int fd, uint32\_t wd)} - Rimuove un evento di osservazione. + 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: @@ -1382,10 +1494,26 @@ di osservazione, usando la seconda funzione dell'interfaccia di gestione, } \end{prototype} -Oltre che per la rimozione, il \textit{watch descriptor} viene usato anche per -identificare l'evento a cui si fa riferimento nella lista dei risultati -restituiti da \textit{inotify} - +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 @@ -1393,33 +1521,219 @@ restituiti da \textit{inotify} \includestruct{listati/inotify_event.h} \end{minipage} \normalsize - \caption{La struttura \structd{inotify\_event}.} + \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_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}. -Inoltre l'interfaccia di \textit{inotify} permette di conoscere, come avviene -per i file descriptor associati ai socket (si veda al proposito quanto -trattato in sez.~\ref{sec:sock_ioctl_IP}) il numero di byte disponibili in -lettura sul nostro file descriptor, utilizzando su di esso l'operazione -\const{FIONREAD} con \func{ioctl}.\footnote{questa è una delle operazioni - speciali (che abbiamo visto in sez.~\ref{sec:file_ioctl}) che nel caso è - disponibile solo per i socket e per i file descriptor creati con - \func{inotify\_init}.} Questo consente anche di ottenere rapidamente il -numero di file che sono cambiati. +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{10cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{IN\_IGNORED} & L'osservatore è stato rimosso, sia in maniera + esplicita con l'uso di \func{inotify\_rm\_watch}, + che in maniera implicita per la rimozione + dell'oggetto osservato o per lo smontaggio del + filesystem su cui questo si trova.\\ + \const{IN\_ISDIR} & L'evento avvenuto fa riferimento ad una directory + (consente così di distinguere, quando si pone + sotto osservazione una directory, fra gli eventi + relativi ad essa e quelli relativi ai file che + essa contiene).\\ + \const{IN\_Q\_OVERFLOW}& Si sono eccedute le dimensioni della coda degli + eventi (\textit{overflow} della coda); in questo + caso il valore di \var{wd} è $-1$.\footnotemark\\ + \const{IN\_UNMOUNT} & Il filesystem contenente l'oggetto posto sotto + osservazione è stato smontato.\\ + \hline + \end{tabular} + \caption{Le costanti che identificano i bit aggiuntivi usati nella maschera + binaria del campo \var{mask} di \struct{inotify\_event}.} + \label{tab:inotify_read_event_flag} +\end{table} +\footnotetext{la coda di notifica ha una dimensione massima specificata dal + parametro di sistema \procfile{/proc/sys/fs/inotify/max\_queued\_events} che + indica il numero massimo di eventi che possono essere mantenuti sulla + stessa; quando detto valore viene ecceduto gli ulteriori eventi vengono + scartati, ma viene comunque generato un evento di tipo + \const{IN\_Q\_OVERFLOW}.} + +Il campo \var{cookie} contiene invece un intero univoco che permette di +identificare eventi correlati (per i quali avrà lo stesso valore), al momento +viene utilizzato soltanto per rilevare lo spostamento di un file, consentendo +così all'applicazione di collegare la corrispondente coppia di eventi +\const{IN\_MOVED\_TO} e \const{IN\_MOVED\_FROM}. + +Infine 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}. + +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}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/inotify_monitor.c} + \end{minipage} + \normalsize + \caption{Esempio di codice che usa l'interfaccia di \textit{inotify}.} + \label{fig:inotify_monitor_example} +\end{figure} +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 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}). + +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. + +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. + +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. + +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. + +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} -% TODO inserire anche inotify, vedi http://www.linuxjournal.com/article/8478 -% TODO e man inotify +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|)} -% TODO inserire anche eventfd (vedi http://lwn.net/Articles/233462/) - - - \subsection{L'interfaccia POSIX per l'I/O asincrono} \label{sec:file_asyncronous_io} @@ -1540,11 +1854,11 @@ appena descritta; i rispettivi prototipi sono: \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 + \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. + \item[\errcode{EAGAIN}] la coda delle richieste è momentaneamente piena. \end{errlist} } \end{functions} @@ -1711,10 +2025,10 @@ specifica operazione; il suo prototipo 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 + \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. + \item[\errcode{ENOSYS}] la funzione non è implementata. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. \end{errlist} } \end{prototype} @@ -1742,13 +2056,13 @@ lettura o scrittura; il suo prototipo \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 + \item[\errcode{EAGAIN}] nessuna operazione è stata completata entro \param{timeout}. - \item[\errcode{EINVAL}] Si è passato un valore di \param{mode} non valido + \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. + \item[\errcode{ENOSYS}] la funzione non è implementata. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. \end{errlist} } \end{prototype} @@ -1784,79 +2098,8 @@ Oltre alle precedenti modalit 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 - vettorizzato} e per l'\textsl{I/O mappato in memoria} e la funzione -\func{sendfile}. - - -\subsection{I/O vettorizzato} -\label{sec:file_multiple_io} - -Un caso abbastanza comune è quello in cui ci si trova a dover eseguire una -serie multipla di operazioni di I/O, come una serie di letture o scritture di -vari buffer. Un esempio tipico è quando i dati sono strutturati nei campi di -una struttura ed essi devono essere caricati o salvati su un file. Benché -l'operazione sia facilmente eseguibile attraverso una serie multipla di -chiamate, ci sono casi in cui si vuole poter contare sulla atomicità delle -operazioni. - -Per questo motivo BSD 4.2\footnote{Le due funzioni sono riprese da BSD4.4 ed - integrate anche dallo standard Unix 98. 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.} ha introdotto -due nuove system call, \funcd{readv} e \funcd{writev}, che permettono di -effettuare con una sola chiamata una lettura o una scrittura su una serie di -buffer (quello che viene chiamato \textsl{I/O vettorizzato}. I relativi -prototipi sono: -\begin{functions} - \headdecl{sys/uio.h} - - \funcdecl{int readv(int fd, const struct iovec *vector, int count)} - \funcdecl{int writev(int fd, const struct iovec *vector, int count)} - - 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{MAX\_IOVEC}). - \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. - -\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} - -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}. 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}. +sezione allora prenderemo in esame le interfacce per l'\textsl{I/O mappato in + memoria}, per l'\textsl{I/O vettorizzato} e altre funzioni di I/O avanzato. \subsection{File mappati in memoria} @@ -1872,7 +2115,7 @@ file in una sezione dello spazio di indirizzi del processo. che lo ha allocato \begin{figure}[htb] \centering - \includegraphics[width=10cm]{img/mmap_layout} + \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} @@ -1929,29 +2172,29 @@ eseguire la mappatura in memoria di un file, 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 + \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 + \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 + \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 + \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 + \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 + \item[\errcode{ENODEV}] il filesystem di \param{fd} non supporta il memory mapping. - \item[\errcode{EPERM}] L'argomento \param{prot} ha richiesto + \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 + \item[\errcode{ENFILE}] si è superato il limite del sistema sul numero di file aperti (vedi sez.~\ref{sec:sys_resource_limit}). \end{errlist} } @@ -1982,7 +2225,6 @@ multiplo della dimensione di una pagina di memoria. \label{tab:file_mmap_prot} \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 @@ -1992,7 +2234,7 @@ Il valore dell'argomento \param{prot} indica la protezione\footnote{in Linux 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_flag}; il valore specificato deve essere +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, @@ -2014,7 +2256,7 @@ tab.~\ref{tab:file_mmap_flag}. 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. \\ + 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 @@ -2022,7 +2264,7 @@ tab.~\ref{tab:file_mmap_flag}. 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}. \\ + 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 @@ -2032,13 +2274,13 @@ tab.~\ref{tab:file_mmap_flag}. 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}. \\ + 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\_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} @@ -2046,7 +2288,7 @@ tab.~\ref{tab:file_mmap_flag}. 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}. \\ + un \const{SIGSEGV}.\\ \const{MAP\_LOCKED} & Se impostato impedisce lo swapping delle pagine mappate.\\ \const{MAP\_GROWSDOWN} & Usato per gli \itindex{stack} stack. Indica @@ -2064,9 +2306,9 @@ tab.~\ref{tab:file_mmap_flag}. richiesto \const{MAP\_FIXED}.\\ \const{MAP\_POPULATE} & Esegue il \itindex{prefaulting} \textit{prefaulting} delle pagine di memoria - necessarie alla mappatura. \\ + necessarie alla mappatura.\\ \const{MAP\_NONBLOCK} & Esegue un \textit{prefaulting} più limitato che - non causa I/O.\footnotemark \\ + non causa I/O.\footnotemark\\ % \const{MAP\_DONTEXPAND}& Non consente una successiva espansione dell'area % mappata con \func{mremap}, proposto ma pare non % implementato.\\ @@ -2095,10 +2337,10 @@ 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[20]{Dato che tutti faranno riferimento alle stesse pagine di +\footnotetext[68]{dato che tutti faranno riferimento alle stesse pagine di memoria.} -\footnotetext[21]{L'uso di questo flag con \const{MAP\_SHARED} è stato +\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}.} @@ -2109,7 +2351,7 @@ effettive del file o della sezione che si vuole mappare. \begin{figure}[!htb] \centering - \includegraphics[width=12cm]{img/mmap_boundary} + \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} @@ -2152,7 +2394,7 @@ che sono utilizzabili solo con questa interfaccia. \begin{figure}[htb] \centering - \includegraphics[width=12cm]{img/mmap_exceed} + \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} @@ -2209,10 +2451,10 @@ memoria mappata con il file su disco; il suo prototipo \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 + \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 + \item[\errcode{EFAULT}] l'intervallo specificato non ricade in una zona precedentemente mappata. \end{errlist} } @@ -2239,7 +2481,8 @@ del file aggiornato. siano invalidate.\\ \hline \end{tabular} - \caption{Valori dell'argomento \param{flag} di \func{msync}.} + \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} @@ -2266,7 +2509,7 @@ mappatura della memoria usando la funzione \funcd{munmap}, il suo prototipo \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 + \item[\errcode{EINVAL}] l'intervallo specificato non ricade in una zona precedentemente mappata. \end{errlist} } @@ -2420,10 +2663,10 @@ nuova system call, \funcd{remap\_file\_pages}, il cui prototipo Permette di rimappare non linearmente un precedente \textit{memory mapping}. - \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: \begin{errlist} - \item[\errcode{EINVAL}] Si è usato un valore non valido per uno degli + \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} @@ -2482,23 +2725,843 @@ mappatura che gi \itindend{memory~mapping} +\subsection{I/O vettorizzato: \func{readv} e \func{writev}} +\label{sec:file_multiple_io} + +Un caso abbastanza comune è quello in cui ci si trova a dover eseguire una +serie multipla di operazioni di I/O, come una serie di letture o scritture di +vari buffer. Un esempio tipico è quando i dati sono strutturati nei campi di +una struttura ed essi devono essere caricati o salvati su un file. Benché +l'operazione sia facilmente eseguibile attraverso una serie multipla di +chiamate, ci sono casi in cui si vuole poter contare sulla atomicità delle +operazioni. + +Per questo motivo su BSD 4.2 sono state introdotte due nuove system call, +\funcd{readv} e \funcd{writev},\footnote{in Linux le due funzioni sono riprese + da BSD4.4, esse sono previste anche dallo standard POSIX.1-2001.} che +permettono di effettuare con una sola chiamata una lettura o una scrittura su +una serie di buffer (quello che viene chiamato \textsl{I/O vettorizzato}. I +relativi prototipi sono: +\begin{functions} + \headdecl{sys/uio.h} + + \funcdecl{int readv(int fd, const struct iovec *vector, int count)} + \funcdecl{int writev(int fd, const struct iovec *vector, int count)} + + 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} + +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]{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} -\subsection{L'I/O diretto fra file descriptor} +La lista dei buffer da utilizzare viene indicata attraverso l'argomento +\param{vector} che è un vettore di strutture \struct{iovec}, la cui lunghezza +è specificata dall'argomento \param{count}.\footnote{fino alle libc5, Linux + usava \type{size\_t} come tipo dell'argomento \param{count}, una scelta + logica, che però è stata dismessa per restare aderenti allo standard + POSIX.1-2001.} Ciascuna struttura dovrà essere inizializzata opportunamente +per indicare i vari buffer da e verso i quali verrà eseguito il trasferimento +dei dati. Essi verranno letti (o scritti) nell'ordine in cui li si sono +specificati nel vettore \param{vector}. + +La standardizzazione delle due funzioni all'interno della revisione +POSIX.1-2001 prevede anche che sia possibile avere un limite al numero di +elementi del vettore \param{vector}. Qualora questo sussista, esso deve essere +indicato dal valore dalla costante \const{IOV\_MAX}, definita come le altre +costanti analoghe (vedi sez.~\ref{sec:sys_limits}) in \file{limits.h}; lo +stesso valore deve essere ottenibile in esecuzione tramite la funzione +\func{sysconf} richiedendo l'argomento \const{\_SC\_IOV\_MAX} (vedi +sez.~\ref{sec:sys_sysconf}). + +Nel caso di Linux il limite di sistema è di 1024, però se si usano le +\acr{glibc} queste forniscono un \textit{wrapper} per le system call che si +accorge se una operazione supererà il precedente limite, in tal caso i dati +verranno letti o scritti con le usuali \func{read} e \func{write} usando un +buffer di dimensioni sufficienti appositamente allocato e sufficiente a +contenere tutti i dati indicati da \param{vector}. L'operazione avrà successo +ma si perderà l'atomicità del trasferimento da e verso la destinazione finale. + +% TODO verificare cosa succederà a preadv e pwritev o alla nuova niovec +% vedi http://lwn.net/Articles/164887/ + + +\subsection{L'I/O diretto fra file descriptor: \func{sendfile} e \func{splice}} \label{sec:file_sendfile_splice} -Uno dei problemi +Uno dei problemi che si presentano nella gestione dell'I/O è quello in cui si +devono trasferire grandi quantità di dati da un file descriptor ed un altro; +questo usualmente comporta la lettura dei dati dal primo file descriptor in un +buffer in memoria, da cui essi vengono poi scritti sul secondo. + +Benché il kernel ottimizzi la gestione di questo processo quando si ha a che +fare con file normali, in generale quando i dati da trasferire sono molti si +pone il problema di effettuare trasferimenti di grandi quantità di dati da +kernel space a user space e all'indietro, quando in realtà potrebbe essere più +efficiente mantenere tutto in kernel space. Tratteremo in questa sezione +alcune funzioni specialistiche che permettono di ottimizzare le prestazioni in +questo tipo di situazioni. + +La prima funzione che si pone l'obiettivo di ottimizzare il trasferimento dei +dati fra due file descriptor è \funcd{sendfile};\footnote{la funzione è stata + introdotta con i kernel della serie 2.2, e disponibile dalle \acr{glibc} + 2.1.} la funzione è presente in diverse versioni di Unix,\footnote{la si + ritrova ad esempio in FreeBSD, HPUX ed altri Unix.} ma non è presente né in +POSIX.1-2001 né in altri standard,\footnote{pertanto si eviti di utilizzarla + se si devono scrivere programmi portabili.} per cui per essa vengono +utilizzati prototipi e semantiche differenti; nel caso di Linux il suo +prototipo è: +\begin{functions} + \headdecl{sys/sendfile.h} + + \funcdecl{ssize\_t sendfile(int out\_fd, int in\_fd, off\_t *offset, size\_t + count)} + + Copia dei dati da un file descriptor ad un altro. + + \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di + successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno + dei valori: + \begin{errlist} + \item[\errcode{EAGAIN}] si è impostata la modalità non bloccante su + \param{out\_fd} e la scrittura si bloccherebbe. + \item[\errcode{EINVAL}] i file descriptor non sono validi, o sono bloccati + (vedi sez.~\ref{sec:file_locking}), o \func{mmap} non è disponibile per + \param{in\_fd}. + \item[\errcode{EIO}] si è avuto un errore di lettura da \param{in\_fd}. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per la lettura da + \param{in\_fd}. + \end{errlist} + ed inoltre \errcode{EBADF} e \errcode{EFAULT}. + } +\end{functions} -NdA è da finire, sul perché non è abilitata fra file vedi: +La funzione copia direttamente \param{count} byte dal file descriptor +\param{in\_fd} al file descriptor \param{out\_fd}; in caso di successo +funzione ritorna il numero di byte effettivamente copiati da \param{in\_fd} a +\param{out\_fd} o $-1$ in caso di errore, come le ordinarie \func{read} e +\func{write} questo valore può essere inferiore a quanto richiesto con +\param{count}. + +Se il puntatore \param{offset} è nullo la funzione legge i dati a partire +dalla posizione corrente su \param{in\_fd}, altrimenti verrà usata la +posizione indicata dal valore puntato da \param{offset}; in questo caso detto +valore sarà aggiornato, come \textit{value result argument}, per indicare la +posizione del byte successivo all'ultimo che è stato letto, mentre la +posizione corrente sul file non sarà modificata. Se invece \param{offset} è +nullo la posizione corrente sul file sarà aggiornata tenendo conto dei byte +letti da \param{in\_fd}. + +Fino ai kernel della serie 2.4 la funzione è utilizzabile su un qualunque file +descriptor, e permette di sostituire la invocazione successiva di una +\func{read} e una \func{write} (e l'allocazione del relativo buffer) con una +sola chiamata a \funcd{sendfile}. In questo modo si può diminuire il numero di +chiamate al sistema e risparmiare in trasferimenti di dati da kernel space a +user space e viceversa. La massima utilità della funzione si ha comunque per +il trasferimento di dati da un file su disco ad un socket di +rete,\footnote{questo è il caso classico del lavoro eseguito da un server web, + ed infatti Apache ha una opzione per il supporto esplicito di questa + funzione.} dato che in questo caso diventa possibile effettuare il +trasferimento diretto via DMA dal controller del disco alla scheda di rete, +senza neanche allocare un buffer nel kernel,\footnote{il meccanismo è detto + \textit{zerocopy} in quanto i dati non vengono mai copiati dal kernel, che + si limita a programmare solo le operazioni di lettura e scrittura via DMA.} +ottenendo la massima efficienza possibile senza pesare neanche sul processore. + +In seguito però ci si è accorti che, fatta eccezione per il trasferimento +diretto da file a socket, non sempre \func{sendfile} comportava miglioramenti +significativi delle prestazioni rispetto all'uso in sequenza di \func{read} e +\func{write},\footnote{nel caso generico infatti il kernel deve comunque + allocare un buffer ed effettuare la copia dei dati, e in tal caso spesso il + guadagno ottenibile nel ridurre il numero di chiamate al sistema non + compensa le ottimizzazioni che possono essere fatte da una applicazione in + user space che ha una conoscenza diretta su come questi sono strutturati.} e +che anzi in certi casi si potevano avere anche dei peggioramenti. Questo ha +portato, per i kernel della serie 2.6,\footnote{per alcune motivazioni di + questa scelta si può fare riferimento a quanto illustrato da Linus Torvalds + in \href{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html} + {\textsf{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}}.} +alla decisione di consentire l'uso della funzione soltanto quando il file da +cui si legge supporta le operazioni di \textit{memory mapping} (vale a dire +non è un socket) e quello su cui si scrive è un socket; in tutti gli altri +casi l'uso di \func{sendfile} darà luogo ad un errore di \errcode{EINVAL}. + +Nonostante ci possano essere casi in cui \func{sendfile} non migliora le +prestazioni, le motivazioni addotte non convincono del tutto e resta il dubbio +se la scelta di disabilitarla sempre per il trasferimento di dati fra file di +dati sia davvero corretta. Se ci sono peggioramenti di prestazioni infatti si +può sempre fare ricorso all'uso successivo di, ma lasciare a disposizione la +funzione consentirebbe se non altro, anche in assenza di guadagni di +prestazioni, di semplificare la gestione della copia dei dati fra file, +evitando di dover gestire l'allocazione di un buffer temporaneo per il loro +trasferimento; inoltre si avrebbe comunque il vantaggio di evitare inutili +trasferimenti di dati da kernel space a user space e viceversa. + +Questo dubbio si può comunque ritenere superato con l'introduzione, avvenuto a +partire dal kernel 2.6.17, della nuova system call \func{splice}. Lo scopo di +questa funzione è quello di fornire un meccanismo generico per il +trasferimento di dati da o verso un file utilizzando un buffer gestito +internamente dal kernel. Descritta in questi termini \func{splice} sembra +semplicemente un ``\textsl{dimezzamento}'' di \func{sendfile}.\footnote{nel + senso che un trasferimento di dati fra due file con \func{sendfile} non + sarebbe altro che la lettura degli stessi su un buffer seguita dalla + relativa scrittura, cosa che in questo caso si dovrebbe eseguire con due + chiamate a \func{splice}.} In realtà le due system call sono profondamente +diverse nel loro meccanismo di funzionamento;\footnote{questo fino al kernel + 2.6.23, dove \func{sendfile} è stata reimplementata in termini di + \func{splice}, pur mantenendo disponibile la stessa interfaccia verso l'user + space.} \func{sendfile} infatti, come accennato, non necessita di avere a +disposizione un buffer interno, perché esegue un trasferimento diretto di +dati; questo la rende in generale più efficiente, ma anche limitata nelle sue +applicazioni, dato che questo tipo di trasferimento è possibile solo in casi +specifici.\footnote{e nel caso di Linux questi sono anche solo quelli in cui + essa può essere effettivamente utilizzata.} + +Il concetto che sta dietro a \func{splice} invece è diverso,\footnote{in + realtà la proposta originale di Larry Mc Voy non differisce poi tanto negli + scopi da \func{sendfile}, quello che rende \func{splice} davvero diversa è + stata la reinterpretazione che ne è stata fatta nell'implementazione su + Linux realizzata da Jens Anxboe, concetti che sono esposti sinteticamente + dallo stesso Linus Torvalds in \href{http://kerneltrap.org/node/6505} + {\textsf{http://kerneltrap.org/node/6505}}.} si tratta semplicemente di una +funzione che consente di fare in maniera del tutto generica delle operazioni +di trasferimento di dati fra un file e un buffer gestito interamente in kernel +space. In questo caso il cuore della funzione (e delle affini \func{vmsplice} +e \func{tee}, che tratteremo più avanti) è appunto l'uso di un buffer in +kernel space, e questo è anche quello che ne ha semplificato l'adozione, +perché l'infrastruttura per la gestione di un tale buffer è presente fin dagli +albori di Unix per la realizzazione delle \textit{pipe} (vedi +sez.~\ref{sec:ipc_unix}). Dal punto di vista concettuale allora \func{splice} +non è altro che una diversa interfaccia (rispetto alle \textit{pipe}) con cui +utilizzare in user space l'oggetto ``\textsl{buffer in kernel space}''. + +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} -\href{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html} -{\texttt{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}} + \funcdecl{long splice(int fd\_in, off\_t *off\_in, int fd\_out, off\_t + *off\_out, size\_t len, unsigned int flags)} + + Trasferisce dati da un file verso una pipe o viceversa. -% TODO documentare la funzione sendfile -% TODO documentare le funzioni tee e splice -% http://kerneltrap.org/node/6505 e http://lwn.net/Articles/178199/ e -% http://lwn.net/Articles/179492/ -% e http://en.wikipedia.org/wiki/Splice_(system_call) + \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}] uno o entrambi fra \param{fd\_in} e \param{fd\_out} + non sono file descriptor validi o, rispettivamente, non sono stati + aperti in lettura o scrittura. + \item[\errcode{EINVAL}] il filesystem su cui si opera non supporta + \func{splice}, oppure nessuno dei file descriptor è una pipe, oppure si + è dato un valore a \param{off\_in} o \param{off\_out} ma il + corrispondente file è un dispositivo che non supporta la funzione + \func{seek}. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione + richiesta. + \item[\errcode{ESPIPE}] o \param{off\_in} o \param{off\_out} non sono + \const{NULL} ma il corrispondente file descriptor è una \textit{pipe}. + \end{errlist} + } +\end{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. + +In caso di successo la funzione ritorna il numero di byte trasferiti, che può +essere, come per le normali funzioni di lettura e scrittura su file, inferiore +a quelli richiesti; un valore negativo indicherà un errore mentre un valore +nullo indicherà che non ci sono dati da trasferire (ad esempio si è giunti +alla fine del file in lettura). Si tenga presente che, a seconda del verso del +trasferimento dei dati, la funzione si comporta nei confronti del file +descriptor che fa riferimento al file ordinario, come \func{read} o +\func{write}, e pertanto potrà anche bloccarsi (a meno che non si sia aperto +il suddetto file in modalità non bloccante). + +I due argomenti \param{off\_in} e \param{off\_out} consentono di specificare, +come per l'analogo \param{offset} di \func{sendfile}, la posizione all'interno +del file da cui partire per il trasferimento dei dati. Come per +\func{sendfile} un valore nullo indica di usare la posizione corrente sul +file, ed essa sarà aggiornata automaticamente secondo il numero di byte +trasferiti. Un valore non nullo invece deve essere un puntatore ad una +variabile intera che indica la posizione da usare; questa verrà aggiornata, al +ritorno della funzione, al byte successivo all'ultimo byte trasferito. +Ovviamente soltanto uno di questi due argomenti, e più precisamente quello che +fa riferimento al file descriptor non associato alla \textit{pipe}, può essere +specificato come valore non nullo. + +Infine l'argomento \param{flags} consente di controllare alcune +caratteristiche del funzionamento della funzione; il contenuto è una maschera +binaria e deve essere specificato come OR aritmetico dei valori riportati in +tab.~\ref{tab:splice_flag}. Alcuni di questi valori vengono utilizzati anche +dalle funzioni \func{vmsplice} e \func{tee} per cui la tabella riporta le +descrizioni complete di tutti i valori possibili anche quando, come per +\const{SPLICE\_F\_GIFT}, questi non hanno effetto su \func{splice}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{10cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{SPLICE\_F\_MOVE} & Suggerisce al kernel di spostare le pagine + di memoria contenenti i dati invece di + copiarle;\footnotemark viene usato soltanto + da \func{splice}.\\ + \const{SPLICE\_F\_NONBLOCK}& Richiede di operare in modalità non + bloccante; questo flag influisce solo sulle + operazioni che riguardano l'I/O da e verso la + \textit{pipe}. Nel caso di \func{splice} + questo significa che la funzione potrà + comunque bloccarsi nell'accesso agli altri + file descriptor (a meno che anch'essi non + siano stati aperti in modalità non + bloccante).\\ + \const{SPLICE\_F\_MORE} & Indica al kernel che ci sarà l'invio di + ulteriori dati in una \func{splice} + successiva, questo è un suggerimento utile + che viene usato quando \param{fd\_out} è un + socket.\footnotemark Attualmente viene usato + solo da \func{splice}, potrà essere + implementato in futuro anche per + \func{vmsplice} e \func{tee}.\\ + \const{SPLICE\_F\_GIFT} & Le pagine di memoria utente sono + ``\textsl{donate}'' al kernel;\footnotemark + se impostato una seguente \func{splice} che + usa \const{SPLICE\_F\_MOVE} potrà spostare le + pagine con successo, altrimenti esse dovranno + essere copiate; per usare questa opzione i + dati dovranno essere opportunamente allineati + in posizione ed in dimensione alle pagine di + memoria. Viene usato soltanto da + \func{vmsplice}.\\ + \hline + \end{tabular} + \caption{Le costanti che identificano i bit della maschera binaria + dell'argomento \param{flags} di \func{splice}, \func{vmsplice} e + \func{tee}.} + \label{tab:splice_flag} +\end{table} + +\footnotetext{per una maggiore efficienza \func{splice} usa quando possibile i + meccanismi della memoria virtuale per eseguire i trasferimenti di dati (in + maniera analoga a \func{mmap}), qualora le pagine non possano essere + spostate dalla pipe o il buffer non corrisponda a pagine intere esse saranno + comunque copiate.} + +\footnotetext{questa opzione consente di utilizzare delle opzioni di gestione + dei socket che permettono di ottimizzare le trasmissioni via rete, si veda + la descrizione di \const{TCP\_CORK} in sez.~\ref{sec:sock_tcp_udp_options} e + quella di \const{MSG\_MORE} in sez.~\ref{sec:net_sendmsg}.} + +\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.} + +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[height=6cm]{img/splice_copy} + \caption{Struttura del flusso di dati usato dal programma \texttt{splicecp}.} + \label{fig:splicecp_data_flux} +\end{figure} + +Una volta trattate le opzioni il programma verifica che restino +(\texttt{\small 13--16}) i due argomenti che indicano il file sorgente ed il +file destinazione. Il passo successivo è aprire il file sorgente +(\texttt{\small 18--22}), quello di destinazione (\texttt{\small 23--27}) ed +infine (\texttt{\small 28--31}) la \textit{pipe} che verrà usata come buffer. + +\begin{figure}[!phtb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/splicecp.c} + \end{minipage} + \normalsize + \caption{Esempio di codice che usa \func{splice} per effettuare la copia di + un file.} + \label{fig:splice_example} +\end{figure} + +Il ciclo principale (\texttt{\small 33--58}) inizia con la lettura dal file +sorgente tramite la prima \func{splice} (\texttt{\small 34--35}), in questo +caso si è usato come primo argomento il file descriptor del file sorgente e +come terzo quello del capo in scrittura della \textit{pipe} (il funzionamento +delle \textit{pipe} e l'uso della coppia di file descriptor ad esse associati +è trattato in dettaglio in sez.~\ref{sec:ipc_unix}; non ne parleremo qui dato +che nell'ottica dell'uso di \func{splice} questa operazione corrisponde +semplicemente al trasferimento dei dati dal file al buffer). + +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}). + +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. + +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. + +Si noti come il programma sia concettualmente identico a quello che si sarebbe +scritto usando \func{read} al posto della prima \func{splice} e \func{write} +al posto della seconda, utilizzando un buffer in user space per eseguire la +copia dei dati, solo che in questo caso non è stato necessario allocare nessun +buffer e non si è trasferito nessun dato in user space. + +Si noti anche come si sia usata la combinazione \texttt{SPLICE\_F\_MOVE | + SPLICE\_F\_MORE } per l'argomento \param{flags} di \func{splice}, infatti +anche se un valore nullo avrebbe dato gli stessi risultati, l'uso di questi +flag, che si ricordi servono solo a dare suggerimenti al kernel, permette in +genere di migliorare le prestazioni. + +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. + +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} + + \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}. + + \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di + successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno + dei valori: + \begin{errlist} + \item[\errcode{EBADF}] o \param{fd} non è un file descriptor valido o non + fa riferimento ad una \textit{pipe}. + \item[\errcode{EINVAL}] si è usato un valore nullo per \param{nr\_segs} + oppure si è usato \const{SPLICE\_F\_GIFT} ma la memoria non è allineata. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione + richiesta. + \end{errlist} + } +\end{functions} + +La \textit{pipe} dovrà essere specificata tramite il file descriptor +corrispondente al suo capo aperto in scrittura (di nuovo si faccia riferimento +a sez.~\ref{sec:ipc_unix}), mentre per indicare quali zone di memoria devono +essere trasferita si deve utilizzare un vettore di strutture \struct{iovec} +(vedi fig.~\ref{fig:file_iovec}), con le stesse con cui le si usano per l'I/O +vettorizzato; le dimensioni del suddetto vettore devono essere passate +nell'argomento \param{nr\_segs} che indica il numero di segmenti di memoria da +trasferire. Sia per il vettore che per il valore massimo di \param{nr\_segs} +valgono le stesse limitazioni illustrate in sez.~\ref{sec:file_multiple_io}. + +In caso di successo la funzione ritorna il numero di byte trasferiti sulla +pipe, in generale (se i dati una volta creati non devono essere riutilizzati) +è opportuno utilizzare il flag \const{SPLICE\_F\_GIFT}; questo fa si che il +kernel possa rimuovere le relative pagine dallo spazio degli indirizzi del +processo, e scaricarle nella cache, così che queste possono essere utilizzate +immediatamente senza necessità di eseguire una copia dei dati che contengono. + +La seconda funzione aggiunta insieme a \func{splice} è \func{tee}, che deve il +suo nome all'omonimo comando in user space, perché in analogia con questo +permette di duplicare i dati in ingresso su una \textit{pipe} su un'altra +\textit{pipe}. In sostanza, sempre nell'ottica della manipolazione dei dati su +dei buffer in kernel space, la funzione consente di eseguire una copia del +contenuto del buffer stesso. Il prototipo di \funcd{tee} è il seguente: +\begin{functions} + \headdecl{fcntl.h} + + \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} + +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}. + +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}). + +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. + + + +\subsection{Gestione avanzata dell'accesso ai dati dei file} +\label{sec:file_fadvise} + +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. + +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}. + +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} + + \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} 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, è: +\begin{functions} + \headdecl{fcntl.h} + + \funcdecl{int posix\_fadvise(int fd, off\_t offset, off\_t len, int advice)} + + 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{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} + } +\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. + +\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} + +Anche \func{posix\_fadvise} si appoggia al sistema della memoria virtuale ed +al meccanismo standard del \textit{readahead} utilizzato dal kernel; in +particolare con \const{POSIX\_FADV\_SEQUENTIAL} si raddoppia la dimensione +dell'ammontare di dati letti preventivamente rispetto al default, aspettandosi +appunto una lettura sequenziale che li utilizzerà, mentre con +\const{POSIX\_FADV\_RANDOM} si disabilita del tutto il suddetto meccanismo, +dato che con un accesso del tutto casuale è inutile mettersi a leggere i dati +immediatamente successivi gli attuali; infine l'uso di +\const{POSIX\_FADV\_NORMAL} consente di riportarsi al comportamento di +default. + +Le due modalità \const{POSIX\_FADV\_NOREUSE} e \const{POSIX\_FADV\_WILLNEED} +danno invece inizio ad una lettura in cache della regione del file indicata. +La quantità di dati che verranno letti è ovviamente limitata in base al carico +che si viene a creare sul sistema della memoria virtuale, ma in genere una +lettura di qualche megabyte viene sempre soddisfatta (ed un valore superiore è +solo raramente di qualche utilità). In particolare l'uso di +\const{POSIX\_FADV\_WILLNEED} si può considerare l'equivalente POSIX di +\func{readahead}. + +Infine con \const{POSIX\_FADV\_DONTNEED} si dice al kernel di liberare le +pagine di cache occupate dai dati presenti nella regione di file indicata. +Questa è una indicazione utile che permette di alleggerire il carico sulla +cache, ed un programma può utilizzare periodicamente questa funzione per +liberare pagine di memoria da dati che non sono più utilizzati per far posto a +nuovi dati utili.\footnote{la pagina di manuale riporta l'esempio dello + streaming di file di grosse dimensioni, dove le pagine occupate dai dati già + inviati possono essere tranquillamente scartate.} + +Sia \func{posix\_fadvise} che \func{readahead} attengono alla ottimizzazione +dell'accesso in lettura; lo standard POSIX.1-2001 prevede anche una funzione +specifica per le operazioni di scrittura, \func{posix\_fallocate},\footnote{la + funzione è stata introdotta a partire dalle glibc 2.1.94.} che consente di +preallocare dello spazio disco per assicurarsi che una seguente scrittura non +fallisca, il suo prototipo, anch'esso disponibile solo se si definisce la +macro \macro{\_XOPEN\_SOURCE} ad almeno 600, è: +\begin{functions} + \headdecl{fcntl.h} + + \funcdecl{int posix\_fallocate(int fd, off\_t offset, off\_t len)} + + Richiede la allocazione di spazio disco per un file. + + \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} + +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. + + + +% 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 + +% 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 %\subsection{L'utilizzo delle porte di I/O} %\label{sec:file_io_port} @@ -2509,6 +3572,7 @@ NdA + \section{Il file locking} \label{sec:file_locking} @@ -2529,7 +3593,7 @@ 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 \textit{race condition} \itindex{race~condition}, attraverso una +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. @@ -2640,7 +3704,7 @@ rimuovere un \textit{file lock} \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 è + \item[\errcode{EWOULDBLOCK}] il file ha già un blocco attivo, e si è specificato \const{LOCK\_NB}. \end{errlist} } @@ -2776,17 +3840,17 @@ essa viene usata solo secondo il prototipo: \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 + \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 + \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 + \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 + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima di poter acquisire un lock. \end{errlist} ed inoltre \errval{EBADF}, \errval{EFAULT}. @@ -2923,13 +3987,6 @@ ed impedirle restituendo un errore di \errcode{EDEADLK} alla funzione che cerca di acquisire un lock che porterebbe ad un \itindex{deadlock} \textit{deadlock}. -\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} - Per capire meglio il funzionamento del file locking in semantica POSIX (che differisce alquanto rispetto da quello di BSD, visto @@ -2948,6 +4005,13 @@ questo caso la titolarit voce nella \itindex{file~table} \textit{file table}, ma con il valore del \acr{pid} del processo. +\begin{figure}[!bht] + \centering \includegraphics[width=13cm]{img/file_posix_lock} + \caption{Schema dell'architettura del file locking, nel caso particolare + del suo utilizzo secondo l'interfaccia standard POSIX.} + \label{fig:file_posix_lock} +\end{figure} + 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 @@ -3218,10 +4282,10 @@ che utilizza la funzione \funcd{lockf}, il cui prototipo \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 è + \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 + \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. \end{errlist} ed inoltre \errval{EBADF}, \errval{EINVAL}. @@ -3243,7 +4307,7 @@ tab.~\ref{tab:file_lockf_type}. \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. \\ + 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 @@ -3399,10 +4463,17 @@ possibilit % LocalWords: attribute Universe epoll Solaris kqueue level triggered Jonathan % LocalWords: Lemon BSDCON edge Libenzi kevent backporting epfd EEXIST ENOENT % LocalWords: MOD wait EPOLLIN EPOLLOUT EPOLLRDHUP SOCK EPOLLPRI EPOLLERR one -% LocalWords: EPOLLHUP EPOLLET EPOLLONESHOT shot maxevents +% LocalWords: EPOLLHUP EPOLLET EPOLLONESHOT shot maxevents ctlv ALL DONT HPUX +% LocalWords: FOLLOW ONESHOT ONLYDIR FreeBSD EIO caching sysctl instances name +% LocalWords: watches IGNORED ISDIR OVERFLOW overflow UNMOUNT queued cookie ls +% LocalWords: NUL sizeof casting printevent nread limits sysconf SC wrapper Di +% 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 %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil" %%% End: +% LocalWords: SEQUENTIAL NOREUSE WILLNEED DONTNEED streaming fallocate EFBIG