From: Simone Piccardi Date: Fri, 18 Sep 2015 19:47:35 +0000 (+0000) Subject: Correzioni varie per splice X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=commitdiff_plain;h=8e03e6dab3729b15c924991f2f4a59079d7e53d7 Correzioni varie per splice --- diff --git a/fileadv.tex b/fileadv.tex index 9437fdb..a61419f 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -5006,10 +5006,18 @@ kernel della serie 2.6,\footnote{per alcune motivazioni di questa scelta si 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}. A partire dal -kernel 2.6.33 però 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}. +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 +trasferimento fra file di dati sia davvero corretta. Se ci sono peggioramenti +di prestazioni infatti si può sempre fare ricorso al metodo ordinario, ma +lasciare a disposizione la funzione consentirebbe se non altro di semplificare +la gestione della copia dei dati fra file, evitando di dover gestire +l'allocazione di un buffer temporaneo per il loro trasferimento. 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 è @@ -5047,23 +5055,22 @@ 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{funcproto}{ \fhead{fcntl.h} \fdecl{long splice(int fd\_in, off\_t *off\_in, int fd\_out, off\_t @@ -5094,12 +5101,12 @@ definito la macro \macro{\_GNU\_SOURCE},\footnote{si ricordi che questa 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 @@ -5141,8 +5148,15 @@ descrizioni complete di tutti i valori possibili anche quando, come per \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 @@ -5156,13 +5170,24 @@ descrizioni complete di tutti i valori possibili anche quando, come per 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 @@ -5178,50 +5203,30 @@ descrizioni complete di tutti i valori possibili anche quando, come per \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} @@ -5232,29 +5237,30 @@ infine (\texttt{\small 28-31}) la \textit{pipe} che verrà usata come buffer. \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 @@ -5264,8 +5270,8 @@ del file di destinazione. Di nuovo si controlla il numero di byte effettivamente scritti restituito in \var{nwrite} e in caso di errore al solito si ripete la scrittura se questo è dovuto a una interruzione o si esce con un messaggio negli altri casi -(\texttt{\small 48-55}). Infine si chiude il ciclo di scrittura sottraendo -(\texttt{\small 57}) il numero di byte scritti a quelli di cui è richiesta la +(\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 diff --git a/listati/splicecp.c b/listati/splicecp.c index 0da067b..52449e6 100644 --- a/listati/splicecp.c +++ b/listati/splicecp.c @@ -9,26 +9,6 @@ int main(int argc, char *argv[]) int in_fd, out_fd; int nread, nwrite; ... - /* Main body */ - if ((argc - optind) != 2) { /* There must two argument */ - printf("Wrong number of arguments %d\n", argc - optind); - usage(); - } - /* open pipe, input and output file */ - in_fd = open(argv[optind], O_RDONLY); - if (in_fd < 0) { - printf("Input error %s on %s\n", strerror(errno), argv[optind]); - exit(EXIT_FAILURE); - } - out_fd = open(argv[optind+1], O_CREAT|O_RDWR|O_TRUNC, 0644); - if (out_fd < 0) { - printf("Cannot open %s, error %s\n", argv[optind+1], strerror(errno)); - exit(EXIT_FAILURE); - } - if (pipe(pipefd) == -1) { - perror("Cannot create buffer pipe"); - exit(EXIT_FAILURE); - } /* copy loop */ while (1) { nread = splice(in_fd, NULL, pipefd[1], NULL, size,