X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=blobdiff_plain;f=fileadv.tex;h=a7851b65ab0347b71be5be874181dce85a6225a7;hb=45633dbe15fd23b17a975eb4d9c231d22a67ac00;hp=6c2bfccc4cb177bc1309572285b2c380b871e32b;hpb=eb9d45f3cc5cfc16b8c478d232080873a202af1c;p=gapil.git diff --git a/fileadv.tex b/fileadv.tex index 6c2bfcc..a7851b6 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -1301,12 +1301,14 @@ eliminato.\footnote{anzi, una delle capacit Inoltre trattandosi di un file descriptor a tutti gli effetti, esso potrà essere utilizzato come argomento per le funzioni \func{select} e \func{poll} e con l'interfaccia di \textit{epoll}; 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,\footnote{considerati una pessima scelta dal punto - di vista dell'interfaccia utente.} si potrà gestire l'osservazione delle -modifiche con una qualunque delle modalità di \textit{I/O multiplexing} -illustrate in sez.~\ref{sec:file_multiplexing}. +come dati disponibili in lettura, dette funzioni ritorneranno tutte le volte +che si avrà un evento di notifica. Così, invece di dover utilizzare i +segnali,\footnote{considerati una pessima scelta dal punto di vista + dell'interfaccia utente.} si potrà gestire l'osservazione degli eventi con +una qualunque delle modalità di \textit{I/O multiplexing} illustrate in +sez.~\ref{sec:file_multiplexing}. Qualora si voglia cessare l'osservazione, +sarà sufficiente chiudere il file descriptor e tutte le risorse allocate +saranno automaticamente rilasciate. Infine l'interfaccia di \textit{inotify} consente di mettere sotto osservazione, oltre che una directory, anche singoli file. Una volta creata @@ -1380,7 +1382,7 @@ flag della prima parte. \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 @@ -1391,13 +1393,13 @@ flag della prima parte. directory sotto osservazione.\\ \const{IN\_OPEN} &$\bullet$& Un file è stato aperto.\\ \hline - \const{IN\_CLOSE} & -- & Combinazione di + \const{IN\_CLOSE} & & Combinazione di \const{IN\_CLOSE\_WRITE} e \const{IN\_CLOSE\_NOWRITE}.\\ - \const{IN\_MOVE} & -- & Combinazione di + \const{IN\_MOVE} & & Combinazione di \const{IN\_MOVED\_FROM} e \const{IN\_MOVED\_TO}.\\ - \const{IN\_ALL\_EVENTS} & -- & Combinazione di tutti i flag + \const{IN\_ALL\_EVENTS} & & Combinazione di tutti i flag possibili.\\ \hline \end{tabular} @@ -1461,7 +1463,7 @@ sottodirectory; se si vogliono osservare anche questi sar ulteriori \textit{watch} per ciascuna sottodirectory. Infine usando il flag \const{IN\_ONESHOT} è possibile richiedere una notifica -singola;\footnote{questa funzionalità però è disponibile soltato a partire dal +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 @@ -1504,7 +1506,6 @@ 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 @@ -1544,7 +1545,7 @@ osservatore 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{maks} di +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}. @@ -1598,18 +1599,137 @@ osservazione, in tal caso essi contengono rispettivamente il nome del file 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. Questo significa che -le dimensioni di ciascun evento di \textit{inotify} saranno pari al valore -\code{sizeof(\struct{inotify\_event}) + len}. +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 un file o una -directory. +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|)} @@ -1983,79 +2103,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} @@ -2071,7 +2120,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} @@ -2308,7 +2357,7 @@ effettive del file o della sezione che si vuole mappare. \begin{figure}[!htb] \centering - \includegraphics[width=12cm]{img/mmap_boundary} + \includegraphics[width=13cm]{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} @@ -2351,7 +2400,7 @@ che sono utilizzabili solo con questa interfaccia. \begin{figure}[htb] \centering - \includegraphics[width=12cm]{img/mmap_exceed} + \includegraphics[width=13cm]{img/mmap_exceed} \caption{Schema della mappatura in memoria di file di dimensioni inferiori alla lunghezza richiesta.} \label{fig:file_mmap_exceed} @@ -2682,11 +2731,102 @@ mappatura che gi \itindend{memory~mapping} -\subsection{L'I/O diretto fra file descriptor} -\label{sec:file_sendfile_splice} +\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} + +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 che si presenta nella gestione dell'I/O è quello in cui si +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. @@ -2694,18 +2834,20 @@ 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à sarebbe molto più -efficiente tenere tutto in kernel space. Tratteremo in questa sezione alcune -funzioni specialistiche che permettono di ottimizzare le prestazioni in questo -tipo di situazioni. +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}; 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, -per cui vengono utilizzati diversi prototipi e semantiche -differenti;\footnote{pertanto si eviti di utilizzarla se si devono scrivere - programmi portabili.} nel caso di Linux il suo prototipo è: +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} @@ -2714,13 +2856,15 @@ differenti;\footnote{pertanto si eviti di utilizzarla se si devono scrivere Copia dei dati da un file descriptor ad un altro. - \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: + \bodydesc{La funzione restituisce 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 - o una operazione di \func{mmap} non è disponibile per \param{in\_fd}. + (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}. @@ -2729,18 +2873,136 @@ differenti;\footnote{pertanto si eviti di utilizzarla se si devono scrivere } \end{functions} +La funzione copia direttamente \param{count} byte dal file descriptor +\param{in\_fd} al file descriptor \param{out\_fd}; in caso di successo +funzione ritorna il numero di byte effettivamente copiati da \param{in\_fd} a +\param{out\_fd} o $-1$ in caso di errore, come le ordinarie \func{read} e +\func{write} questo valore può essere inferiore a quanto richiesto con +\param{count}. + +Se il puntatore \param{offset} è nullo la funzione legge i dati a partire +dalla posizione corrente su \param{in\_fd}, altrimenti verrà usata la +posizione indicata dal valore puntato da \param{offset}. In questo caso detto +valore sarà aggiornato, come \textit{value result argument}, per indicare la +posizione del byte successivo all'ultimo che è stato letto, mentre la +posizione corrente sul file non sarà modificata. Se invece \param{offset} è +nullo la posizione corrente sul file sarà aggiornata tenendo conto dei byte +letti da \param{in\_fd}. + +Fino ai kernel della serie 2.4 la funzione è utilizzabile su un qualunque file +descriptor, e permette di sostituire la invocazione successiva di una +\func{read} ed 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 socket di rete,\footnote{il + caso classico del lavoro 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.} + +Con i kernel della serie 2.6 ci si è accorti però che, a parte quest'ultimo +caso, l'uso di \func{sendfile} non sempre portava significativi miglioramenti +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 + maggiore conoscenza su come questi sono strutturati.} e che anzi in certi +casi si avevano dei peggioramenti, questo ha portato alla +decisione\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} + {\texttt{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}}.} +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 si avrà +un errore di \errcode{EINVAL}. + +Nonostante i limiti illustrati resta comunque il dubbio se la scelta di +disabilitare \func{sendfile} per il trasferimento di dati fra file di dati sia +davvero corretta; la funzione infatti se non altro consentirebbe di +semplificare l'interfaccia per la copia dei dati, evitando di dover gestire +l'allocazione di un buffer temporaneo per il loro trasferimento in tutti quei +casi in cui non c'è necessità di fare controlli sugli stessi. Inoltre essa +avrebbe comunque il vantaggio di evitare trasferimenti di dati da e verso +l'user space. + +Il dubbio è stato rimosso con l'introduzione della system call +\func{splice},\footnote{avvenuto a partire dal kernel 2.6.17.} il cui scopo è +appunto quello di fornire un meccanismo generico per il trasferimento di dati +da o verso un file utilizzando un buffer intermedio gestito direttamente dal +kernel. Lo scopo della funzione può sembrare lo stesso di \func{sendfile}, ma +in realtà esse sono profondamente diverse nel loro meccanismo di +funzionamento; \func{sendfile} infatti, come accennato, non necessita affatto +(anzi nel caso di Linux viene sostanzialmente usata solo in questo caso) di +avere a disposizione un buffer interno, perché esegue un trasferimento diretto +di dati; questo la rende in generale molto più efficiente, ma anche limitata +nelle sue applicazioni. + +Il concetto che sta dietro a \func{splice} invece è diverso,\footnote{in + realtà la proposta originale di Larry Mc Voy non ne differisce poi tanto, + quello che la rende davvero diversa è stata la reinterpretazione che ne è + stata fatta nell'implementazione su Linux realizzata da Jens Anxboe, di cui + si può trovare un buon riassunto in \href{http://kerneltrap.org/node/6505} + {\texttt{http://kerneltrap.org/node/6505}}.} si tratta semplicemente di una +funzione che consente di fare delle operazioni di trasferimento dati da e +verso un buffer interamente gestito in kernel space, in maniera del tutto +generica. In questo caso il cuore della funzione (e delle affini +\func{vmsplice} e \func{tee}, che tratteremo più avanti) è appunto il buffer +in kernel space; questo è anche quello che ne ha semplificato +l'adozione,\footnote{la funzione infatti non è definita in nessuno standard, + e, allo stato attuale è disponibile soltanto su Linux.} perché +l'infrastruttura per la gestione di un buffer in kernel space è presente fin +dagli albori di Unix per la realizzazione delle \textit{pipe} (tratteremo +l'argomento in sez.~\ref{sec:ipc_unix}). Dal punto di vista concettuale allora +\func{splice} non è che un'altra interfaccia con cui esporre in userspace +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 dove appoggiare i dati che vengono trasferiti da un capo +all'altro della stessa (vedi fig.~\ref{fig:ipc_pipe_singular}) per creare un +meccanismo di comunicazione fra processi, nel caso di \funcd{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 infatti è una +interfaccia generica che consente di trasferire dati da un buffer ad un file o +viceversa; il suo prototipo, accessibile solo avendo definito +\macro{\_GNU\_SOURCE},\footnote{ovviamente, essendo come detto la funzione + totalmente specifica di Linux, essa non è prevista da nessuno standard e + deve essere evitata se si vogliono scrivere programmi portabili.} è: +\begin{functions} + \headdecl{fcntl.h} + + \funcdecl{} + + Trasferisce dati da un file verso una pipe o viceversa. + + \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di + successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno + dei valori: + \begin{errlist} + \item[\errcode{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: -%\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}} -% 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) -% e http://kerneltrap.org/node/6505 @@ -2769,6 +3031,7 @@ il loro accesso ai dati dei file. % 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/ @@ -2782,6 +3045,7 @@ il loro accesso ai dati dei file. + \section{Il file locking} \label{sec:file_locking} @@ -3196,13 +3460,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 @@ -3221,6 +3478,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 @@ -3673,7 +3937,10 @@ possibilit % 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 ctlv ALL DONT HPUX -% LocalWords: FOLLOW ONESHOT ONLYDIR FreeBSD EIO caching +% 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 +% LocalWords: splice result argument DMA controller zerocopy Linus %%% Local Variables: