X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=fileadv.tex;h=c3be626d21908e1b90ee2b69b2771e306f369b50;hp=e99b81bb3ca7f1c59888dd6f020bab73da0baf03;hb=0c4a9ed958f4797e1cf4dc90e0c0358e302956f5;hpb=6760429dc70f868716393c4413c5450b0649869b diff --git a/fileadv.tex b/fileadv.tex index e99b81b..c3be626 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -940,7 +940,7 @@ I/O. 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 @@ -1127,7 +1127,7 @@ funzione invece i \textit{file descriptor set} non vengono modificati anche in 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 @@ -1227,14 +1227,14 @@ una variabile locale, in modo da mantenere l'aderenza allo standard POSIX che richiede che il valore di \param{timeout} non sia modificato. Rispetto a \func{select} la nuova funzione prende un argomento -aggiuntivo \param{sigmask}, un puntatore ad una \index{maschera~dei~segnali} -maschera di segnali (si veda sez.~\ref{sec:sig_sigmask}). Nell'esecuzione la -maschera dei segnali corrente viene sostituita da quella così indicata -immediatamente prima di eseguire l'attesa, e viene poi ripristinata al ritorno -della funzione. L'uso di \param{sigmask} è stato introdotto allo scopo di -prevenire possibili \textit{race condition} \itindex{race~condition} quando -oltre alla presenza di dati sui file descriptor come nella \func{select} -ordinaria, ci si deve porre in attesa anche dell'arrivo di un segnale. +aggiuntivo \param{sigmask}, un puntatore ad una maschera di segnali (si veda +sez.~\ref{sec:sig_sigmask}). Nell'esecuzione la maschera dei segnali corrente +viene sostituita da quella così indicata immediatamente prima di eseguire +l'attesa, e viene poi ripristinata al ritorno della funzione. L'uso +di \param{sigmask} è stato introdotto allo scopo di prevenire possibili +\textit{race condition} \itindex{race~condition} quando oltre alla presenza di +dati sui file descriptor come nella \func{select} ordinaria, ci si deve porre +in attesa anche dell'arrivo di un segnale. Come abbiamo visto in sez.~\ref{sec:sig_example} la tecnica classica per rilevare l'arrivo di un segnale è quella di utilizzare il gestore per @@ -1285,9 +1285,9 @@ Nello sviluppo di System V, invece di utilizzare l'interfaccia di \func{select}, che è una estensione tipica di BSD, è stata introdotta una interfaccia completamente diversa, basata sulla funzione di sistema \funcd{poll},\footnote{la funzione è prevista dallo standard XPG4, ed è stata - introdotta in Linux come system call a partire dal kernel 2.1.23 ed inserita - nelle \acr{libc} 5.4.28, originariamente l'argomento \param{nfds} era di - tipo \ctyp{unsigned int}, la funzione è stata inserita nello standard + introdotta in Linux come \textit{system call} a partire dal kernel 2.1.23 ed + inserita nelle \acr{libc} 5.4.28, originariamente l'argomento \param{nfds} + era di tipo \ctyp{unsigned int}, la funzione è stata inserita nello standard POSIX.1-2001 in cui è stato introdotto il tipo nativo \type{nfds\_t}.} il cui prototipo è: @@ -1480,12 +1480,11 @@ ed inoltre \errval{EFAULT} e \errval{ENOMEM} nel loro significato generico. \end{funcproto} La funzione ha lo stesso comportamento di \func{poll}, solo che si può -specificare, con l'argomento \param{sigmask}, il puntatore ad una -\index{maschera~dei~segnali} maschera di segnali; questa sarà la maschera -utilizzata per tutto il tempo che la funzione resterà in attesa, all'uscita -viene ripristinata la maschera originale. L'uso di questa funzione è cioè -equivalente, come illustrato nella pagina di manuale, all'esecuzione atomica -del seguente codice: +specificare, con l'argomento \param{sigmask}, il puntatore ad una maschera di +segnali; questa sarà la maschera utilizzata per tutto il tempo che la funzione +resterà in attesa, all'uscita viene ripristinata la maschera originale. L'uso +di questa funzione è cioè equivalente, come illustrato nella pagina di +manuale, all'esecuzione atomica del seguente codice: \includecodesnip{listati/ppoll_means.c} Eccetto per \param{timeout}, che come per \func{pselect} deve essere un @@ -1499,7 +1498,7 @@ questo comportamento non modificando mai il valore di \param{timeout} anche se 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. @@ -1965,12 +1964,11 @@ Come già per \func{select} e \func{poll} anche per l'interfaccia di contemporaneamente. Valgono le osservazioni fatte in sez.~\ref{sec:file_select}, e per poterlo fare di nuovo è necessaria una variante della funzione di attesa che consenta di reimpostare all'uscita una -\index{maschera~dei~segnali} maschera di segnali, analoga alle estensioni -\func{pselect} e \func{ppoll} che abbiamo visto in precedenza per -\func{select} e \func{poll}. In questo caso la funzione di sistema si chiama -\funcd{epoll\_pwait}\footnote{la funzione è stata introdotta a partire dal - kernel 2.6.19, ed è come tutta l'interfaccia di \textit{epoll}, specifica di - Linux.} ed il suo prototipo è: +maschera di segnali, analoga alle estensioni \func{pselect} e \func{ppoll} che +abbiamo visto in precedenza per \func{select} e \func{poll}. In questo caso la +funzione di sistema si chiama \funcd{epoll\_pwait}\footnote{la funzione è + stata introdotta a partire dal kernel 2.6.19, ed è, come tutta l'interfaccia + di \textit{epoll}, specifica di Linux.} ed il suo prototipo è: \begin{funcproto}{ \fhead{sys/epoll.h} @@ -1989,10 +1987,10 @@ variante della funzione di attesa che consenta di reimpostare all'uscita una \end{funcproto} La funzione è del tutto analoga \funcd{epoll\_wait}, soltanto che alla sua -uscita viene ripristinata la \index{maschera~dei~segnali} maschera di segnali -originale, sostituita durante l'esecuzione da quella impostata con -l'argomento \param{sigmask}; in sostanza la chiamata a questa funzione è -equivalente al seguente codice, eseguito però in maniera atomica: +uscita viene ripristinata la maschera di segnali originale, sostituita durante +l'esecuzione da quella impostata con l'argomento \param{sigmask}; in sostanza +la chiamata a questa funzione è equivalente al seguente codice, eseguito però +in maniera atomica: \includecodesnip{listati/epoll_pwait_means.c} Si tenga presente che come le precedenti funzioni di \textit{I/O multiplexing} @@ -2081,8 +2079,8 @@ tramite file descriptor è \funcd{signalfd},\footnote{in realtà quella versione, \funcm{signalfd4}, introdotta con il kernel 2.6.27 e che è quella che viene sempre usata a partire dalle \acr{glibc} 2.9, che prende un argomento aggiuntivo \code{size\_t sizemask} che indica la dimensione della - \index{maschera~dei~segnali} maschera dei segnali, il cui valore viene - impostato automaticamente dalle \acr{glibc}.} il cui prototipo è: + maschera dei segnali, il cui valore viene impostato automaticamente dalle + \acr{glibc}.} il cui prototipo è: \begin{funcproto}{ \fhead{sys/signalfd.h} @@ -2120,13 +2118,13 @@ con \param{fd}, in caso di errore invece verrà restituito $-1$. L'elenco dei segnali che si vogliono gestire con \func{signalfd} deve essere specificato tramite l'argomento \param{mask}. Questo deve essere passato come -puntatore ad una \index{maschera~dei~segnali} maschera di segnali creata con -l'uso delle apposite macro già illustrate in sez.~\ref{sec:sig_sigset}. La -maschera deve indicare su quali segnali si intende operare con -\func{signalfd}; l'elenco può essere modificato con una successiva chiamata a -\func{signalfd}. Dato che \signal{SIGKILL} e \signal{SIGSTOP} non possono -essere intercettati (e non prevedono neanche la possibilità di un gestore) un -loro inserimento nella maschera verrà ignorato senza generare errori. +puntatore ad una maschera di segnali creata con l'uso delle apposite macro già +illustrate in sez.~\ref{sec:sig_sigset}. La maschera deve indicare su quali +segnali si intende operare con \func{signalfd}; l'elenco può essere modificato +con una successiva chiamata a \func{signalfd}. Dato che \signal{SIGKILL} e +\signal{SIGSTOP} non possono essere intercettati (e non prevedono neanche la +possibilità di un gestore) un loro inserimento nella maschera verrà ignorato +senza generare errori. L'argomento \param{flags} consente di impostare direttamente in fase di creazione due flag per il file descriptor analoghi a quelli che si possono @@ -2285,13 +2283,13 @@ Il primo passo (\texttt{\small 19-20}) è la creazione di un file descriptor quello che useremo per il controllo degli altri. É poi necessario disabilitare la ricezione dei segnali (nel caso \signal{SIGINT}, \signal{SIGQUIT} e \signal{SIGTERM}) per i quali si vuole la notifica tramite -file descriptor. Per questo prima li si inseriscono (\texttt{\small 22-25}) -in una \index{maschera~dei~segnali} maschera di segnali \texttt{sigmask} che -useremo con (\texttt{\small 26}) \func{sigprocmask} per disabilitarli. Con la -stessa maschera si potrà per passare all'uso (\texttt{\small 28-29}) di -\func{signalfd} per abilitare la notifica sul file descriptor -\var{sigfd}. Questo poi (\texttt{\small 30-33}) dovrà essere aggiunto con -\func{epoll\_ctl} all'elenco di file descriptor controllati con \texttt{epfd}. +file descriptor. Per questo prima li si inseriscono (\texttt{\small 22-25}) in +una maschera di segnali \texttt{sigmask} che useremo con (\texttt{\small 26}) +\func{sigprocmask} per disabilitarli. Con la stessa maschera si potrà per +passare all'uso (\texttt{\small 28-29}) di \func{signalfd} per abilitare la +notifica sul file descriptor \var{sigfd}. Questo poi (\texttt{\small 30-33}) +dovrà essere aggiunto con \func{epoll\_ctl} all'elenco di file descriptor +controllati con \texttt{epfd}. Occorrerà infine (\texttt{\small 35-38}) creare la \textit{named fifo} se questa non esiste ed aprirla per la lettura (\texttt{\small 39-40}); una @@ -2764,8 +2762,8 @@ funzionalità che serve soltanto in alcuni casi particolari. Dato che all'origine di Unix i soli programmi che potevano avere una tale esigenza erano i demoni, attenendosi a uno dei criteri base della progettazione, che era di far fare al kernel solo le operazioni strettamente necessarie e -lasciare tutto il resto a processi in user space, non era stata prevista -nessuna funzionalità di notifica. +lasciare tutto il resto a processi in \textit{user space}, non era stata +prevista nessuna funzionalità di notifica. Visto però il crescente interesse nei confronti di una funzionalità di questo tipo, che è molto richiesta specialmente nello sviluppo dei programmi ad @@ -3515,11 +3513,11 @@ dedicate per la lettura e la scrittura dei file, completamente separate rispetto a quelle usate normalmente. In generale questa interfaccia è completamente astratta e può essere -implementata sia direttamente nel kernel che in user space attraverso l'uso di -\itindex{thread} \textit{thread}. Per le versioni del kernel meno recenti -esiste una implementazione di questa interfaccia fornita completamente delle -\acr{glibc} a partire dalla versione 2.1, che è realizzata completamente in -user space, ed è accessibile linkando i programmi con la libreria +implementata sia direttamente nel kernel che in \textit{user space} attraverso +l'uso di \itindex{thread} \textit{thread}. Per le versioni del kernel meno +recenti esiste una implementazione di questa interfaccia fornita completamente +delle \acr{glibc} a partire dalla versione 2.1, che è realizzata completamente +in \textit{user space}, ed è accessibile linkando i programmi con la libreria \file{librt}. A partire dalla versione 2.5.32 è stato introdotto nel kernel una nuova infrastruttura per l'I/O asincrono, ma ancora il supporto è parziale ed insufficiente ad implementare tutto l'AIO POSIX. @@ -3922,13 +3920,12 @@ interfaccia è più efficiente delle usuali funzioni di I/O, in quanto permette di caricare in memoria solo le parti del file che sono effettivamente usate ad un dato istante. -Infatti, dato che l'accesso è fatto direttamente attraverso la -\index{memoria~virtuale} memoria virtuale, la sezione di memoria mappata su -cui si opera sarà a sua volta letta o scritta sul file una pagina alla volta e -solo per le parti effettivamente usate, il tutto in maniera completamente -trasparente al processo; l'accesso alle pagine non ancora caricate avverrà -allo stesso modo con cui vengono caricate in memoria le pagine che sono state -salvate sullo \textit{swap}. +Infatti, dato che l'accesso è fatto direttamente attraverso la memoria +virtuale, la sezione di memoria mappata su cui si opera sarà a sua volta letta +o scritta sul file una pagina alla volta e solo per le parti effettivamente +usate, il tutto in maniera completamente trasparente al processo; l'accesso +alle pagine non ancora caricate avverrà allo stesso modo con cui vengono +caricate in memoria le pagine che sono state salvate sullo \textit{swap}. Infine in situazioni in cui la memoria è scarsa, le pagine che mappano un file vengono salvate automaticamente, così come le pagine dei programmi vengono @@ -4354,33 +4351,30 @@ Lo standard POSIX prevede anche una funzione che permetta di cambiare le protezioni delle pagine di memoria; lo standard prevede che essa si applichi solo ai \textit{memory mapping} creati con \func{mmap}, ma nel caso di Linux la funzione può essere usata con qualunque pagina valida nella memoria -virtuale. Questa funzione è \funcd{mprotect} ed il suo prototipo è: -\begin{functions} -% \headdecl{unistd.h} - \headdecl{sys/mman.h} +virtuale. Questa funzione di sistema è \funcd{mprotect} ed il suo prototipo è: - \funcdecl{int mprotect(const void *addr, size\_t len, int prot)} - - Modifica le protezioni delle pagine di memoria comprese nell'intervallo - specificato. +\begin{funcproto}{ +\fhead{sys/mman.h} +\fdecl{int mprotect(const void *addr, size\_t len, int prot)} +\fdesc{Modifica le protezioni delle pagine di memoria.} +} - \bodydesc{La funzione restituisce 0 in caso di successo, e -1 in caso di - errore nel qual caso \var{errno} assumerà uno dei valori: - \begin{errlist} +{La funzione ritorna $0$ in caso di successo e $-1$ per un errore, nel qual + caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EINVAL}] il valore di \param{addr} non è valido o non è un multiplo di \const{PAGE\_SIZE}. \item[\errcode{EACCES}] l'operazione non è consentita, ad esempio si è cercato di marcare con \const{PROT\_WRITE} un segmento di memoria cui si ha solo accesso in lettura. -% \item[\errcode{ENOMEM}] non è stato possibile allocare le risorse -% necessarie all'interno del kernel. -% \item[\errcode{EFAULT}] si è specificato un indirizzo di memoria non -% accessibile. - \end{errlist} - ed inoltre \errval{ENOMEM} ed \errval{EFAULT}. - } -\end{functions} - + \item[\errcode{ENOMEM}] non è stato possibile allocare le risorse + necessarie all'interno del kernel o si è specificato un indirizzo di + memoria non valido del processo o non corrispondente a pagine mappate + (negli ultimi due casi prima del kernel 2.4.19 veniva prodotto, + erroneamente, \errcode{EFAULT}). + \end{errlist} +} +\end{funcproto} La funzione prende come argomenti un indirizzo di partenza in \param{addr}, allineato alle dimensioni delle pagine di memoria, ed una dimensione @@ -4390,23 +4384,23 @@ protezione verrà applicata a tutte le pagine contenute, anche parzialmente, dall'intervallo fra \param{addr} e \param{addr}+\param{size}-1. Infine Linux supporta alcune operazioni specifiche non disponibili su altri -kernel unix-like. La prima di queste è la possibilità di modificare un -precedente \textit{memory mapping}, ad esempio per espanderlo o restringerlo. -Questo è realizzato dalla funzione \funcd{mremap}, il cui prototipo è: -\begin{functions} - \headdecl{unistd.h} - \headdecl{sys/mman.h} +kernel unix-like per poter usare le quali occorre però dichiarare +\macro{\_GNU\_SOURCE} prima dell'inclusione di \texttt{sys/mman.h}. La prima +di queste è la possibilità di modificare un precedente \textit{memory + mapping}, ad esempio per espanderlo o restringerlo. Questo è realizzato +dalla funzione di sistema \funcd{mremap}, il cui prototipo è: - \funcdecl{void * mremap(void *old\_address, size\_t old\_size , size\_t +\begin{funcproto}{ +\fhead{sys/mman.h} +\fdecl{void * mremap(void *old\_address, size\_t old\_size , size\_t new\_size, unsigned long flags)} - - Restringe o allarga una mappatura in memoria di un file. +\fdesc{Restringe o allarga una mappatura in memoria.} +} - \bodydesc{La funzione restituisce l'indirizzo alla nuova area di memoria in - caso di successo od il valore \const{MAP\_FAILED} (pari a \texttt{(void *) - -1}) in caso di errore, nel qual caso \var{errno} assumerà uno dei - valori: - \begin{errlist} +{La funzione ritorna l'indirizzo alla nuova area di memoria in caso di + successo o il valore \const{MAP\_FAILED} (pari a \texttt{(void *) -1}), nel + qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EINVAL}] il valore di \param{old\_address} non è un puntatore valido. \item[\errcode{EFAULT}] ci sono indirizzi non validi nell'intervallo @@ -4417,9 +4411,9 @@ Questo è realizzato dalla funzione \funcd{mremap}, il cui prototipo è: è specificato \const{MREMAP\_MAYMOVE} nei flag. \item[\errcode{EAGAIN}] il segmento di memoria scelto è bloccato e non può essere rimappato. - \end{errlist} - } -\end{functions} + \end{errlist} +} +\end{funcproto} La funzione richiede come argomenti \param{old\_address} (che deve essere allineato alle dimensioni di una pagina di memoria) che specifica il @@ -4427,80 +4421,77 @@ precedente indirizzo del \textit{memory mapping} e \param{old\_size}, che ne indica la dimensione. Con \param{new\_size} si specifica invece la nuova dimensione che si vuole ottenere. Infine l'argomento \param{flags} è una maschera binaria per i flag che controllano il comportamento della funzione. -Il solo valore utilizzato è \const{MREMAP\_MAYMOVE}\footnote{per poter - utilizzare questa costante occorre aver definito \macro{\_GNU\_SOURCE} prima - di includere \headfile{sys/mman.h}.} che consente di eseguire l'espansione -anche quando non è possibile utilizzare il precedente indirizzo. Per questo -motivo, se si è usato questo flag, la funzione può restituire un indirizzo -della nuova zona di memoria che non è detto coincida con \param{old\_address}. - -La funzione si appoggia al sistema della \index{memoria~virtuale} memoria -virtuale per modificare l'associazione fra gli indirizzi virtuali del processo -e le pagine di memoria, modificando i dati direttamente nella -\itindex{page~table} \textit{page table} del processo. Come per -\func{mprotect} la funzione può essere usata in generale, anche per pagine di -memoria non corrispondenti ad un \textit{memory mapping}, e consente così di -implementare la funzione \func{realloc} in maniera molto efficiente. +Il solo valore utilizzato è \const{MREMAP\_MAYMOVE} che consente di eseguire +l'espansione anche quando non è possibile utilizzare il precedente +indirizzo. Per questo motivo, se si è usato questo flag, la funzione può +restituire un indirizzo della nuova zona di memoria che non è detto coincida +con \param{old\_address}. + +La funzione si appoggia al sistema della memoria virtuale per modificare +l'associazione fra gli indirizzi virtuali del processo e le pagine di memoria, +modificando i dati direttamente nella \textit{page table} del processo. Come +per \func{mprotect} la funzione può essere usata in generale, anche per pagine +di memoria non corrispondenti ad un \textit{memory mapping}, e consente così +di implementare la funzione \func{realloc} in maniera molto efficiente. Una caratteristica comune a tutti i sistemi unix-like è che la mappatura in memoria di un file viene eseguita in maniera lineare, cioè parti successive di un file vengono mappate linearmente su indirizzi successivi in memoria. -Esistono però delle applicazioni\footnote{in particolare la tecnica è usata - dai database o dai programmi che realizzano macchine virtuali.} in cui è -utile poter mappare sezioni diverse di un file su diverse zone di memoria. +Esistono però delle applicazioni (in particolare la tecnica è usata dai +database o dai programmi che realizzano macchine virtuali) in cui è utile +poter mappare sezioni diverse di un file su diverse zone di memoria. Questo è ovviamente sempre possibile eseguendo ripetutamente la funzione \func{mmap} per ciascuna delle diverse aree del file che si vogliono mappare -in sequenza non lineare,\footnote{ed in effetti è quello che veniva fatto - anche con Linux prima che fossero introdotte queste estensioni.} ma questo -approccio ha delle conseguenze molto pesanti in termini di prestazioni. -Infatti per ciascuna mappatura in memoria deve essere definita nella -\itindex{page~table} \textit{page table} del processo una nuova area di -memoria virtuale\footnote{quella che nel gergo del kernel viene chiamata VMA - (\textit{virtual memory area}).} che corrisponda alla mappatura, in modo che -questa diventi visibile nello spazio degli indirizzi come illustrato in -fig.~\ref{fig:file_mmap_layout}. - -Quando un processo esegue un gran numero di mappature diverse\footnote{si può - arrivare anche a centinaia di migliaia.} per realizzare a mano una mappatura -non-lineare si avrà un accrescimento eccessivo della sua \itindex{page~table} -\textit{page table}, e lo stesso accadrà per tutti gli altri processi che -utilizzano questa tecnica. In situazioni in cui le applicazioni hanno queste -esigenze si avranno delle prestazioni ridotte, dato che il kernel dovrà -impiegare molte risorse\footnote{sia in termini di memoria interna per i dati - delle \itindex{page~table} \textit{page table}, che di CPU per il loro - aggiornamento.} solo per mantenere i dati di una gran quantità di -\textit{memory mapping}. +in sequenza non lineare (ed in effetti è quello che veniva fatto anche con +Linux prima che fossero introdotte queste estensioni) ma questo approccio ha +delle conseguenze molto pesanti in termini di prestazioni. Infatti per +ciascuna mappatura in memoria deve essere definita nella \textit{page table} +del processo una nuova area di memoria virtuale, quella che nel gergo del +kernel viene chiamata VMA (\textit{virtual memory area}, che corrisponda alla +mappatura, in modo che questa diventi visibile nello spazio degli indirizzi +come illustrato in fig.~\ref{fig:file_mmap_layout}. + +Quando un processo esegue un gran numero di mappature diverse (si può arrivare +anche a centinaia di migliaia) per realizzare a mano una mappatura non-lineare +esso vedrà un accrescimento eccessivo della sua \textit{page table}, e lo +stesso accadrà per tutti gli altri processi che utilizzano questa tecnica. In +situazioni in cui le applicazioni hanno queste esigenze si avranno delle +prestazioni ridotte, dato che il kernel dovrà impiegare molte risorse per +mantenere i dati relativi al \textit{memory mapping}, sia in termini di +memoria interna per i dati delle \textit{page table}, che di CPU per il loro +aggiornamento. Per questo motivo con il kernel 2.5.46 è stato introdotto, ad opera di Ingo Molnar, un meccanismo che consente la mappatura non-lineare. Anche questa è una caratteristica specifica di Linux, non presente in altri sistemi -unix-like. Diventa così possibile utilizzare una sola mappatura -iniziale\footnote{e quindi una sola \textit{virtual memory area} nella - \itindex{page~table} \textit{page table} del processo.} e poi rimappare a -piacere all'interno di questa i dati del file. Ciò è possibile grazie ad una -nuova \textit{system call}, \funcd{remap\_file\_pages}, il cui prototipo è: -\begin{functions} - \headdecl{sys/mman.h} +unix-like. Diventa così possibile utilizzare una sola mappatura iniziale, e +quindi una sola \textit{virtual memory area} nella \textit{page table} del +processo, e poi rimappare a piacere all'interno di questa i dati del file. Ciò +è possibile grazie ad una nuova \textit{system call}, +\funcd{remap\_file\_pages}, il cui prototipo è: - \funcdecl{int remap\_file\_pages(void *start, size\_t size, int prot, +\begin{funcproto}{ +\fhead{sys/mman.h} +\fdecl{int remap\_file\_pages(void *start, size\_t size, int prot, ssize\_t pgoff, int flags)} - - Permette di rimappare non linearmente un precedente \textit{memory mapping}. +\fdesc{Rimappa non linearmente un \textit{memory mapping}.} +} - \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: - \begin{errlist} +{La funzione ritorna $0$ in caso di successo e $-1$ per un errore, nel qual + caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EINVAL}] si è usato un valore non valido per uno degli argomenti o \param{start} non fa riferimento ad un \textit{memory mapping} valido creato con \const{MAP\_SHARED}. - \end{errlist} - } -\end{functions} + \end{errlist} + ed inoltre + nel loro significato generico.} +\end{funcproto} Per poter utilizzare questa funzione occorre anzitutto effettuare preliminarmente una chiamata a \func{mmap} con \const{MAP\_SHARED} per -definire l'area di memoria che poi sarà rimappata non linearmente. Poi di +definire l'area di memoria che poi sarà rimappata non linearmente. Poi si chiamerà questa funzione per modificare le corrispondenze fra pagine di memoria e pagine del file; si tenga presente che \func{remap\_file\_pages} permette anche di mappare la stessa pagina di un file in più pagine della @@ -4532,12 +4523,12 @@ corrispondenza dell'accesso a ciascuna delle pagine interessate dal \textit{memory mapping}. Questo vuol dire che il passaggio dei dati dal disco alla memoria avverrà una -pagina alla volta con un gran numero di \itindex{page~fault} \textit{page - fault}, chiaramente se si sa in anticipo che il file verrà utilizzato -immediatamente, è molto più efficiente eseguire un \textit{prefaulting} in cui -tutte le pagine di memoria interessate alla mappatura vengono -``\textsl{popolate}'' in una sola volta, questo comportamento viene abilitato -quando si usa con \func{mmap} il flag \const{MAP\_POPULATE}. +pagina alla volta con un gran numero di \textit{page fault}, chiaramente se si +sa in anticipo che il file verrà utilizzato immediatamente, è molto più +efficiente eseguire un \textit{prefaulting} in cui tutte le pagine di memoria +interessate alla mappatura vengono ``\textsl{popolate}'' in una sola volta, +questo comportamento viene abilitato quando si usa con \func{mmap} il flag +\const{MAP\_POPULATE}. Dato che l'uso di \const{MAP\_POPULATE} comporta dell'I/O su disco che può rallentare l'esecuzione di \func{mmap} è stato introdotto anche un secondo @@ -4556,52 +4547,63 @@ verranno eseguiti gli accessi ad un file; è ad esempio molto comune per i 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 è: -\begin{functions} - \headdecl{sys/mman.h} +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 è: - \funcdecl{int madvise(void *start, size\_t length, int advice)} - - Fornisce indicazioni sull'uso previsto di un \textit{memory mapping}. +\begin{funcproto}{ +\fhead{sys/mman.h} +\fdecl{int madvise(void *start, size\_t length, int advice)} +\fdesc{Fornisce indicazioni sull'uso previsto di un segmento di memoria.} +} - \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: - \begin{errlist} +{La funzione ritorna $0$ in caso di successo e $-1$ per un errore, nel qual + caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EBADF}] la mappatura esiste ma non corrisponde ad un file. \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}). \item[\errcode{ENOMEM}] gli indirizzi specificati non sono mappati, o, in caso \const{MADV\_WILLNEED}, non c'è sufficiente memoria per soddisfare la richiesta. - \end{errlist} - ed inoltre \errval{EAGAIN} e \errval{ENOSYS}. - } -\end{functions} + \end{errlist} + 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, -mentre \param{length} deve essere un numero positivo.\footnote{la versione di - Linux consente anche un valore nullo per \param{length}, inoltre se una - parte dell'intervallo non è mappato in memoria l'indicazione viene comunque - applicata alle restanti parti, anche se la funzione ritorna un errore di - \errval{ENOMEM}.} L'indicazione viene espressa dall'argomento \param{advice} -che deve essere specificato con uno dei valori\footnote{si tenga presente che - gli ultimi tre valori sono specifici di Linux (introdotti a partire dal - kernel 2.6.16) e non previsti dallo standard POSIX.1b.} riportati in -tab.~\ref{tab:madvise_advice_values}. +mentre \param{length} deve essere un numero positivo; la versione di Linux +consente anche un valore nullo per \param{length}, inoltre se una parte +dell'intervallo non è mappato in memoria l'indicazione viene comunque +applicata alle restanti parti, anche se la funzione ritorna un errore di +\errval{ENOMEM}. + +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 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 \textit{read-ahead} (vedi sez.~\ref{sec:file_fadvise}) e del +caching dei dati. -\begin{table}[htb] +\begin{table}[!htb] \centering \footnotesize \begin{tabular}[c]{|l|p{10 cm}|} @@ -4609,13 +4611,19 @@ tab.~\ref{tab:madvise_advice_values}. \textbf{Valore} & \textbf{Significato} \\ \hline \hline + \const{MADV\_DONTNEED}& non ci si aspetta nessun accesso nell'immediato + futuro, pertanto le pagine possono essere + liberate dal kernel non appena necessario; l'area + di memoria resterà accessibile, ma un accesso + richiederà che i dati vengano ricaricati dal file + a cui la mappatura fa riferimento.\\ \const{MADV\_NORMAL} & nessuna indicazione specifica, questo è il valore di default usato quando non si è chiamato \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 - \itindex{read-ahead} \textit{read-ahead} (vedi + \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, @@ -4626,40 +4634,64 @@ tab.~\ref{tab:madvise_advice_values}. \const{MADV\_WILLNEED}& ci si aspetta un accesso nell'immediato futuro, pertanto l'applicazione del \textit{read-ahead} deve essere incentivata.\\ - \const{MADV\_DONTNEED}& non ci si aspetta nessun accesso nell'immediato - futuro, pertanto le pagine possono essere - liberate dal kernel non appena necessario; l'area - di memoria resterà accessibile, ma un accesso - richiederà che i dati vengano ricaricati dal file - a cui la mappatura fa riferimento.\\ \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}.\footnotemark\\ + \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 \itindex{copy~on~write} - \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 - operazioni in DMA su quelle pagine.\\ + 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 + operazioni in DMA su quelle pagine (dal kernel + 2.6.16).\\ \const{MADV\_DOFORK} & rimuove l'effetto della precedente - \const{MADV\_DONTFORK}.\\ - \const{MADV\_MERGEABLE}& marca la pagina come accorpabile (indicazione + \const{MADV\_DONTFORK} (dal kernel 2.6.16).\\ + \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\\ - \hline + 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{se usato su altri tipi di filesystem causa un errore di - \errcode{ENOSYS}.} - \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 @@ -4669,64 +4701,115 @@ tab.~\ref{tab:madvise_advice_values}. 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}}.} - -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.} + \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}).} + + +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. -\itindend{memory~mapping} \subsection{I/O vettorizzato: \func{readv} e \func{writev}} \label{sec:file_multiple_io} -Un caso abbastanza comune è quello in cui ci si trova a dover eseguire una -serie multipla di operazioni di I/O, come una serie di letture o scritture di -vari buffer. Un esempio tipico è quando i dati sono strutturati nei campi di -una struttura ed essi devono essere caricati o salvati su un file. Benché -l'operazione sia facilmente eseguibile attraverso una serie multipla di -chiamate 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 @@ -4792,29 +4875,24 @@ sez.~\ref{sec:file_read} e \ref{sec:file_write}); le due funzioni sono 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 @@ -4844,32 +4922,31 @@ 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. +\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 @@ -4878,17 +4955,16 @@ di \funcd{sendfile} è: \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 @@ -4899,38 +4975,38 @@ 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 +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 -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.} +sola chiamata a \funcd{sendfile}. In questo modo si poteva diminuire il numero +di chiamate al sistema e risparmiare in trasferimenti di dati da +\textit{kernel space} a \textit{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 (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 +\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 \textit{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} darà luogo ad un errore di \errcode{EINVAL}. +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 @@ -4938,27 +5014,31 @@ 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. - -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'\textit{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 @@ -4968,40 +5048,40 @@ Il concetto che sta dietro a \func{splice} invece è diverso,\footnote{in 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 -\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}''. +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 \textit{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 \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 - *off\_out, size\_t len, unsigned int flags)} - - Trasferisce dati da un file verso una \textit{pipe} o viceversa. +\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, \\ +\phantom{long splice(}unsigned int flags)} +\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. @@ -5015,18 +5095,19 @@ definito la macro \macro{\_GNU\_SOURCE},\footnote{si ricordi che questa 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 @@ -5068,8 +5149,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 @@ -5083,13 +5171,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 @@ -5105,50 +5204,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 \textit{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=3.5cm]{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} @@ -5159,29 +5238,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 @@ -5191,8 +5271,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 @@ -5202,49 +5282,47 @@ presenti sul buffer. Si noti come il programma sia concettualmente identico a quello che si sarebbe scritto usando \func{read} al posto della prima \func{splice} e \func{write} -al posto della seconda, utilizzando un buffer in user space per eseguire la -copia dei dati, solo che in questo caso non è stato necessario allocare nessun -buffer e non si è trasferito nessun dato in user space. - -Si noti anche come si sia usata la combinazione \texttt{SPLICE\_F\_MOVE | - SPLICE\_F\_MORE } per l'argomento \param{flags} di \func{splice}, infatti -anche se un valore nullo avrebbe dato gli stessi risultati, l'uso di questi -flag, che si ricordi servono solo a dare suggerimenti al kernel, permette in -genere di migliorare le prestazioni. +al posto della seconda, utilizzando un buffer in \textit{user space} per +eseguire la copia dei dati, solo che in questo caso non è stato necessario +allocare nessun buffer e non si è trasferito nessun dato in \textit{user + space}. Si noti anche come si sia usata la combinazione +\texttt{SPLICE\_F\_MOVE | SPLICE\_F\_MORE } per l'argomento \param{flags} di +\func{splice}, infatti anche se un valore nullo avrebbe dato gli stessi +risultati, l'uso di questi flag, che si ricordi servono solo a dare +suggerimenti al kernel, permette in genere di migliorare le prestazioni. Come accennato con l'introduzione di \func{splice} sono state realizzate anche altre due \textit{system call}, \func{vmsplice} e \func{tee}, che utilizzano la stessa infrastruttura e si basano sullo stesso concetto di manipolazione e -trasferimento di dati attraverso un buffer in kernel space; benché queste non -attengono strettamente ad operazioni di trasferimento dati fra file +trasferimento di dati attraverso un buffer in \textit{kernel space}; benché +queste non attengono strettamente ad operazioni di trasferimento dati fra file descriptor, le tratteremo qui, essendo strettamente correlate fra loro. La prima funzione, \funcd{vmsplice}, è la più simile a \func{splice} e come indica il suo nome consente di trasferire i dati dalla memoria virtuale di un processo (ad esempio per un file mappato in memoria) verso una \textit{pipe}; il suo prototipo è: -\begin{functions} - \headdecl{fcntl.h} - \headdecl{sys/uio.h} - \funcdecl{long vmsplice(int fd, const struct iovec *iov, unsigned long - nr\_segs, unsigned int flags)} - - Trasferisce dati dalla memoria di un processo verso una \textit{pipe}. +\begin{funcproto}{ +\fhead{fcntl.h} +\fhead{sys/uio.h} +\fdecl{long vmsplice(int fd, const struct iovec *iov, unsigned long nr\_segs,\\ +\phantom{long vmsplice(}unsigned int flags)} +\fdesc{Trasferisce dati dalla memoria di un processo verso una \textit{pipe}.} +} - \bodydesc{La funzione restituisce il numero di byte trasferiti in caso di - successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno - dei valori: - \begin{errlist} +{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}] o \param{fd} non è un file descriptor valido o non fa riferimento ad una \textit{pipe}. \item[\errcode{EINVAL}] si è usato un valore nullo per \param{nr\_segs} oppure si è usato \const{SPLICE\_F\_GIFT} ma la memoria non è allineata. \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione richiesta. - \end{errlist} - } -\end{functions} + \end{errlist} +} +\end{funcproto} La \textit{pipe} indicata da \param{fd} dovrà essere specificata tramite il file descriptor corrispondente al suo capo aperto in scrittura (di nuovo si @@ -5269,31 +5347,31 @@ che queste possono essere utilizzate immediatamente senza necessità di eseguire una copia dei dati che contengono. La seconda funzione aggiunta insieme a \func{splice} è \func{tee}, che deve il -suo nome all'omonimo comando in user space, perché in analogia con questo -permette di duplicare i dati in ingresso su una \textit{pipe} su un'altra -\textit{pipe}. In sostanza, sempre nell'ottica della manipolazione dei dati su -dei buffer in kernel space, la funzione consente di eseguire una copia del -contenuto del buffer stesso. Il prototipo di \funcd{tee} è il seguente: -\begin{functions} - \headdecl{fcntl.h} +suo nome all'omonimo comando in \textit{user space}, perché in analogia con +questo permette di duplicare i dati in ingresso su una \textit{pipe} su +un'altra \textit{pipe}. In sostanza, sempre nell'ottica della manipolazione +dei dati su dei buffer in \textit{kernel space}, la funzione consente di +eseguire una copia del contenuto del buffer stesso. Il prototipo di +\funcd{tee} è il seguente: - \funcdecl{long tee(int fd\_in, int fd\_out, size\_t len, unsigned int +\begin{funcproto}{ +\fhead{fcntl.h} +\fdecl{long tee(int fd\_in, int fd\_out, size\_t len, unsigned int flags)} - - Duplica \param{len} byte da una \textit{pipe} ad un'altra. +\fdesc{Duplica i dati da una \textit{pipe} ad un'altra.} +} - \bodydesc{La funzione restituisce il numero di byte copiati in caso di - successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno - dei valori: - \begin{errlist} +{La funzione ritorna restituisce il numero di byte copiati in caso di successo + e $-1$ per un errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EINVAL}] o uno fra \param{fd\_in} e \param{fd\_out} non fa riferimento ad una \textit{pipe} o entrambi fanno riferimento alla stessa \textit{pipe}. \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'operazione richiesta. - \end{errlist} - } -\end{functions} + \end{errlist} +} +\end{funcproto} La funzione copia \param{len} byte del contenuto di una \textit{pipe} su di un'altra; \param{fd\_in} deve essere il capo in lettura della \textit{pipe} @@ -5311,17 +5389,17 @@ funzione non bloccante. La funzione restituisce il numero di byte copiati da una \textit{pipe} all'altra (o $-1$ in caso di errore), un valore nullo indica che non ci sono byte disponibili da copiare e che il capo in scrittura della \textit{pipe} è -stato chiuso.\footnote{si tenga presente però che questo non avviene se si è - impostato il flag \const{SPLICE\_F\_NONBLOCK}, in tal caso infatti si - avrebbe un errore di \errcode{EAGAIN}.} Un esempio di realizzazione del -comando \texttt{tee} usando questa funzione, ripreso da quello fornito nella -pagina di manuale e dall'esempio allegato al patch originale, è riportato in +stato chiuso; si tenga presente però che questo non avviene se si è impostato +il flag \const{SPLICE\_F\_NONBLOCK}, in tal caso infatti si avrebbe un errore +di \errcode{EAGAIN}. Un esempio di realizzazione del comando \texttt{tee} +usando questa funzione, ripreso da quello fornito nella pagina di manuale e +dall'esempio allegato al patch originale, è riportato in fig.~\ref{fig:tee_example}. Il programma consente di copiare il contenuto -dello standard input sullo standard output e su un file specificato come -argomento, il codice completo si trova nel file \texttt{tee.c} dei sorgenti -allegati alla guida. +dello \textit{standard input} sullo \textit{standard output} e su un file +specificato come argomento, il codice completo si trova nel file +\texttt{tee.c} dei sorgenti allegati alla guida. -\begin{figure}[!htbp] +\begin{figure}[!htb] \footnotesize \centering \begin{minipage}[c]{\codesamplewidth} \includecodesample{listati/tee.c} @@ -5332,28 +5410,27 @@ allegati alla guida. \label{fig:tee_example} \end{figure} -La prima parte del programma (\texttt{\small 10-35}) si cura semplicemente di -controllare (\texttt{\small 11-14}) che sia stato fornito almeno un argomento -(il nome del file su cui scrivere), di aprirlo ({\small 15-19}) e che sia lo -standard input (\texttt{\small 20-27}) che lo standard output (\texttt{\small - 28-35}) corrispondano ad una \textit{pipe}. +La prima parte del programma, che si è omessa per brevità, si cura +semplicemente di controllare che sia stato fornito almeno un argomento (il +nome del file su cui scrivere), di aprirlo e che sia lo standard input che lo +standard output corrispondano ad una \textit{pipe}. -Il ciclo principale (\texttt{\small 37-58}) inizia con la chiamata a +Il ciclo principale (\texttt{\small 11-32}) inizia con la chiamata a \func{tee} che duplica il contenuto dello standard input sullo standard output -(\texttt{\small 39}), questa parte è del tutto analoga ad una lettura ed +(\texttt{\small 13}), questa parte è del tutto analoga ad una lettura ed infatti come nell'esempio di fig.~\ref{fig:splice_example} si controlla il valore di ritorno della funzione in \var{len}; se questo è nullo significa che -non ci sono più dati da leggere e si chiude il ciclo (\texttt{\small 40}), se +non ci sono più dati da leggere e si chiude il ciclo (\texttt{\small 14}), se è negativo c'è stato un errore, ed allora si ripete la chiamata se questo è -dovuto ad una interruzione (\texttt{\small 42-44}) o si stampa un messaggio -di errore e si esce negli altri casi (\texttt{\small 44-47}). - -Una volta completata la copia dei dati sullo standard output si possono -estrarre dalla standard input e scrivere sul file, di nuovo su usa un ciclo di -scrittura (\texttt{\small 50-58}) in cui si ripete una chiamata a -\func{splice} (\texttt{\small 51}) fintanto che non si sono scritti tutti i -\var{len} byte copiati in precedenza con \func{tee} (il funzionamento è -identico all'analogo ciclo di scrittura del precedente esempio di +dovuto ad una interruzione (\texttt{\small 15-48}) o si stampa un messaggio +di errore e si esce negli altri casi (\texttt{\small 18-21}). + +Una volta completata la copia dei dati sullo \textit{standard output} si +possono estrarre dallo \textit{standard input} e scrivere sul file, di nuovo +su usa un ciclo di scrittura (\texttt{\small 24-31}) in cui si ripete una +chiamata a \func{splice} (\texttt{\small 25}) fintanto che non si sono scritti +tutti i \var{len} byte copiati in precedenza con \func{tee} (il funzionamento +è identico all'analogo ciclo di scrittura del precedente esempio di fig.~\ref{fig:splice_example}). Infine una nota finale riguardo \func{splice}, \func{vmsplice} e \func{tee}: @@ -5394,29 +5471,29 @@ il loro accesso ai dati dei file e controllare la gestione del relativo \itindbeg{read-ahead} Una prima funzione che può essere utilizzata per modificare la gestione -ordinaria dell'I/O su un file è \funcd{readahead},\footnote{questa è una - funzione specifica di Linux, introdotta con il kernel 2.4.13, e non deve - essere usata se si vogliono scrivere programmi portabili.} che consente di -richiedere una lettura anticipata del contenuto dello stesso in cache, così -che le seguenti operazioni di lettura non debbano subire il ritardo dovuto -all'accesso al disco; il suo prototipo è: -\begin{functions} - \headdecl{fcntl.h} +ordinaria dell'I/O su un file è \funcd{readahead} (questa è una funzione +specifica di Linux, introdotta con il kernel 2.4.13, e non deve essere usata +se si vogliono scrivere programmi portabili), che consente di richiedere una +lettura anticipata del contenuto dello stesso in cache, così che le seguenti +operazioni di lettura non debbano subire il ritardo dovuto all'accesso al +disco; il suo prototipo è: - \funcdecl{ssize\_t readahead(int fd, off64\_t *offset, size\_t count)} - - Esegue una lettura preventiva del contenuto di un file in cache. +\begin{funcproto}{ +\fhead{fcntl.h} +\fdecl{ssize\_t readahead(int fd, off64\_t *offset, size\_t count)} +\fdesc{Esegue una lettura preventiva del contenuto di un file in cache.} +} - \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: - \begin{errlist} +{La funzione ritorna $0$ in caso di successo e $-1$ per un errore, nel qual + caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor valido o non è aperto in lettura. \item[\errcode{EINVAL}] l'argomento \param{fd} si riferisce ad un tipo di file che non supporta l'operazione (come una \textit{pipe} o un socket). - \end{errlist} - } -\end{functions} + \end{errlist} +} +\end{funcproto} La funzione richiede che venga letto in anticipo il contenuto del file \param{fd} a partire dalla posizione \param{offset} e per un ammontare di @@ -5425,7 +5502,7 @@ virtuale ed il meccanismo della paginazione per cui la lettura viene eseguita in blocchi corrispondenti alle dimensioni delle pagine di memoria, ed i valori di \param{offset} e \param{count} vengono arrotondati di conseguenza. -La funzione estende quello che è un comportamento normale del kernel che +La funzione estende quello che è un comportamento normale del kernel che, quando si legge un file, aspettandosi che l'accesso prosegua, esegue sempre una lettura preventiva di una certa quantità di dati; questo meccanismo di lettura anticipata viene chiamato \textit{read-ahead}, da cui deriva il nome @@ -5445,35 +5522,38 @@ nelle operazioni successive. \itindend{read-ahead} Il concetto di \func{readahead} viene generalizzato nello standard -POSIX.1-2001 dalla funzione \func{posix\_fadvise},\footnote{anche se - l'argomento \param{len} è stato modificato da \type{size\_t} a \type{off\_t} - nella revisione POSIX.1-2003 TC5.} che consente di ``\textsl{avvisare}'' il +POSIX.1-2001 dalla funzione \func{posix\_fadvise} (anche se +l'argomento \param{len} è stato modificato da \type{size\_t} a \type{off\_t} +nella revisione POSIX.1-2003 TC5) che consente di ``\textsl{avvisare}'' il kernel sulle modalità con cui si intende accedere nel futuro ad una certa -porzione di un file,\footnote{la funzione però è stata introdotta su Linux - solo a partire dal kernel 2.5.60.} così che esso possa provvedere le -opportune ottimizzazioni; il prototipo di \funcd{posix\_fadvise}, che è -disponibile soltanto se è stata definita la macro \macro{\_XOPEN\_SOURCE} ad -valore di almeno 600, è: -\begin{functions} - \headdecl{fcntl.h} +porzione di un file, così che esso possa provvedere le opportune +ottimizzazioni; il prototipo di \funcd{posix\_fadvise}\footnote{la funzione è + stata introdotta su Linux solo a partire dal kernel 2.5.60, ed è disponibile + soltanto se è stata definita la macro \macro{\_XOPEN\_SOURCE} ad valore di + almeno \texttt{600} o la macro \macro{\_POSIX\_C\_SOURCE} ad valore di + almeno \texttt{200112L}.} è: - \funcdecl{int posix\_fadvise(int fd, off\_t offset, off\_t len, int advice)} - - Dichiara al kernel le future modalità di accesso ad un file. - \bodydesc{La funzione restituisce 0 in caso di successo e $-1$ in caso di - errore, nel qual caso \var{errno} assumerà uno dei valori: - \begin{errlist} +\begin{funcproto}{ +\fhead{fcntl.h} +\fdecl{int posix\_fadvise(int fd, off\_t offset, off\_t len, int advice)} +\fdesc{Dichiara al kernel le future modalità di accesso ad un file.} +} + +{La funzione ritorna $0$ in caso di successo e $-1$ per un errore, nel qual + caso \var{errno} assumerà uno dei valori: + \begin{errlist} \item[\errcode{EBADF}] l'argomento \param{fd} non è un file descriptor valido. \item[\errcode{EINVAL}] il valore di \param{advice} non è valido o \param{fd} si riferisce ad un tipo di file che non supporta l'operazione (come una \textit{pipe} o un socket). - \item[\errcode{ESPIPE}] previsto dallo standard se \param{fd} è una \textit{pipe} o - un socket (ma su Linux viene restituito \errcode{EINVAL}). - \end{errlist} - } -\end{functions} + \item[\errcode{ESPIPE}] previsto dallo standard se \param{fd} è una + \textit{pipe} o un socket (ma su Linux viene restituito + \errcode{EINVAL}). + \end{errlist} +} +\end{funcproto} La funzione dichiara al kernel le modalità con cui intende accedere alla regione del file indicato da \param{fd} che inizia alla posizione @@ -5681,6 +5761,9 @@ livello di kernel. % 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/ @@ -5740,11 +5823,12 @@ livello di kernel. % LocalWords: sigwaitinfo FifoReporter Windows ptr sigqueue named timerfd TFD % 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 TLOCK ULOCK EPOLLWAKEUP %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil" %%% End: -% LocalWords: sigfd fifofd break siginf names starting echo Message from Got -% LocalWords: message kill received means exit +