Abbiamo visto in sez.~\ref{sec:sig_gen_beha}, affrontando la suddivisione fra
\textit{fast} e \textit{slow} \textit{system call},\index{system~call~lente}
-che in certi casi le funzioni di I/O eseguite su un file descritor possono
+che in certi casi le funzioni di I/O eseguite su un file descriptor possono
bloccarsi indefinitamente. Questo non avviene mai per i file normali, per i
quali le funzioni di lettura e scrittura ritornano sempre subito, ma può
avvenire per alcuni \index{file!di~dispositivo} file di dispositivo, come ad
caso di errore.
Si tenga presente infine che su Linux, in caso di programmazione
-\textit{multithread} se un file descriptor viene chiuso in un altro
+\textit{multi-thread} se un file descriptor viene chiuso in un altro
\textit{thread} rispetto a quello in cui si sta usando \func{select}, questa
non subisce nessun effetto. In altre varianti di sistemi unix-like invece
\func{select} ritorna indicando che il file descriptor è pronto, con
in questo caso non esiste nessuno standard che richieda questo comportamento.
Infine anche per \func{poll} e \func{ppoll} valgono le considerazioni relative
-alla possibilità di avere delle notificazione spurie della disponibilita di
+alla possibilità di avere delle notificazione spurie della disponibilità di
accesso ai file descriptor illustrate per \func{select} in
sez.~\ref{sec:file_select}, che non staremo a ripetere qui.
database effettuare accessi ai dati in maniera pressoché casuale, mentre un
riproduttore audio o video eseguirà per lo più letture sequenziali.
+\itindend{memory~mapping}
+
Per migliorare le prestazioni a seconda di queste modalità di accesso è
disponibile una apposita funzione, \funcd{madvise},\footnote{tratteremo in
sez.~\ref{sec:file_fadvise} le funzioni che consentono di ottimizzare
l'accesso ai file con l'interfaccia classica.} che consente di fornire al
-kernel delle indicazioni su dette modalità, così che possano essere adottate
-le opportune strategie di ottimizzazione. Il suo prototipo è:
+kernel delle indicazioni su come un processo intende accedere ad un segmento
+di memoria, anche al di là delle mappature dei file, così che possano essere
+adottate le opportune strategie di ottimizzazione. Il suo prototipo è:
\begin{funcproto}{
\fhead{sys/mman.h}
\fdecl{int madvise(void *start, size\_t length, int advice)}
-\fdesc{Fornisce indicazioni sull'uso previsto di un \textit{memory mapping}.}
+\fdesc{Fornisce indicazioni sull'uso previsto di un segmento di memoria.}
}
{La funzione ritorna $0$ in caso di successo e $-1$ per un errore, nel qual
\item[\errcode{EINVAL}] \param{start} non è allineato alla dimensione di
una pagina, \param{length} ha un valore negativo, o \param{advice} non è
un valore valido, o si è richiesto il rilascio (con
- \const{MADV\_DONTNEED}) di pagine bloccate o condivise.
+ \const{MADV\_DONTNEED}) di pagine bloccate o condivise o si è usato
+ \const{MADV\_MERGEABLE} o \const{MADV\_UNMERGEABLE} ma il kernel non è
+ stato compilato per il relativo supporto.
\item[\errcode{EIO}] la paginazione richiesta eccederebbe i limiti (vedi
sez.~\ref{sec:sys_resource_limit}) sulle pagine residenti in memoria del
processo (solo in caso di \const{MADV\_WILLNEED}).
ed inoltre \errval{EAGAIN} e \errval{ENOSYS} nel loro significato generico.}
\end{funcproto}
-
La sezione di memoria sulla quale si intendono fornire le indicazioni deve
essere indicata con l'indirizzo iniziale \param{start} e l'estensione
\param{length}, il valore di \param{start} deve essere allineato,
L'indicazione viene espressa dall'argomento \param{advice} che deve essere
specificato con uno dei valori riportati in
tab.~\ref{tab:madvise_advice_values}; si tenga presente che i valori indicati
-nella seconda parte sono specifici di Linux e non sono previsti dallo standard
-POSIX.1b.
+nella seconda parte della tabella sono specifici di Linux e non sono previsti
+dallo standard POSIX.1b.
+La funzione non ha, tranne il caso di \const{MADV\_DONTFORK}, nessun effetto
+sul comportamento di un programma, ma può influenzarne le prestazioni fornendo
+al kernel indicazioni sulle esigenze dello stesso, così che sia possibile
+scegliere le opportune strategie per la gestione del \itindex{read-ahead}
+\textit{read-ahead} e del caching dei dati.
-\begin{table}[htb]
+\begin{table}[!htb]
\centering
\footnotesize
\begin{tabular}[c]{|l|p{10 cm}|}
\func{madvise}.\\
\const{MADV\_RANDOM} & ci si aspetta un accesso casuale all'area
indicata, pertanto l'applicazione di una lettura
- anticipata con il meccanismo del \textit{read-ahead} (vedi
+ anticipata con il meccanismo del
+ \textit{read-ahead} (vedi
sez.~\ref{sec:file_fadvise}) è di
scarsa utilità e verrà disabilitata.\\
\const{MADV\_SEQUENTIAL}& ci si aspetta un accesso sequenziale al file,
pertanto l'applicazione del \textit{read-ahead}
deve essere incentivata.\\
\hline
- \const{MADV\_REMOVE} & libera un intervallo di pagine di memoria ed il
- relativo supporto sottostante; è supportato
- soltanto sui filesystem in RAM \textit{tmpfs} e
- \textit{shmfs} se usato su altri tipi di
- filesystem causa un errore di \errcode{ENOSYS}
- (dal kernel 2.6.16).\\
+ \const{MADV\_DONTDUMP}& esclude da un \textit{core dump} (vedi
+ sez.~\ref{sec:sig_standard}) le pagine
+ specificate, viene usato per evitare di scrivere
+ su disco dati relativi a zone di memoria che si sa
+ non essere utili in un \textit{core dump}.\\
+ \const{MADV\_DODUMP} & rimuove l'effetto della precedente
+ \const{MADV\_DONTDUMP} (dal kernel 3.4).\\
\const{MADV\_DONTFORK}& impedisce che l'intervallo specificato venga
ereditato dal processo figlio dopo una
\func{fork}; questo consente di evitare che il
meccanismo del \textit{copy on write} effettui la
rilocazione delle pagine quando il padre scrive
- sull'area di memoria dopo la \func{fork}, cosa che può
- causare problemi per l'hardware che esegue
+ sull'area di memoria dopo la \func{fork}, cosa che
+ può causare problemi per l'hardware che esegue
operazioni in DMA su quelle pagine (dal kernel
2.6.16).\\
\const{MADV\_DOFORK} & rimuove l'effetto della precedente
\const{MADV\_DONTFORK} (dal kernel 2.6.16).\\
- \const{MADV\_MERGEABLE}& marca la pagina come accorpabile (indicazione
+ \const{MADV\_HUGEPAGE}& abilita il meccanismo delle \textit{Transparent
+ Huge Page} (vedi sez.~\ref{sec:huge_pages})
+ sulla regione indicata; se questa è allineata
+ alle relative dimensioni il kernel alloca
+ direttamente delle \textit{huge page}; è
+ utilizzabile solo con mappature anomime private
+ (dal kernel 2.6.38).\\
+ \const{MADV\_NOHUGEPAGE}& impedisce che la regione indicata venga
+ collassata in eventuali \textit{huge page} (dal
+ kernel 2.6.38).\\
+ \const{MADV\_HWPOISON} &opzione ad uso di debug per verificare codice
+ che debba gestire errori nella gestione della
+ memoria; richiede una apposita opzione di
+ compilazione del kernel, privilegi amministrativi
+ (la capacità \const{CAP\_SYS\_ADMIN}) e provoca
+ l'emissione di un segnale di \const{SIGBUS} dal
+ programma chiamante e rimozione della mappatura
+ (dal kernel 2.6.32).\\
+ \const{MADV\_SOFT\_OFFLINE}&opzione utilizzata per il debug del
+ codice di verifica degli errori di gestione
+ memoria, richiede una apposita opzione di
+ compilazione (dal kernel 2.6.33).\\
+ \const{MADV\_MERGEABLE}& marca la pagina come accorpabile, indicazione
principalmente ad uso dei sistemi di
- virtualizzazione).\footnotemark\\
- \const{MADV\_UNMERGEABLE}& \\
+ virtualizzazione\footnotemark (dal kernel 2.6.32).\\
+ \const{MADV\_REMOVE} & libera un intervallo di pagine di memoria ed il
+ relativo supporto sottostante; è supportato
+ soltanto sui filesystem in RAM \textit{tmpfs} e
+ \textit{shmfs} se usato su altri tipi di
+ filesystem causa un errore di \errcode{ENOSYS}
+ (dal kernel 2.6.16).\\
+ \const{MADV\_UNMERGEABLE}& rimuove l'effetto della precedente
+ \const{MADV\_MERGEABLE} (dal kernel 2.6.32). \\
\hline
\end{tabular}
\caption{Valori dell'argomento \param{advice} di \func{madvise}.}
\label{tab:madvise_advice_values}
\end{table}
-\footnotetext{.}
-
\footnotetext{a partire dal kernel 2.6.32 è stato introdotto un meccanismo che
identifica pagine di memoria identiche e le accorpa in una unica pagina
(soggetta al \textit{copy-on-write} per successive modifiche); per evitare
la loro occupazione di memoria, ma il meccanismo può essere usato anche in
altre applicazioni in cui sian presenti numerosi processi che usano gli
stessi dati; per maggiori dettagli si veda
- \href{http://kernelnewbies.org/Linux_2_6_32\#head-d3f32e41df508090810388a57efce73f52660ccb}{\texttt{http://kernelnewbies.org/Linux\_2\_6\_32}}.}
+ \href{http://kernelnewbies.org/Linux_2_6_32\#head-d3f32e41df508090810388a57efce73f52660ccb}{\texttt{http://kernelnewbies.org/Linux\_2\_6\_32}}
+ e la documentazione nei sorgenti del kernel
+ (\texttt{Documentation/vm/ksm.txt}).}
-La funzione non ha, tranne il caso di \const{MADV\_DONTFORK}, nessun effetto
-sul comportamento di un programma, ma può influenzarne le prestazioni fornendo
-al kernel indicazioni sulle esigenze dello stesso, così che sia possibile
-scegliere le opportune strategie per la gestione del \itindex{read-ahead}
-\textit{read-ahead} e del caching dei dati. A differenza da quanto specificato
-nello standard POSIX.1b, per il quale l'uso di \func{madvise} è a scopo
-puramente indicativo, Linux considera queste richieste come imperative, per
-cui ritorna un errore qualora non possa soddisfarle.\footnote{questo
- comportamento differisce da quanto specificato nello standard.}
-\itindend{memory~mapping}
+A differenza da quanto specificato nello standard POSIX.1b, per il quale l'uso
+di \func{madvise} è a scopo puramente indicativo, Linux considera queste
+richieste come imperative, per cui ritorna un errore qualora non possa
+soddisfarle; questo comportamento differisce da quanto specificato nello
+standard.
+
+Nello standard POSIX.1-2001 è prevista una ulteriore funzione
+\funcd{posix\_madvise} che su Linux viene reimplementata utilizzando
+\func{madvise}; il suo prototipo è:
+
+\begin{funcproto}{
+\fhead{sys/mman.h}
+\fdecl{int posix\_madvise(void *start, size\_t lenght, int advice)}
+\fdesc{Fornisce indicazioni sull'uso previsto di un segmento di memoria.}
+}
+
+{La funzione ritorna $0$ in caso di successo ed un valore positivo per un
+ errore, nel qual caso \var{errno} assumerà uno dei valori:
+ \begin{errlist}
+ \item[\errcode{EINVAL}] \param{start} non è allineato alla dimensione di
+ una pagina, \param{length} ha un valore negativo, o \param{advice} non è
+ un valore valido.
+ \item[\errcode{ENOMEM}] gli indirizzi specificati non sono nello spazio di
+ indirizzi del processo.
+ \end{errlist}
+}
+\end{funcproto}
+
+Gli argomenti \param{start} e \param{lenght} hanno lo stesso identico
+significato degli analoghi di \func{madvise}, a cui si rimanda per la loro
+descrizione ma a differenza di quanto indicato dallo standard per questa
+funzione, su Linux un valore nullo di \param{len} è consentito.
+
+\begin{table}[!htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|l|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{POSIX\_MADV\_DONTNEED}& analogo a \const{MADV\_DONTNEED}.\\
+ \const{POSIX\_MADV\_NORMAL} & identico a \const{MADV\_NORMAL}.\\
+ \const{POSIX\_MADV\_RANDOM} & identico a \const{MADV\_RANDOM}.\\
+ \const{POSIX\_MADV\_SEQUENTIAL}& identico a \const{MADV\_SEQUENTIAL}.\\
+ \const{POSIX\_MADV\_WILLNEED}& identico a \const{MADV\_WILLNEED}.\\
+ \hline
+ \end{tabular}
+ \caption{Valori dell'argomento \param{advice} di \func{posix\_madvise}.}
+ \label{tab:posix_madvise_advice_values}
+\end{table}
+
+
+L'argomento \param{advice} invece può assumere solo i valori indicati in
+tab.~\ref{tab:posix_madvise_advice_values}, che riflettono gli analoghi di
+\func{madvise}, con lo stesso effetto per tutti tranne
+\const{POSIX\_MADV\_DONTNEED}. Infatti a partire dalle \acr{glibc} 2.6
+\const{POSIX\_MADV\_DONTNEED} viene ignorato, in quanto l'uso del
+corrispondente \const{MADV\_DONTNEED} di \func{madvise} ha, per la semantica
+imperativa, l'effetto immediato di far liberare le pagine da parte del kernel,
+che viene considerato distruttivo.
+
\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 a \func{read} e \func{write}, ci sono casi in cui si vuole poter
-contare sulla atomicità delle operazioni.
+Una seconda modalità di I/O diversa da quella ordinaria è il cosiddetto
+\textsl{I/O vettorizzato}, che nasce per rispondere al caso abbastanza comune
+in cui ci si trova nell'esigenza di dover eseguire una serie multipla di
+operazioni di I/O, come una serie di letture o scritture di vari buffer. Un
+esempio tipico è quando i dati sono strutturati nei campi di una struttura ed
+essi devono essere caricati o salvati su un file. Benché l'operazione sia
+facilmente eseguibile attraverso una serie multipla di chiamate a \func{read}
+e \func{write}, ci sono casi in cui si vuole poter contare sulla atomicità
+delle operazioni di lettura e scrittura rispetto all'esecuzione del programma.
Per questo motivo fino da BSD 4.2 vennero introdotte delle nuove
\textit{system call} che permettessero di effettuare con una sola chiamata una
-serie di letture o scritture su una serie di buffer, con quello che viene
-normalmente chiamato \textsl{I/O vettorizzato}. Queste funzioni sono
+serie di letture da, o scritture su, una serie di buffer, quello che poi venne
+chiamato \textsl{I/O vettorizzato}. Queste funzioni di sistema sono
\funcd{readv} e \funcd{writev},\footnote{in Linux le due funzioni sono riprese
da BSD4.4, esse sono previste anche dallo standard POSIX.1-2001.} ed i
relativi prototipi sono:
-\begin{functions}
- \headdecl{sys/uio.h}
-
- \funcdecl{int readv(int fd, const struct iovec *vector, int count)}
- \funcdecl{int writev(int fd, const struct iovec *vector, int count)}
- 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{funcproto}{
+\fhead{sys/uio.h}
+\fdecl{int readv(int fd, const struct iovec *vector, int count)}
+\fdecl{int writev(int fd, const struct iovec *vector, int count)}
+\fdesc{Eseguono rispettivamente una lettura o una scrittura vettorizzata.}
+}
+
+{Le funzioni ritornano il numero di byte letti o scritti in caso di successo e
+ $-1$ per un errore, nel qual caso \var{errno} assumerà uno dei valori:
\begin{errlist}
- \item[\errcode{EINVAL}] si è specificato un valore non valido per uno degli
+ \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}
+ più tutti i valori, con lo stesso significato, che possono risultare
+ dalle condizioni di errore di \func{read} e \func{write}.
+ }
+\end{funcproto}
+
Entrambe le funzioni usano una struttura \struct{iovec}, la cui definizione è
riportata in fig.~\ref{fig:file_iovec}, che definisce dove i dati devono
bit dell'argomento \param{offset}, che varia a seconda delle architetture,
ma queste differenze vengono gestite dalle funzioni di librerie di libreria
che mantengono l'interfaccia delle analoghe tratte da BSD.}
-\begin{functions}
- \headdecl{sys/uio.h}
-
- \funcdecl{int preadv(int fd, const struct iovec *vector, int count, off\_t
+
+
+\begin{funcproto}{
+\fhead{sys/uio.h}
+\fdecl{int preadv(int fd, const struct iovec *vector, int count, off\_t
offset)}
- \funcdecl{int pwritev(int fd, const struct iovec *vector, int count, off\_t
+\fdecl{int pwritev(int fd, const struct iovec *vector, int count, off\_t
offset)}
+\fdesc{Eseguono una lettura o una scrittura vettorizzata a partire da una data
+ posizione sul file.}
+}
- Eseguono una lettura o una scrittura vettorizzata a partire da una data
- posizione sul file.
-
- \bodydesc{Le funzioni hanno gli stessi valori di ritorno delle
- corrispondenti \func{readv} e \func{writev}; anche gli eventuali errori
- sono gli stessi già visti in precedenza, ma ad essi si possono aggiungere
- per \var{errno} anche i valori:
- \begin{errlist}
- \item[\errcode{EOVERFLOW}] \param{offset} ha un valore che non può essere
- usato come \type{off\_t}.
- \item[\errcode{ESPIPE}] \param{fd} è associato ad un socket o una
- \textit{pipe}.
- \end{errlist}
+{ Le funzioni hanno gli stessi valori di ritorno delle corrispondenti
+ \func{readv} e \func{writev} ed anche gli eventuali errori sono gli stessi,
+ con in più quelli che si possono ottenere dalle possibili condizioni di
+ errore di \func{lseek}.
}
-\end{functions}
+\end{funcproto}
Le due funzioni eseguono rispettivamente una lettura o una scrittura
vettorizzata a partire dalla posizione \param{offset} sul file indicato
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.
+\textit{kernel space} a \textit{user space} e all'indietro, quando in realtà
+potrebbe essere più efficiente mantenere tutto in \textit{kernel
+ space}. Tratteremo in questa sezione alcune funzioni specialistiche che
+permettono di ottimizzare le prestazioni in questo tipo di situazioni.
La prima funzione che è stata ideata per ottimizzare il trasferimento dei dati
-fra due file descriptor è \func{sendfile};\footnote{la funzione è stata
+fra due file descriptor è \func{sendfile}.\footnote{la funzione è stata
introdotta con i kernel della serie 2.2, e disponibile dalle \acr{glibc}
- 2.1.} la funzione è presente in diverse versioni di Unix,\footnote{la si
- ritrova ad esempio in FreeBSD, HPUX ed altri Unix.} ma non è presente né in
-POSIX.1-2001 né in altri standard,\footnote{pertanto si eviti di utilizzarla
- se si devono scrivere programmi portabili.} per cui per essa vengono
-utilizzati prototipi e semantiche differenti; nel caso di Linux il prototipo
-di \funcd{sendfile} è:
-\begin{functions}
- \headdecl{sys/sendfile.h}
+ 2.1.} La funzione è presente in diverse versioni di Unix (la si ritrova ad
+esempio in FreeBSD, HPUX ed altri Unix) ma non è presente né in POSIX.1-2001
+né in altri standard (pertanto si eviti di utilizzarla se si devono scrivere
+programmi portabili) per cui per essa vengono utilizzati prototipi e
+semantiche differenti. Nel caso di Linux il prototipo di \funcd{sendfile} è:
- \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}
+\begin{funcproto}{
+\fhead{sys/sendfile.h}
+\fdecl{ssize\_t sendfile(int out\_fd, int in\_fd, off\_t *offset, size\_t
+ count)}
+\fdesc{Copia dei dati da un file descriptor ad un altro.}
+}
+
+{La funzione ritorna il numero di byte trasferiti in caso di successo e $-1$
+ per un 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
\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}
+ \end{errlist}
+ ed inoltre \errcode{EBADF} e \errcode{EFAULT} nel loro significato
+ generico.}
+\end{funcproto}
La funzione copia direttamente \param{count} byte dal file descriptor
-\param{in\_fd} al file descriptor \param{out\_fd}; in caso di successo
+\param{in\_fd} al file descriptor \param{out\_fd}. In caso di successo la
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}.
+\param{out\_fd} e come per 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
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
+Fino ai kernel della serie 2.4 la funzione era utilizzabile su un qualunque
+file descriptor, e permetteva 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
+sola chiamata a \funcd{sendfile}. In questo modo si poteva 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 ottiene
+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.}
+senza neanche allocare un buffer nel kernel (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
+In seguito però ci si accorse che, fatta eccezione per il trasferimento
diretto da file a socket, non sempre \func{sendfile} comportava miglioramenti
significativi delle prestazioni rispetto all'uso in sequenza di \func{read} e
-\func{write},\footnote{nel caso generico infatti il kernel deve comunque
- allocare un buffer ed effettuare la copia dei dati, e in tal caso spesso il
- guadagno ottenibile nel ridurre il numero di chiamate al sistema non
- compensa le ottimizzazioni che possono essere fatte da una applicazione in
- user space che ha una conoscenza diretta su come questi sono strutturati.} e
-che anzi in certi casi si potevano avere anche dei peggioramenti. Questo ha
-portato, per i kernel della serie 2.6,\footnote{per alcune motivazioni di
- questa scelta si può fare riferimento a quanto illustrato da Linus Torvalds
- in \url{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}.}
-alla decisione di consentire l'uso della funzione soltanto quando il file da
-cui si legge supporta le operazioni di \textit{memory mapping} (vale a dire
-non è un socket) e quello su cui si scrive è un socket; in tutti gli altri
-casi l'uso di \func{sendfile} darà luogo ad un errore di \errcode{EINVAL}.
+\func{write}. 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, per cui in certi
+casi si potevano avere anche dei peggioramenti. Questo ha portato, per i
+kernel della serie 2.6,\footnote{per alcune motivazioni di questa scelta si
+ può fare riferimento a quanto illustrato da Linus Torvalds in
+ \url{http://www.cs.helsinki.fi/linux/linux-kernel/2001-03/0200.html}.} alla
+decisione di consentire l'uso della funzione soltanto quando il file da cui si
+legge supporta le operazioni di \textit{memory mapping} (vale a dire non è un
+socket) e quello su cui si scrive è un socket; in tutti gli altri casi l'uso
+di \func{sendfile} da luogo ad un errore di \errcode{EINVAL}.
Nonostante ci possano essere casi in cui \func{sendfile} non migliora le
prestazioni, resta il dubbio se la scelta di disabilitarla sempre per il
di prestazioni infatti si può sempre fare ricorso al metodo ordinario, ma
lasciare a disposizione la funzione consentirebbe se non altro di semplificare
la gestione della copia dei dati fra file, evitando di dover gestire
-l'allocazione di un buffer temporaneo per il loro trasferimento.
-
-Questo dubbio si può comunque ritenere superato con l'introduzione, avvenuta a
-partire dal kernel 2.6.17, della nuova \textit{system call} \func{splice}. Lo
-scopo di questa funzione è quello di fornire un meccanismo generico per il
-trasferimento di dati da o verso un file utilizzando un buffer gestito
-internamente dal kernel. Descritta in questi termini \func{splice} sembra
-semplicemente un ``\textsl{dimezzamento}'' di \func{sendfile}.\footnote{nel
- senso che un trasferimento di dati fra due file con \func{sendfile} non
- sarebbe altro che la lettura degli stessi su un buffer seguita dalla
- relativa scrittura, cosa che in questo caso si dovrebbe eseguire con due
- chiamate a \func{splice}.} In realtà le due \textit{system call} sono
-profondamente diverse nel loro meccanismo di funzionamento;\footnote{questo
- fino al kernel 2.6.23, dove \func{sendfile} è stata reimplementata in
- termini di \func{splice}, pur mantenendo disponibile la stessa interfaccia
- verso l'user space.} \func{sendfile} infatti, come accennato, non necessita
-di avere a disposizione un buffer interno, perché esegue un trasferimento
-diretto di dati; questo la rende in generale più efficiente, ma anche limitata
-nelle sue applicazioni, dato che questo tipo di trasferimento è possibile solo
-in casi specifici.\footnote{e nel caso di Linux questi sono anche solo quelli
- in cui essa può essere effettivamente utilizzata.}
+l'allocazione di un buffer temporaneo per il loro trasferimento. Comunque a
+partire dal kernel 2.6.33 la restrizione su \param{out\_fd} è stata rimossa e
+questo può essere un file qualunque, rimane però quella di non poter usare un
+socket per \param{in\_fd}.
+
+A partire dal kernel 2.6.17 come alternativa a \func{sendfile} è disponibile
+la nuova \textit{system call} \func{splice}. Lo scopo di questa funzione è
+quello di fornire un meccanismo generico per il trasferimento di dati da o
+verso un file, utilizzando un buffer gestito internamente dal
+kernel. Descritta in questi termini \func{splice} sembra semplicemente un
+``\textsl{dimezzamento}'' di \func{sendfile}, nel senso che un trasferimento
+di dati fra due file con \func{sendfile} non sarebbe altro che la lettura
+degli stessi su un buffer seguita dalla relativa scrittura, cosa che in questo
+caso si dovrebbe eseguire con due chiamate a \func{splice}.
+
+In realtà le due \textit{system call} sono profondamente diverse nel loro
+meccanismo di funzionamento;\footnote{questo fino al kernel 2.6.23, dove
+ \func{sendfile} è stata reimplementata in termini di \func{splice}, pur
+ mantenendo disponibile la stessa interfaccia verso l'user space.}
+\func{sendfile} infatti, come accennato, non necessita di avere a disposizione
+un buffer interno, perché esegue un trasferimento diretto di dati; questo la
+rende in generale più efficiente, ma anche limitata nelle sue applicazioni,
+dato che questo tipo di trasferimento è possibile solo in casi specifici che
+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
dallo stesso Linus Torvalds in \url{http://kerneltrap.org/node/6505}.} si
tratta semplicemente di una funzione che consente di fare in maniera del tutto
generica delle operazioni di trasferimento di dati fra un file e un buffer
-gestito interamente in kernel space. In questo caso il cuore della funzione (e
-delle affini \func{vmsplice} e \func{tee}, che tratteremo più avanti) è
-appunto l'uso di un buffer in kernel space, e questo è anche quello che ne ha
-semplificato l'adozione, perché l'infrastruttura per la gestione di un tale
-buffer è presente fin dagli albori di Unix per la realizzazione delle
+gestito interamente in \textit{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}''.
+\textit{pipe}) con cui utilizzare in \textit{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
+destinazione dei dati che vengono letti da un file. La funzione fornisce
+quindi una interfaccia generica che consente di trasferire dati da un buffer
+ad un file o viceversa; il prototipo di \funcd{splice}, 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
+\begin{funcproto}{
+\fhead{fcntl.h}
+\fdecl{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 \textit{pipe} o viceversa.
+\fdesc{Trasferisce dati da un file verso una \textit{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}
+{La funzione ritorna il numero di byte trasferiti in caso di successo e $-1$
+ per un 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.
richiesta.
\item[\errcode{ESPIPE}] o \param{off\_in} o \param{off\_out} non sono
\val{NULL} ma il corrispondente file descriptor è una \textit{pipe}.
- \end{errlist}
- }
-\end{functions}
+ \end{errlist}
+}
+\end{funcproto}
+
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.
+una \textit{pipe}; l'altro file descriptor può essere qualunque, 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 \textit{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
\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}.\\
+ copiarle: 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 \textit{pipe} o
+ il buffer non corrisponda a pagine intere
+ esse saranno comunque copiate. 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
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
+ socket. 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}). 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
+ ``\textsl{donate}'' al kernel; 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.
+ 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
\label{tab:splice_flag}
\end{table}
-\footnotetext[120]{per una maggiore efficienza \func{splice} usa quando
- possibile i meccanismi della memoria virtuale per eseguire i trasferimenti
- di dati (in maniera analoga a \func{mmap}), qualora le pagine non possano
- essere spostate dalla \textit{pipe} o il buffer non corrisponda a pagine
- intere esse saranno comunque copiate.}
-
-\footnotetext[121]{questa opzione consente di utilizzare delle opzioni di
- gestione dei socket che permettono di ottimizzare le trasmissioni via rete,
- si veda la descrizione di \const{TCP\_CORK} in
- sez.~\ref{sec:sock_tcp_udp_options} e quella di \const{MSG\_MORE} in
- sez.~\ref{sec:net_sendmsg}.}
-
-\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}.
+su un altro senza utilizzare buffer in user space. Lo scopo del programma è
+quello di eseguire la copia dei dati con \func{splice}, questo significa che
+si dovrà usare la funzione due volte, prima per leggere i dati dal file di
+ingresso e poi per scriverli su quello di uscita, appoggiandosi 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}
+ \includegraphics[height=4cm]{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.
+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, le funzioni di ausilio, le
+aperture dei file di ingresso e di uscita passati come argomenti e quella
+della \textit{pipe} intermedia, è riportato in fig.~\ref{fig:splice_example}.
-\begin{figure}[!htbp]
+\begin{figure}[!htb]
\footnotesize \centering
\begin{minipage}[c]{\codesamplewidth}
\includecodesample{listati/splicecp.c}
\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
+Il ciclo principale (\texttt{\small 13-38}) inizia con la lettura dal file
+sorgente tramite la prima \func{splice} (\texttt{\small 14-15}), 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
+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).
+semplicemente al trasferimento dei dati dal file al buffer in \textit{kernel
+ space}.
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
+detto valore è nullo (\texttt{\small 16}) 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
+ 59}). In caso di valore negativo (\texttt{\small 17-24}) c'è stato un
+errore ed allora si ripete la lettura (\texttt{\small 16}) se questo è dovuto
ad una interruzione, o altrimenti si esce con un messaggio di errore
-(\texttt{\small 41-43}).
+(\texttt{\small 21-23}).
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
+(\texttt{\small 25-37}); questo inizia (\texttt{\small 26-27}) 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
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
+(\texttt{\small 28-35}). Infine si chiude il ciclo di scrittura sottraendo
+(\texttt{\small 37}) 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
% nel kernel 3.15 (sul secondo vedi http://lwn.net/Articles/589260/), vedi
% anche http://lwn.net/Articles/629965/
+% TODO aggiungere FALLOC_FL_INSERT vedi http://lwn.net/Articles/629965/
+
+
% TODO non so dove trattarli, ma dal 2.6.39 ci sono i file handle, vedi
% http://lwn.net/Articles/432757/
% LocalWords: clockid CLOCK MONOTONIC REALTIME itimerspec interval Resource
% LocalWords: ABSTIME gettime temporarily unavailable SIGINT SIGQUIT SIGTERM
% LocalWords: sigfd fifofd break siginf names starting echo Message from Got
-% LocalWords: message kill received means exit
+% LocalWords: message kill received means exit TLOCK ULOCK EPOLLWAKEUP
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "gapil"
%%% End:
+