- \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 presentano nella gestione dell'I/O è quello in cui si
-devono trasferire grandi quantità di dati da un file descriptor ed un altro;
-questo usualmente comporta la lettura dei dati dal primo file descriptor in un
-buffer in memoria, da cui essi vengono poi scritti sul secondo.
-
-Benché il kernel ottimizzi la gestione di questo processo quando si ha a che
-fare con file normali, in generale quando i dati da trasferire sono molti si
-pone il problema di effettuare trasferimenti di grandi quantità di dati da
-kernel space a user space e all'indietro, quando in realtà potrebbe essere più
-efficiente mantenere tutto in kernel space. Tratteremo in questa sezione
-alcune funzioni specialistiche che permettono di ottimizzare le prestazioni in
-questo tipo di situazioni.
-
-La prima funzione che si pone l'obiettivo di ottimizzare il trasferimento dei
-dati fra due file descriptor è \funcd{sendfile};\footnote{la funzione è stata
- introdotta con i kernel della serie 2.2, e disponibile dalle \acr{glibc}
- 2.1.} la funzione è presente in diverse versioni di Unix,\footnote{la si
- ritrova ad esempio in FreeBSD, HPUX ed altri Unix.} ma non è presente né in
-POSIX.1-2001 né in altri standard,\footnote{pertanto si eviti di utilizzarla
- se si devono scrivere programmi portabili.} per cui per essa vengono
-utilizzati prototipi e semantiche differenti; nel caso di Linux il suo
-prototipo è:
-\begin{functions}
- \headdecl{sys/sendfile.h}
-
- \funcdecl{ssize\_t sendfile(int out\_fd, int in\_fd, off\_t *offset, size\_t
- count)}
-
- Copia dei dati da un file descriptor ad un altro.
-
- \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di
- successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno
- dei valori:
- \begin{errlist}
- \item[\errcode{EAGAIN}] si è impostata la modalità non bloccante su
- \param{out\_fd} e la scrittura si bloccherebbe.
- \item[\errcode{EINVAL}] i file descriptor non sono validi, o sono bloccati
- (vedi sez.~\ref{sec:file_locking}), o \func{mmap} non è disponibile per
- \param{in\_fd}.
- \item[\errcode{EIO}] si è avuto un errore di lettura da \param{in\_fd}.
- \item[\errcode{ENOMEM}] non c'è memoria sufficiente per la lettura da
- \param{in\_fd}.
- \end{errlist}
- ed inoltre \errcode{EBADF} e \errcode{EFAULT}.
- }
-\end{functions}
-
-La funzione copia direttamente \param{count} byte dal file descriptor
-\param{in\_fd} al file descriptor \param{out\_fd}; in caso di successo
-funzione ritorna il numero di byte effettivamente copiati da \param{in\_fd} a
-\param{out\_fd} o $-1$ in caso di errore, come le ordinarie \func{read} e
-\func{write} questo valore può essere inferiore a quanto richiesto con
-\param{count}.
-
-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}
- {\texttt{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}
- {\texttt{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}
-
- \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.
-
- \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}