di un server in attesa di dati in ingresso da vari client). Quello che può
accadere è di restare bloccati nell'eseguire una operazione su un file
descriptor che non è ``\textsl{pronto}'', quando ce ne potrebbe essere
-un'altro disponibile. Questo comporta nel migliore dei casi una operazione
+un altro disponibile. Questo comporta nel migliore dei casi una operazione
ritardata inutilmente nell'attesa del completamento di quella bloccata, mentre
nel peggiore dei casi (quando la conclusione della operazione bloccata dipende
da quanto si otterrebbe dal file descriptor ``\textsl{disponibile}'') si
Inoltre c'è anche il problema che il numero massimo dei file che si possono
tenere sotto controllo, la funzione è nata quando il kernel consentiva un
numero massimo di 1024 file descriptor per processo, adesso che il numero può
-essere arbitario si viene a creare una dipendenza del tutto artificiale dalle
+essere arbitrario si viene a creare una dipendenza del tutto artificiale dalle
dimensioni della struttura \type{fd\_set}, che può necessitare di essere
estesa, con ulteriori perdite di prestazioni.
%\label{sec:file_epoll}
% placeholder ...
-%da fare
+% TODO epoll
\section{L'accesso \textsl{asincrono} ai file}
\label{sec:file_asyncronous_access}
riconoscere il file descriptor che li ha emessi. In questo caso infatti si può
fare ricorso alle informazioni aggiuntive restituite attraverso la struttura
\struct{siginfo\_t}, utilizzando la forma estesa \var{sa\_sigaction} del
-gestore (si riveda quanto illustrato in sez.~\ref{sec:sig_sigaction}).
+gestore installata con il flag \const{SA\_SIGINFO} (si riveda quanto
+illustrato in sez.~\ref{sec:sig_sigaction}).
Per far questo però occorre utilizzare le funzionalità dei segnali real-time
(vedi sez.~\ref{sec:sig_real_time}) impostando esplicitamente con il comando
i segnali in eccesso, e si dovrà allora determinare con un ciclo quali sono i
file diventati attivi.
+% TODO fare esempio che usa O_ASYNC
+
+
+\subsection{I meccanismi di notifica asincrona.}
+\label{sec:file_asyncronous_lease}
+
+Una delle domande più frequenti nella programmazione in ambiente unix-like è
+quella di come fare a sapere quando un file viene modificato. La
+risposta\footnote{o meglio la non risposta, tanto che questa nelle Unix FAQ
+ \cite{UnixFAQ} viene anche chiamata una \textit{Frequently Unanswered
+ Question}.} è che nell'architettura classica di Unix questo non è
+possibile. Al contrario di altri sistemi operativi infatti un kernel unix-like
+non prevede alcun meccanismo per cui un processo possa essere
+\textsl{notificato} di eventuali modifiche avvenute su un file. Questo è il
+motivo per cui i demoni devono essere \textsl{avvisati} in qualche
+modo\footnote{in genere questo vien fatto inviandogli un segnale di
+ \const{SIGHUP} che, per convenzione adottata dalla gran parte di detti
+ programmi, causa la rilettura della configurazione.} se il loro file di
+configurazione è stato modificato, perché possano rileggerlo e riconoscere le
+modifiche.
+
+Questa scelta è stata fatta perché provvedere un simile meccanismo a livello
+generale comporterebbe un notevole aumento di complessità dell'architettura
+della gestione dei file, per fornire una funzionalità necessaria soltanto in
+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.
+
+Visto però il crescente interesse nei confronti di una funzionalità di questo
+tipo (molto richiesta specialmente nello sviluppo dei programmi ad interfaccia
+grafica) sono state successivamente introdotte delle estensioni che
+permettessero la creazione di meccanismi di notifica più efficienti dell'unica
+soluzione disponibile con l'interfaccia tradizionale, che è quella del
+\itindex{polling}\textit{polling}.
+
+Queste nuove funzionalità sono delle estensioni specifiche, non
+standardizzate, che sono disponibili soltanto su Linux (anche se altri kernel
+supportano meccanismi simili). Esse sono realizzate, e solo a partire dalla
+versione 2.4 del kernel, attraverso l'uso di alcuni \textsl{comandi}
+aggiuntivi per la funzione \func{fcntl} (vedi sez.~\ref{sec:file_fcntl}), che
+divengono disponibili soltanto se si è definita la macro \macro{\_GNU\_SOURCE}
+prima di includere \file{fcntl.h}.
+
+\index{file!lease|(}
+
+La prima di queste funzionalità è quella del cosiddetto \textit{file lease};
+questo è un meccanismo che consente ad un processo, detto \textit{lease
+ holder}, di essere notificato quando un altro processo, chiamato a sua volta
+\textit{lease breaker}, cerca di eseguire una \func{open} o una
+\func{truncate} sul file del quale l'\textit{holder} detiene il
+\textit{lease}.
+
+La notifica avviene in maniera analoga a come illustrato in precedenza per
+l'uso di \const{O\_ASYNC}: di default viene inviato al \textit{lease holder}
+il segnale \const{SIGIO}, ma questo segnale può essere modificato usando il
+comando \const{F\_SETSIG} di \func{fcntl}.\footnote{anche in questo caso si
+ può rispecificare lo stesso \const{SIGIO}.} Se si è fatto questo\footnote{è
+ in genere è opportuno farlo, come in precedenza, per utilizzare segnali
+ real-time.} e si è installato il manipolatore del segnale con
+\const{SA\_SIGINFO} si riceverà nel campo \var{si\_fd} della struttura
+\struct{siginfo\_t} il valore del file descriptor del file sul quale è stato
+compiuto l'accesso; in questo modo un processo può mantenere anche più di un
+\textit{file lease}.
+
+Esistono due tipi di \textit{file lease}: di lettura (\textit{read lease}) e
+di scrittura (\textit{write lease}). Nel primo caso la notifica avviene quando
+un altro processo esegue l'apertura del file in scrittura o usa
+\func{truncate} per troncarlo. Nel secondo caso la notifica avviene anche se
+il file viene aperto il lettura; in quest'ultimo caso però il \textit{lease}
+può essere ottenuto solo se nessun altro processo ha aperto lo stesso file.
+
+Come accennato in sez.~\ref{sec:file_fcntl} il comando di \func{fcntl} che
+consente di acquisire un \textit{file lease} è \const{F\_SETLEASE}, che viene
+utilizzato anche per rilasciarlo. In tal caso il file descriptor \param{fd}
+passato a \func{fcntl} servirà come riferimento per il file su cui si vuole
+operare, mentre per indicare il tipo di operazione (acquisizione o rilascio)
+occorrerà specificare come valore dell'argomento \param{arg} di \func{fcntl}
+uno dei tre valori di tab.~\ref{tab:file_lease_fctnl}.
+
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|l|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{F\_RDLCK} & Richiede un \textit{read lease}.\\
+ \const{F\_WRLCK} & Richiede un \textit{write lease}.\\
+ \const{F\_UNLCK} & Rilascia un \textit{file lease}.\\
+ \hline
+ \end{tabular}
+ \caption{Costanti per i tre possibili valori dell'argomento \param{arg} di
+ \func{fcntl} quando usata con i comandi \const{F\_SETLEASE} e
+ \const{F\_GETLEASE}.}
+ \label{tab:file_lease_fctnl}
+\end{table}
+
+Se invece si vuole conoscere lo stato di eventuali \textit{file lease}
+occorrerà chiamare \func{fcntl} sul relativo file descriptor \param{fd} con il
+comando \const{F\_GETLEASE}, e si otterrà indietro nell'argomento \param{arg}
+uno dei valori di tab.~\ref{tab:file_lease_fctnl}, che indicheranno la
+presenza del rispettivo tipo di \textit{lease}, o, nel caso di
+\const{F\_UNLCK}, l'assenza di qualunque \textit{file lease}.
+
+Si tenga presente che un processo può mantenere solo un tipo di \textit{lease}
+su un file, e che un \textit{lease} può essere ottenuto solo su file di dati
+(pipe e dispositivi sono quindi esclusi). Inoltre un processo non privilegiato
+può ottenere un \textit{lease} soltanto per un file appartenente ad un
+\acr{uid} corrispondente a quello del processo. Soltanto un processo con
+privilegi di amministratore (cioè con la \itindex{capabilities} capability
+\const{CAP\_LEASE}) può acquisire \textit{lease} su qualunque file.
+
+Se su un file è presente un \textit{lease} quando il \textit{lease breaker}
+esegue una \func{truncate} o una \func{open} che confligge con
+esso,\footnote{in realtà \func{truncate} confligge sempre, mentre \func{open},
+ se eseguita in sola lettura, non confligge se si tratta di un \textit{read
+ lease}.} la funzione si blocca\footnote{a meno di non avere aperto il file
+ con \const{O\_NONBLOCK}, nel qual caso \func{open} fallirebbe con un errore
+ di \errcode{EWOULDBLOCK}.} e viene eseguita la notifica al \textit{lease
+ holder}, così che questo possa completare le sue operazioni sul file e
+rilasciare il \textit{lease}. In sostanza con un \textit{read lease} si
+rilevano i tentativi di accedere al file per modificarne i dati da parte di un
+altro processo, mentre con un \textit{write lease} si rilevano anche i
+tentativi di accesso in lettura. Si noti comunque che le operazioni di
+notifica avvengono solo in fase di apertura del file e non sulle singole
+operazioni di lettura e scrittura.
+
+L'utilizzo dei \textit{file lease} consente al \textit{lease holder} di
+assicurare la consistenza di un file, a seconda dei due casi, prima che un
+altro processo inizi con le sue operazioni di scrittura o di lettura su di
+esso. In genere un \textit{lease holder} che riceve una notifica deve
+provvedere a completare le necessarie operazioni (ad esempio scaricare
+eventuali buffer), per poi rilasciare il \textit{lease} così che il
+\textit{lease breaker} possa eseguire le sue operazioni. Questo si fa con il
+comando \const{F\_SETLEASE}, o rimuovendo il \textit{lease} con
+\const{F\_UNLCK}, o, nel caso di \textit{write lease} che confligge con una
+operazione di lettura, declassando il \textit{lease} a lettura con
+\const{F\_RDLCK}.
+
+Se il \textit{lease holder} non provvede a rilasciare il \textit{lease} entro
+il numero di secondi specificato dal parametro di sistema mantenuto in
+\file{/proc/sys/fs/lease-break-time} sarà il kernel stesso a rimuoverlo (o
+declassarlo) automaticamente.\footnote{questa è una misura di sicurezza per
+ evitare che un processo blocchi indefinitamente l'accesso ad un file
+ acquisendo un \textit{lease}.} Una volta che un \textit{lease} è stato
+rilasciato o declassato (che questo sia fatto dal \textit{lease holder} o dal
+kernel è lo stesso) le chiamate a \func{open} o \func{truncate} eseguite dal
+\textit{lease breaker} rimaste bloccate proseguono automaticamente.
+
+
+\index{file!notify|(}
+
+Benché possa risultare utile per sincronizzare l'accesso ad uno stesso file da
+parte di più processi, l'uso dei \textit{file lease} non consente comunque di
+risolvere il problema di rilevare automaticamente quando un file viene
+modificato, che è quanto necessario ad esempio ai programma di gestione dei
+file dei vari desktop grafici.
+
+Per risolvere questo problema è stata allora creata un'altra interfaccia che
+consente di richiedere una notifica quando una directory, o di uno qualunque
+dei file in essa contenuti, viene modificato. Come per i \textit{file lease}
+la notifica avviene di default attraverso il segnale \const{SIGIO}, ma questo
+può essere modificato e si può ottenere nel manipolatore il file descriptor
+che è stato modificato dal contenuto della struttura \struct{siginfo\_t}.
+
+\index{file!lease|)}
+
+Ci si può registrare per le notifiche dei cambiamenti al contenuto di una
+certa directory eseguendo \func{fcntl} su un file descriptor \param{fd}
+associato alla stessa con il comando \const{F\_NOTIFY}. In questo caso
+l'argomento \param{arg} serve ad indicare per quali classi eventi si vuole
+ricevere la notifica, e prende come valore una maschera binaria composta
+dall'OR aritmetico di una o più delle costanti riportate in
+tab.~\ref{tab:file_notify}.
+
+\begin{table}[htb]
+ \centering
+ \footnotesize
+ \begin{tabular}[c]{|l|p{8cm}|}
+ \hline
+ \textbf{Valore} & \textbf{Significato} \\
+ \hline
+ \hline
+ \const{DN\_ACCESS} & Un file è stato acceduto, con l'esecuzione di una fra
+ \func{read}, \func{pread}, \func{readv}.\\
+ \const{DN\_MODIFY} & Un file è stato modificato, con l'esecuzione di una
+ fra \func{write}, \func{pwrite}, \func{writev},
+ \func{truncate}, \func{ftruncate}.\\
+ \const{DN\_CREATE} & È stato creato un file nella directory, con
+ l'esecuzione di una fra \func{open}, \func{creat},
+ \func{mknod}, \func{mkdir}, \func{link},
+ \func{symlink}, \func{rename} (da un'altra
+ directory).\\
+ \const{DN\_DELETE} & È stato cancellato un file dalla directory con
+ l'esecuzione di una fra \func{unlink}, \func{rename}
+ (su un'altra directory), \func{rmdir}.\\
+ \const{DN\_RENAME} & È stato rinominato un file all'interno della
+ directory (con \func{rename}).\\
+ \const{DN\_ATTRIB} & È stato modificato un attributo di un file con
+ l'esecuzione di una fra \func{chown}, \func{chmod},
+ \func{utime}.\\
+ \const{DN\_MULTISHOT}& richiede una notifica permanente di tutti gli
+ eventi.\\
+ \hline
+ \end{tabular}
+ \caption{Le costanti che identificano le varie classi di eventi per i quali
+ si richiede la notifica con il comando \const{F\_NOTIFY} di \func{fcntl}.}
+ \label{tab:file_notify}
+\end{table}
+
+A meno di non impostare in maniera esplicita una notifica permanente usando
+\const{DN\_MULTISHOT}, la notifica è singola: viene cioè inviata una sola
+volta quando si verifica uno qualunque fra gli eventi per i quali la si è
+richiesta. Questo significa che un programma deve registrarsi un'altra volta se
+desidera essere notificato di ulteriori cambiamenti. Se si eseguono diverse
+chiamate con \const{F\_NOTIFY} e con valori diversi per \param{arg} questi
+ultimi si \textsl{accumulano}; cioè eventuali nuovi classi di eventi
+specificate in chiamate successive vengono aggiunte a quelle già impostate
+nelle precedenti. Se si vuole rimuovere la notifica si deve invece
+specificare un valore nullo.
+
+\index{file!notify|)}
+
+
+
+
+
+
+
+
+
+% TODO inserire anche inotify
+
+
\subsection{L'interfaccia POSIX per l'I/O asincrono}
\label{sec:file_asyncronous_io}
In generale questa interfaccia è completamente astratta e può essere
implementata sia direttamente nel kernel, che in user space attraverso l'uso
-di thread. Al momento esiste una sola versione stabile di questa interfaccia,
-quella delle \acr{glibc}, che è realizzata completamente in user space, ed
-accessibile linkando i programmi con la libreria \file{librt}. Nei kernel
-della nuova serie è stato anche introdotta (a partire dal 2.5.32) un nuovo
-layer per l'I/O asincrono.
+di thread. Per le versioni del kernel meno recenti esiste una implementazione
+di questa interfaccia fornita delle \acr{glibc}, che è realizzata
+completamente in user space, ed è accessibile linkando i programmi con la
+libreria \file{librt}. Nelle versioni più recenti (a partire dalla 2.5.32) è
+stato introdotto direttamente nel kernel un nuovo layer per l'I/O asincrono.
Lo standard prevede che tutte le operazioni di I/O asincrono siano controllate
attraverso l'uso di una apposita struttura \struct{aiocb} (il cui nome sta per
\funcdecl{void * mmap(void * start, size\_t length, int prot, int flags, int
fd, off\_t offset)}
- Esegue la mappatura in memoria del file \param{fd}.
+ Esegue la mappatura in memoria della sezione specificata del file \param{fd}.
\bodydesc{La funzione restituisce il puntatore alla zona di memoria mappata
in caso di successo, e \const{MAP\_FAILED} (-1) in caso di errore, nel
dimensione delle pagine).
\item[\errcode{ETXTBSY}] Si è impostato \const{MAP\_DENYWRITE} ma
\param{fd} è aperto in scrittura.
- \item[\errcode{EAGAIN}] Il file è bloccato, o si è bloccata troppa memoria.
+ \item[\errcode{EAGAIN}] Il file è bloccato, o si è bloccata troppa memoria
+ rispetto a quanto consentito dai limiti di sistema (vedi
+ sez.~\ref{sec:sys_resource_limit}).
\item[\errcode{ENOMEM}] Non c'è memoria o si è superato il limite sul
numero di mappature possibili.
\item[\errcode{ENODEV}] Il filesystem di \param{fd} non supporta il memory
mapping.
+ \item[\errcode{EPERM}] L'argomento \param{prot} ha richiesto
+ \const{PROT\_EXEC}, ma il filesystem di \param{fd} è montato con
+ l'opzione \texttt{noexec}.
+ \item[\errcode{ENFILE}] Si è superato il limite del sistema sul numero di
+ file aperti (vedi sez.~\ref{sec:sys_resource_limit}).
\end{errlist}
}
\end{functions}
Il valore dell'argomento \param{prot} indica la protezione\footnote{in Linux
la memoria reale è divisa in pagine: ogni processo vede la sua memoria
attraverso uno o più segmenti lineari di memoria virtuale. Per ciascuno di
- questi segmenti il kernel mantiene nella \textit{page table} la mappatura
- sulle pagine di memoria reale, ed le modalità di accesso (lettura,
- esecuzione, scrittura); una loro violazione causa quella che si chiama una
- \textit{segment violation}, e la relativa emissione del segnale
- \const{SIGSEGV}.} da applicare al segmento di memoria e deve essere
+ questi segmenti il kernel mantiene nella \itindex{page~table}\textit{page
+ table} la mappatura sulle pagine di memoria reale, ed le modalità di
+ accesso (lettura, esecuzione, scrittura); una loro violazione causa quella
+ che si chiama una \textit{segment violation}, e la relativa emissione del
+ segnale \const{SIGSEGV}.} da applicare al segmento di memoria e deve essere
specificato come maschera binaria ottenuta dall'OR di uno o più dei valori
riportati in tab.~\ref{tab:file_mmap_flag}; il valore specificato deve essere
compatibile con la modalità di accesso con cui si è aperto il file.
vengano riportati sulla regione
mappata. Incompatibile con \const{MAP\_SHARED}. \\
\const{MAP\_DENYWRITE} & In Linux viene ignorato per evitare
- \textit{DoS}\index{DoS} (veniva usato per
- segnalare che tentativi di scrittura sul file
- dovevano fallire con \errcode{ETXTBSY}).\\
+ \textit{DoS}\itindex{Denial~of~Service~(DoS)}
+ (veniva usato per segnalare che tentativi di
+ scrittura sul file dovevano fallire con
+ \errcode{ETXTBSY}).\\
\const{MAP\_EXECUTABLE}& Ignorato. \\
\const{MAP\_NORESERVE} & Si usa con \const{MAP\_PRIVATE}. Non riserva
delle pagine di swap ad uso del meccanismo del
memoria disponibile, si ha l'emissione di
un \const{SIGSEGV}. \\
\const{MAP\_LOCKED} & Se impostato impedisce lo swapping delle pagine
- mappate. \\
+ mappate.\\
\const{MAP\_GROWSDOWN} & Usato per gli stack. Indica
che la mappatura deve essere effettuata con gli
indirizzi crescenti verso il basso.\\
argomenti \param{fd} e \param{offset} sono
ignorati.\footnotemark\\
\const{MAP\_ANON} & Sinonimo di \const{MAP\_ANONYMOUS}, deprecato.\\
- \const{MAP\_FILE} & Valore di compatibilità, deprecato.\\
+ \const{MAP\_FILE} & Valore di compatibilità, ignorato.\\
+ \const{MAP\_32BIT} & Esegue la mappatura sui primi 2GiB dello spazio
+ degli indirizzi, viene supportato solo sulle
+ piattaforme \texttt{x86-64} per compatibilità con
+ le applicazioni a 32 bit. Viene ignorato se si è
+ richiesto \const{MAP\_FIXED}.\\
+ \const{MAP\_POPULATE} & Esegue il \itindex{prefaulting}
+ \textit{prefaulting} delle pagine di memoria
+ necessarie alla mappatura. \\
+ \const{MAP\_NONBLOCK} & Esegue un \textit{prefaulting} più limitato che
+ non causa I/O.\footnotemark \\
+% \const{MAP\_DONTEXPAND}& Non consente una successiva espansione dell'area
+% mappata con \func{mremap}, proposto ma pare non
+% implementato.\\
\hline
\end{tabular}
\caption{Valori possibili dell'argomento \param{flag} di \func{mmap}.}
\label{tab:file_mmap_flag}
\end{table}
-\footnotetext{Dato che tutti faranno riferimento alle stesse pagine di
- memoria.}
-\footnotetext{L'uso di questo flag con \const{MAP\_SHARED} è
- stato implementato in Linux a partire dai kernel della serie 2.4.x.}
Gli effetti dell'accesso ad una zona di memoria mappata su file possono essere
piuttosto complessi, essi si possono comprendere solo tenendo presente che
-tutto quanto è comunque basato sul basato sul meccanismo della memoria
+tutto quanto è comunque basato sul meccanismo della memoria
virtuale.\index{memoria~virtuale} Questo comporta allora una serie di
conseguenze. La più ovvia è che se si cerca di scrivere su una zona mappata in
sola lettura si avrà l'emissione di un segnale di violazione di accesso
(\const{SIGSEGV}), dato che i permessi sul segmento di memoria relativo non
consentono questo tipo di accesso.
-\begin{figure}[!htb]
- \centering
- \includegraphics[width=10cm]{img/mmap_boundary}
- \caption{Schema della mappatura in memoria di una sezione di file di
- dimensioni non corrispondenti al bordo di una pagina.}
- \label{fig:file_mmap_boundary}
-\end{figure}
-
È invece assai diversa la questione relativa agli accessi al di fuori della
regione di cui si è richiesta la mappatura. A prima vista infatti si potrebbe
ritenere che anch'essi debbano generare un segnale di violazione di accesso;
paginazione\index{paginazione}, la mappatura in memoria non può che essere
eseguita su un segmento di dimensioni rigorosamente multiple di quelle di una
pagina, ed in generale queste potranno non corrispondere alle dimensioni
-effettive del file o della sezione che si vuole mappare. Il caso più comune è
-quello illustrato in fig.~\ref{fig:file_mmap_boundary}, in cui la sezione di
-file non rientra nei confini di una pagina: in tal caso verrà il file sarà
-mappato su un segmento di memoria che si estende fino al bordo della pagina
-successiva.
+effettive del file o della sezione che si vuole mappare.
+
+\footnotetext[20]{Dato che tutti faranno riferimento alle stesse pagine di
+ memoria.}
+\footnotetext[21]{L'uso di questo flag con \const{MAP\_SHARED} è
+ stato implementato in Linux a partire dai kernel della serie 2.4.x.}
+
+\footnotetext{questo flag ed il precedente \const{MAP\_POPULATE} sono stati
+ introdotti nel kernel 2.5.46 insieme alla mappatura non lineare di cui
+ parleremo più avanti.}
+
+\begin{figure}[!htb]
+ \centering
+ \includegraphics[width=12cm]{img/mmap_boundary}
+ \caption{Schema della mappatura in memoria di una sezione di file di
+ dimensioni non corrispondenti al bordo di una pagina.}
+ \label{fig:file_mmap_boundary}
+\end{figure}
+
+
+Il caso più comune è quello illustrato in fig.~\ref{fig:file_mmap_boundary},
+in cui la sezione di file non rientra nei confini di una pagina: in tal caso
+verrà il file sarà mappato su un segmento di memoria che si estende fino al
+bordo della pagina successiva.
In questo caso è possibile accedere a quella zona di memoria che eccede le
dimensioni specificate da \param{lenght}, senza ottenere un \const{SIGSEGV}
file è stato troncato, dopo che è stato mappato, ad una dimensione inferiore a
quella della mappatura in memoria.
-\begin{figure}[htb]
- \centering
- \includegraphics[width=10cm]{img/mmap_exceed}
- \caption{Schema della mappatura in memoria di file di dimensioni inferiori
- alla lunghezza richiesta.}
- \label{fig:file_mmap_exceed}
-\end{figure}
-
In questa situazione, per la sezione di pagina parzialmente coperta dal
contenuto del file, vale esattamente quanto visto in precedenza; invece per la
parte che eccede, fino alle dimensioni date da \param{length}, l'accesso non
di dispositivi (un esempio è l'interfaccia al ponte PCI-VME del chip Universe)
che sono utilizzabili solo con questa interfaccia.
+\begin{figure}[htb]
+ \centering
+ \includegraphics[width=12cm]{img/mmap_exceed}
+ \caption{Schema della mappatura in memoria di file di dimensioni inferiori
+ alla lunghezza richiesta.}
+ \label{fig:file_mmap_exceed}
+\end{figure}
+
Dato che passando attraverso una \func{fork} lo spazio di indirizzi viene
copiato integralmente, i file mappati in memoria verranno ereditati in maniera
trasparente dal processo figlio, mantenendo gli stessi attributi avuti nel
\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}
- \item[\errcode{EINVAL}] O \param{start} non è multiplo di \const{PAGESIZE},
- o si è specificato un valore non valido per \param{flags}.
+ \item[\errcode{EINVAL}] O \param{start} non è multiplo di
+ \const{PAGE\_SIZE}, o si è specificato un valore non valido per
+ \param{flags}.
\item[\errcode{EFAULT}] L'intervallo specificato non ricade in una zona
precedentemente mappata.
\end{errlist}
}
\end{functions}
-La funzione cancella la mappatura per l'intervallo specificato attraverso
-\param{start} e \param{length}, ed ogni successivo accesso a tale regione
-causerà un errore di accesso in memoria. L'argomento \param{start} deve essere
-allineato alle dimensioni di una pagina di memoria, e la mappatura di tutte le
-pagine contenute (anche parzialmente) nell'intervallo indicato, verrà rimossa.
-Indicare un intervallo che non contiene pagine mappate non è un errore.
+La funzione cancella la mappatura per l'intervallo specificato con
+\param{start} e \param{length}; ogni successivo accesso a tale regione causerà
+un errore di accesso in memoria. L'argomento \param{start} deve essere
+allineato alle dimensioni di una pagina, e la mappatura di tutte le pagine
+contenute anche parzialmente nell'intervallo indicato, verrà rimossa.
+Indicare un intervallo che non contiene mappature non è un errore. Si tenga
+presente inoltre che alla conclusione di un processo ogni pagina mappata verrà
+automaticamente rilasciata, mentre la chiusura del file descriptor usato per
+il \textit{memory mapping} non ha alcun effetto su di esso.
+
+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}
+
+ \funcdecl{int mprotect(const void *addr, size\_t len, int prot)}
+
+ Modifica le protezioni delle pagine di memoria comprese nell'intervallo
+ specificato.
+
+ \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}
+ \item[\errcode{EINVAL}] il valore di \param{addr} non è valido o non è un
+ multiplo di \const{PAGE\_SIZE}.
+ \item[\errcode{EACCESS}] 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}
+
+
+La funzione prende come argomenti un indirizzo di partenza in \param{addr},
+allineato alle dimensioni delle pagine di memoria, ed una dimensione
+\param{size}. La nuova protezione deve essere specificata in \param{prot} con
+una combinazione dei valori di tab.~\ref{tab:file_mmap_prot}. La nuova
+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}
+
+ \funcdecl{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.
+
+ \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}
+ \item[\errcode{EINVAL}] il valore di \param{old\_address} non è un
+ puntatore valido.
+ \item[\errcode{EFAULT}] ci sono indirizzi non validi nell'intervallo
+ specificato da \param{old\_address} e \param{old\_size}, o ci sono altre
+ mappature di tipo non corrispondente a quella richiesta.
+ \item[\errcode{ENOMEM}] non c'è memoria sufficiente oppure l'area di
+ memoria non può essere espansa all'indirizzo virtuale corrente, e non si
+ è specificato \const{MREMAP\_MAYMOVE} nei flag.
+ \item[\errcode{EAGAIN}] il segmento di memoria scelto è bloccato e non può
+ essere rimappato.
+ \end{errlist}
+ }
+\end{functions}
+
+La funzione richiede come argomenti \param{old\_address} (che deve essere
+allineato alle dimensioni di una pagina di memoria) che specifica il
+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 \file{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.
+
+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.
+
+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}.
+
+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 system call, \funcd{remap\_file\_pages}, il cui prototipo è:
+\begin{functions}
+ \headdecl{sys/mman.h}
+
+ \funcdecl{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}.
+
+ \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}
+ \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}
+
+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
+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
+regione mappata.
+
+La funzione richiede che si identifichi la sezione del file che si vuole
+riposizionare all'interno del \textit{memory mapping} con gli argomenti
+\param{pgoff} e \param{size}; l'argomento \param{start} invece deve indicare
+un indirizzo all'interno dell'area definita dall'\func{mmap} iniziale, a
+partire dal quale la sezione di file indicata verrà rimappata. L'argomento
+\param{prot} deve essere sempre nullo, mentre \param{flags} prende gli stessi
+valori di \func{mmap} (quelli di tab.~\ref{tab:file_mmap_prot}) ma di tutti i
+flag solo \const{MAP\_NONBLOCK} non viene ignorato.
+
+Insieme alla funzione \func{remap\_file\_pages} nel kernel 2.5.46 con sono
+stati introdotti anche due nuovi flag per \func{mmap}: \const{MAP\_POPULATE} e
+\const{MAP\_NONBLOCK}. Il primo dei due consente di abilitare il meccanismo
+del \itindex{prefaulting} \textit{prefaulting}. Questo viene di nuovo in aiuto
+per migliorare le prestazioni in certe condizioni di utilizzo del
+\textit{memory mapping}.
+
+Il problema si pone tutte le volte che si vuole mappare in memoria un file di
+grosse dimensioni. Il comportamento normale del sistema della
+\index{memoria~virtuale} memoria virtuale è quello per cui la regione mappata
+viene aggiunta alla \itindex{page~table} \textit{page table} del processo, ma
+i dati verranno effettivamente utilizzati (si avrà cioè un
+\itindex{page~fault} \textit{page fault} che li trasferisce dal disco alla
+memoria) soltanto in 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 \itindex{prefaulting}
+\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
+flag, \const{MAP\_NONBLOCK}, che esegue un \itindex{prefaulting}
+\textit{prefaulting} più limitato in cui vengono popolate solo le pagine della
+mappatura che già si trovano nella cache del kernel.\footnote{questo può
+ essere utile per il linker dinamico, in particolare quando viene effettuato
+ il \textit{prelink} delle applicazioni.}
-Alla conclusione del processo, ogni pagina mappata verrà automaticamente
-rilasciata, mentre la chiusura del file descriptor usato per effettuare la
-mappatura in memoria non ha alcun effetto sulla stessa.
\itindend{memory~mapping}
+% i raw device
+%\subsection{I \textit{raw} device}
+%\label{sec:file_raw_device}
+%
+% TODO i raw device
+
+
+%\subsection{L'utilizzo delle porte di I/O}
+%\label{sec:file_io_port}
+%
+% TODO l'I/O sulle porte di I/O
+% consultare le manpage di ioperm, iopl e outb
+
+
\section{Il file locking}
\label{sec:file_locking}
sistemi unix-like è quella che viene usualmente chiamata \textit{advisory
locking},\footnote{Stevens in \cite{APUE} fa riferimento a questo argomento
come al \textit{record locking}, dizione utilizzata anche dal manuale delle
- \acr{glibc}; nelle pagine di manuale si parla di \textit{discretionary file
+ \acr{glibc}; nelle pagine di manuale si parla di \textit{discrectionary file
lock} per \func{fcntl} e di \textit{advisory locking} per \func{flock},
mentre questo nome viene usato da Stevens per riferirsi al \textit{file
locking} POSIX. Dato che la dizione \textit{record locking} è quantomeno
perciò le informazioni relative agli eventuali \textit{file lock} sono
mantenute a livello di inode\index{inode},\footnote{in particolare, come
accennato in fig.~\ref{fig:file_flock_struct}, i \textit{file lock} sono
- mantenuti un una \textit{linked~list} di strutture \struct{file\_lock}. La
- lista è referenziata dall'indirizzo di partenza mantenuto dal campo
- \var{i\_flock} della struttura \struct{inode} (per le definizioni esatte si
- faccia riferimento al file \file{fs.h} nei sorgenti del kernel). Un bit del
- campo \var{fl\_flags} di specifica se si tratta di un lock in semantica BSD
- (\const{FL\_FLOCK}) o POSIX (\const{FL\_POSIX}).} \itindex{linked~list} dato
-che questo è l'unico riferimento in comune che possono avere due processi
+ mantenuti in una \itindex{linked~list} \textit{linked list} di strutture
+ \struct{file\_lock}. La lista è referenziata dall'indirizzo di partenza
+ mantenuto dal campo \var{i\_flock} della struttura \struct{inode} (per le
+ definizioni esatte si faccia riferimento al file \file{fs.h} nei sorgenti
+ del kernel). Un bit del campo \var{fl\_flags} di specifica se si tratta di
+ un lock in semantica BSD (\const{FL\_FLOCK}) o POSIX (\const{FL\_POSIX}).}
+dato che questo è l'unico riferimento in comune che possono avere due processi
diversi che aprono lo stesso file.
\begin{figure}[htb]
stati creati usando altri file descriptor che restano aperti.
Dato che il controllo sull'accesso ai lock viene eseguito sulla base del
-\acr{pid} del processo, possiamo anche prendere in considerazione un'altro
+\acr{pid} del processo, possiamo anche prendere in considerazione un altro
degli aspetti meno chiari di questa interfaccia e cioè cosa succede quando si
richiedono dei lock su regioni che si sovrappongono fra loro all'interno
stesso processo. Siccome il controllo, come nel caso della rimozione, si basa
opportune verifiche nei processi, questo verrebbe comunque rispettato.
Per poter utilizzare il \textit{mandatory locking} è stato introdotto un
-utilizzo particolare del bit \acr{sgid}. Se si ricorda quanto esposto in
-sez.~\ref{sec:file_suid_sgid}), esso viene di norma utilizzato per cambiare il
-group-ID effettivo con cui viene eseguito un programma, ed è pertanto sempre
-associato alla presenza del permesso di esecuzione per il gruppo. Impostando
-questo bit su un file senza permesso di esecuzione in un sistema che supporta
-il \textit{mandatory locking}, fa sì che quest'ultimo venga attivato per il
-file in questione. In questo modo una combinazione dei permessi
-originariamente non contemplata, in quanto senza significato, diventa
-l'indicazione della presenza o meno del \textit{mandatory
+utilizzo particolare del bit \itindex{sgid~bit} \acr{sgid}. Se si ricorda
+quanto esposto in sez.~\ref{sec:file_suid_sgid}), esso viene di norma
+utilizzato per cambiare il group-ID effettivo con cui viene eseguito un
+programma, ed è pertanto sempre associato alla presenza del permesso di
+esecuzione per il gruppo. Impostando questo bit su un file senza permesso di
+esecuzione in un sistema che supporta il \textit{mandatory locking}, fa sì che
+quest'ultimo venga attivato per il file in questione. In questo modo una
+combinazione dei permessi originariamente non contemplata, in quanto senza
+significato, diventa l'indicazione della presenza o meno del \textit{mandatory
locking}.\footnote{un lettore attento potrebbe ricordare quanto detto in
- sez.~\ref{sec:file_chmod} e cioè che il bit \acr{sgid} viene cancellato (come
- misura di sicurezza) quando di scrive su un file, questo non vale quando
- esso viene utilizzato per attivare il \textit{mandatory locking}.}
+ sez.~\ref{sec:file_chmod} e cioè che il bit \acr{sgid} viene cancellato
+ (come misura di sicurezza) quando di scrive su un file, questo non vale
+ quando esso viene utilizzato per attivare il \textit{mandatory locking}.}
L'uso del \textit{mandatory locking} presenta vari aspetti delicati, dato che
neanche root può passare sopra ad un lock; pertanto un processo che blocchi un
file cruciale può renderlo completamente inaccessibile, rendendo completamente
inutilizzabile il sistema\footnote{il problema si potrebbe risolvere
- rimuovendo il bit \acr{sgid}, ma non è detto che sia così facile fare questa
- operazione con un sistema bloccato.} inoltre con il \textit{mandatory
- locking} si può bloccare completamente un server NFS richiedendo una lettura
-su un file su cui è attivo un lock. Per questo motivo l'abilitazione del
-mandatory locking è di norma disabilitata, e deve essere attivata filesystem
-per filesystem in fase di montaggio (specificando l'apposita opzione di
-\func{mount} riportata in tab.~\ref{tab:sys_mount_flags}, o con l'opzione
-\cmd{mand} per il comando).
+ rimuovendo il bit \itindex{sgid~bit} \acr{sgid}, ma non è detto che sia così
+ facile fare questa operazione con un sistema bloccato.} inoltre con il
+\textit{mandatory locking} si può bloccare completamente un server NFS
+richiedendo una lettura su un file su cui è attivo un lock. Per questo motivo
+l'abilitazione del mandatory locking è di norma disabilitata, e deve essere
+attivata filesystem per filesystem in fase di montaggio (specificando
+l'apposita opzione di \func{mount} riportata in
+tab.~\ref{tab:sys_mount_flags}, o con l'opzione \code{-o mand} per il comando
+omonimo).
Si tenga presente inoltre che il \textit{mandatory locking} funziona solo
sull'interfaccia POSIX di \func{fcntl}. Questo ha due conseguenze: che non si