X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=fileadv.tex;h=4775a6827a196befafa04432deeff23fe3f3689a;hp=688fab8da124d145e7cfe801500da2848ac30366;hb=4fd7e210cdb76c7e88deba4f2283dc6b8931a8d9;hpb=3f50b8e3fd683f710e34a88436109157d328e1b6 diff --git a/fileadv.tex b/fileadv.tex index 688fab8..4775a68 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -1,5 +1,2864 @@ -capacità \const{CAP\_LEASE}, vedi sez.~\ref{sec:proc_capabilities}) può -acquisire \textit{lease} su qualunque file. +%% fileadv.tex +%% +%% Copyright (C) 2000-2019 Simone Piccardi. Permission is granted to +%% copy, distribute and/or modify this document under the terms of the GNU Free +%% Documentation License, Version 1.1 or any later version published by the +%% Free Software Foundation; with the Invariant Sections being "Un preambolo", +%% with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the +%% license is included in the section entitled "GNU Free Documentation +%% License". +%% +\chapter{La gestione avanzata dei file} +\label{cha:file_advanced} + +In questo capitolo affronteremo le tematiche relative alla gestione avanzata +dei file. Inizieremo con la trattazione delle problematiche del \textit{file + locking} e poi prenderemo in esame le varie funzionalità avanzate che +permettono una gestione più sofisticata dell'I/O su file, a partire da quelle +che consentono di gestire l'accesso contemporaneo a più file esaminando le +varie modalità alternative di gestire l'I/O per concludere con la gestione dei +file mappati in memoria e le altre funzioni avanzate che consentono un +controllo più dettagliato delle modalità di I/O. + + +\section{Il \textit{file locking}} +\label{sec:file_locking} + +\itindbeg{file~locking} + +In sez.~\ref{sec:file_shared_access} abbiamo preso in esame le modalità in cui +un sistema unix-like gestisce l'accesso concorrente ai file da parte di +processi diversi. In quell'occasione si è visto come, con l'eccezione dei file +aperti in \textit{append mode}, quando più processi scrivono +contemporaneamente sullo stesso file non è possibile determinare la sequenza +in cui essi opereranno. + +Questo causa la possibilità di una \textit{race condition}; in generale le +situazioni più comuni sono due: l'interazione fra un processo che scrive e +altri che leggono, in cui questi ultimi possono leggere informazioni scritte +solo in maniera parziale o incompleta; o quella in cui diversi processi +scrivono, mescolando in maniera imprevedibile il loro output sul file. + +In tutti questi casi il \textit{file locking} è la tecnica che permette di +evitare le \textit{race condition}, attraverso una serie di funzioni che +permettono di bloccare l'accesso al file da parte di altri processi, così da +evitare le sovrapposizioni, e garantire la atomicità delle operazioni di +lettura o scrittura. + + +\subsection{L'\textit{advisory locking}} +\label{sec:file_record_locking} + +La prima modalità di \textit{file locking} che è stata implementata nei +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 della + \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 + ambigua, in quanto in un sistema Unix non esiste niente che possa fare + riferimento al concetto di \textit{record}, alla fine si è scelto di + mantenere il nome \textit{advisory locking}.} in quanto sono i singoli +processi, e non il sistema, che si incaricano di asserire e verificare se +esistono delle condizioni di blocco per l'accesso ai file. + +Questo significa che le funzioni \func{read} o \func{write} vengono eseguite +comunque e non risentono affatto della presenza di un eventuale \textit{lock}; +pertanto è sempre compito dei vari processi che intendono usare il +\textit{file locking}, controllare esplicitamente lo stato dei file condivisi +prima di accedervi, utilizzando le relative funzioni. + +In generale si distinguono due tipologie di \textit{file lock};\footnote{di + seguito ci riferiremo sempre ai blocchi di accesso ai file con la + nomenclatura inglese di \textit{file lock}, o più brevemente con + \textit{lock}, per evitare confusioni linguistiche con il blocco di un + processo (cioè la condizione in cui il processo viene posto in stato di + \textit{sleep}).} la prima è il cosiddetto \textit{shared lock}, detto anche +\textit{read lock} in quanto serve a bloccare l'accesso in scrittura su un +file affinché il suo contenuto non venga modificato mentre lo si legge. Si +parla appunto di \textsl{blocco condiviso} in quanto più processi possono +richiedere contemporaneamente uno \textit{shared lock} su un file per +proteggere il loro accesso in lettura. + +La seconda tipologia è il cosiddetto \textit{exclusive lock}, detto anche +\textit{write lock} in quanto serve a bloccare l'accesso su un file (sia in +lettura che in scrittura) da parte di altri processi mentre lo si sta +scrivendo. Si parla di \textsl{blocco esclusivo} appunto perché un solo +processo alla volta può richiedere un \textit{exclusive lock} su un file per +proteggere il suo accesso in scrittura. + +In Linux sono disponibili due interfacce per utilizzare l'\textit{advisory + locking}, la prima è quella derivata da BSD, che è basata sulla funzione +\func{flock}, la seconda è quella recepita dallo standard POSIX.1 (che è +derivata dall'interfaccia usata in System V), che è basata sulla funzione +\func{fcntl}. I \textit{file lock} sono implementati in maniera completamente +indipendente nelle due interfacce (in realtà con Linux questo avviene solo +dalla serie 2.0 dei kernel) che pertanto possono coesistere senza +interferenze. + +Entrambe le interfacce prevedono la stessa procedura di funzionamento: si +inizia sempre con il richiedere l'opportuno \textit{file lock} (un +\textit{exclusive lock} per una scrittura, uno \textit{shared lock} per una +lettura) prima di eseguire l'accesso ad un file. Se il blocco viene acquisito +il processo prosegue l'esecuzione, altrimenti (a meno di non aver richiesto un +comportamento non bloccante) viene posto in stato di \textit{sleep}. Una volta +finite le operazioni sul file si deve provvedere a rimuovere il blocco. + +La situazione delle varie possibilità che si possono verificare è riassunta in +tab.~\ref{tab:file_file_lock}, dove si sono riportati, a seconda delle varie +tipologie di blocco già presenti su un file, il risultato che si avrebbe in +corrispondenza di una ulteriore richiesta da parte di un processo di un blocco +nelle due tipologie di \textit{file lock} menzionate, con un successo o meno +della richiesta. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|c|c|c|} + \hline + \textbf{Richiesta} & \multicolumn{3}{|c|}{\textbf{Stato del file}}\\ + \cline{2-4} + &Nessun \textit{lock}&\textit{Read lock}&\textit{Write lock}\\ + \hline + \hline + \textit{Read lock} & esecuzione & esecuzione & blocco \\ + \textit{Write lock}& esecuzione & blocco & blocco \\ + \hline + \end{tabular} + \caption{Tipologie di \textit{file locking}.} + \label{tab:file_file_lock} +\end{table} + +Si tenga presente infine che il controllo di accesso e la gestione dei +permessi viene effettuata quando si apre un file, l'unico controllo residuo +che si può avere riguardo il \textit{file locking} è che il tipo di blocco che +si vuole ottenere su un file deve essere compatibile con le modalità di +apertura dello stesso (in lettura per un \textit{read lock} e in scrittura per +un \textit{write lock}). + +%% Si ricordi che +%% la condizione per acquisire uno \textit{shared lock} è che il file non abbia +%% già un \textit{exclusive lock} attivo, mentre per acquisire un +%% \textit{exclusive lock} non deve essere presente nessun tipo di blocco. + + +\subsection{La funzione \func{flock}} +\label{sec:file_flock} + +La prima interfaccia per il \textit{file locking}, quella derivata da BSD, +permette di eseguire un blocco solo su un intero file; la funzione di sistema +usata per richiedere e rimuovere un \textit{file lock} è \funcd{flock}, ed il +suo prototipo è: + +\begin{funcproto}{ +\fhead{sys/file.h} +\fdecl{int flock(int fd, int operation)} +\fdesc{Applica o rimuove un \textit{file lock}.} +} + +{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{EINTR}] la funzione è stata interrotta da un segnale + nell'attesa dell'acquisizione di un \textit{file lock}. + \item[\errcode{EINVAL}] si è specificato un valore non valido + per \param{operation}. + \item[\errcode{ENOLCK}] il kernel non ha memoria sufficiente per gestire il + \textit{file lock}. + \item[\errcode{EWOULDBLOCK}] il file ha già un blocco attivo, e si è + specificato \const{LOCK\_NB}. + \end{errlist} + ed inoltre \errval{EBADF} nel suo significato generico. +} +\end{funcproto} + +La funzione può essere usata per acquisire o rilasciare un \textit{file lock} +a seconda di quanto specificato tramite il valore dell'argomento +\param{operation}; questo viene interpretato come maschera binaria, e deve +essere passato costruendo il valore con un OR aritmetico delle costanti +riportate in tab.~\ref{tab:file_flock_operation}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{6cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \constd{LOCK\_SH} & Richiede uno \textit{shared lock} sul file.\\ + \constd{LOCK\_EX} & Richiede un \textit{esclusive lock} sul file.\\ + \constd{LOCK\_UN} & Rilascia il \textit{file lock}.\\ + \constd{LOCK\_NB} & Impedisce che la funzione si blocchi nella + richiesta di un \textit{file lock}.\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{operation} di \func{flock}.} + \label{tab:file_flock_operation} +\end{table} + +I primi due valori, \const{LOCK\_SH} e \const{LOCK\_EX} permettono di +richiedere un \textit{file lock} rispettivamente condiviso o esclusivo, ed +ovviamente non possono essere usati insieme. Se con essi si specifica anche +\const{LOCK\_NB} la funzione non si bloccherà qualora il \textit{file lock} +non possa essere acquisito, ma ritornerà subito con un errore di +\errcode{EWOULDBLOCK}. Per rilasciare un \textit{file lock} si dovrà invece +usare direttamente \const{LOCK\_UN}. + +Si tenga presente che non esiste una modalità per eseguire atomicamente un +cambiamento del tipo di blocco (da \textit{shared lock} a \textit{esclusive + lock}), il blocco deve essere prima rilasciato e poi richiesto, ed è sempre +possibile che nel frattempo abbia successo un'altra richiesta pendente, +facendo fallire la riacquisizione. + +Si tenga presente infine che \func{flock} non è supportata per i file +mantenuti su NFS, in questo caso, se si ha la necessità di utilizzare il +\textit{file locking}, occorre usare l'interfaccia del \textit{file locking} +POSIX basata su \func{fcntl} che è in grado di funzionare anche attraverso +NFS, a condizione ovviamente che sia il client che il server supportino questa +funzionalità. + +La semantica del \textit{file locking} di BSD inoltre è diversa da quella del +\textit{file locking} POSIX, in particolare per quanto riguarda il +comportamento dei \textit{file lock} nei confronti delle due funzioni +\func{dup} e \func{fork}. Per capire queste differenze occorre descrivere con +maggiore dettaglio come viene realizzato dal kernel il \textit{file locking} +per entrambe le interfacce. + +In fig.~\ref{fig:file_flock_struct} si è riportato uno schema essenziale +dell'implementazione del \textit{file locking} in stile BSD su Linux. Il punto +fondamentale da capire è che un \textit{file lock}, qualunque sia +l'interfaccia che si usa, anche se richiesto attraverso un file descriptor, +agisce sempre su di un file; perciò le informazioni relative agli eventuali +\textit{file lock} sono mantenute dal kernel a livello di \textit{inode}, dato +che questo è l'unico riferimento in comune che possono avere due processi +diversi che aprono lo stesso file. + +In particolare, come accennato in fig.~\ref{fig:file_flock_struct}, i +\textit{file lock} sono mantenuti in una \textit{linked list} di strutture +\kstructd{file\_lock}. La lista è referenziata dall'indirizzo di partenza +mantenuto dal campo \var{i\_flock} della struttura \kstruct{inode} (per le +definizioni esatte si faccia riferimento al file \file{include/linux/fs.h} nei +sorgenti del kernel). Un bit del campo \var{fl\_flags} di specifica se si +tratta di un lock in semantica BSD (\constd{FL\_FLOCK}) o POSIX +(\constd{FL\_POSIX}) o un \textit{file lease} (\constd{FL\_LEASE}, vedi +sez.~\ref{sec:file_asyncronous_lease}). + +\begin{figure}[!htb] + \centering + \includegraphics[width=12cm]{img/file_flock} + \caption{Schema dell'architettura del \textit{file locking}, nel caso + particolare del suo utilizzo da parte dalla funzione \func{flock}.} + \label{fig:file_flock_struct} +\end{figure} + +La richiesta di un \textit{file lock} prevede una scansione della lista per +determinare se l'acquisizione è possibile, ed in caso positivo l'aggiunta di +un nuovo elemento (cioè l'aggiunta di una nuova struttura +\kstruct{file\_lock}). Nel caso dei blocchi creati con \func{flock} la +semantica della funzione prevede che sia \func{dup} che \func{fork} non creino +ulteriori istanze di un \textit{file lock} quanto piuttosto degli ulteriori +riferimenti allo stesso. Questo viene realizzato dal kernel secondo lo schema +di fig.~\ref{fig:file_flock_struct}, associando ad ogni nuovo \textit{file + lock} un puntatore alla voce nella \textit{file table} da cui si è richiesto +il blocco, che così ne identifica il titolare. Il puntatore è mantenuto nel +campo \var{fl\_file} di \kstruct{file\_lock}, e viene utilizzato solo per i +\textit{file lock} creati con la semantica BSD. + +Questa struttura prevede che, quando si richiede la rimozione di un +\textit{file lock}, il kernel acconsenta solo se la richiesta proviene da un +file descriptor che fa riferimento ad una voce nella \textit{file table} +corrispondente a quella registrata nel blocco. Allora se ricordiamo quanto +visto in sez.~\ref{sec:file_dup} e sez.~\ref{sec:file_shared_access}, e cioè +che i file descriptor duplicati e quelli ereditati in un processo figlio +puntano sempre alla stessa voce nella \textit{file table}, si può capire +immediatamente quali sono le conseguenze nei confronti delle funzioni +\func{dup} e \func{fork}. + +Sarà così possibile rimuovere un \textit{file lock} attraverso uno qualunque +dei file descriptor che fanno riferimento alla stessa voce nella \textit{file + table}, anche se questo è diverso da quello con cui lo si è +creato,\footnote{attenzione, questo non vale se il file descriptor fa + riferimento allo stesso file, ma attraverso una voce diversa della + \textit{file table}, come accade tutte le volte che si apre più volte lo + stesso file.} o se si esegue la rimozione in un processo figlio. Inoltre una +volta tolto un \textit{file lock} su un file, la rimozione avrà effetto su +tutti i file descriptor che condividono la stessa voce nella \textit{file + table}, e quindi, nel caso di file descriptor ereditati attraverso una +\func{fork}, anche per processi diversi. + +Infine, per evitare che la terminazione imprevista di un processo lasci attivi +dei \textit{file lock}, quando un file viene chiuso il kernel provvede anche a +rimuovere tutti i blocchi ad esso associati. Anche in questo caso occorre +tenere presente cosa succede quando si hanno file descriptor duplicati; in tal +caso infatti il file non verrà effettivamente chiuso (ed il blocco rimosso) +fintanto che non viene rilasciata la relativa voce nella \textit{file table}; +e questo avverrà solo quando tutti i file descriptor che fanno riferimento +alla stessa voce sono stati chiusi. Quindi, nel caso ci siano duplicati o +processi figli che mantengono ancora aperto un file descriptor, il +\textit{file lock} non viene rilasciato. + + +\subsection{Il \textit{file locking} POSIX} +\label{sec:file_posix_lock} + +La seconda interfaccia per l'\textit{advisory locking} disponibile in Linux è +quella standardizzata da POSIX, basata sulla funzione di sistema +\func{fcntl}. Abbiamo già trattato questa funzione nelle sue molteplici +possibilità di utilizzo in sez.~\ref{sec:file_fcntl_ioctl}. Quando la si +impiega per il \textit{file locking} essa viene usata solo secondo il seguente +prototipo: + +\begin{funcproto}{ +\fhead{fcntl.h} +\fdecl{int fcntl(int fd, int cmd, struct flock *lock)} +\fdesc{Applica o rimuove un \textit{file lock}.} +} + +{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{EACCES}] l'operazione è proibita per la presenza di + \textit{file lock} da parte di altri processi. + \item[\errcode{EDEADLK}] si è richiesto un \textit{lock} su una regione + bloccata da un altro processo che è a sua volta in attesa dello sblocco + di un \textit{lock} mantenuto dal processo corrente; si avrebbe pertanto + un \textit{deadlock}. Non è garantito che il sistema riconosca sempre + questa situazione. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima + di poter acquisire un \textit{file lock}. + \item[\errcode{ENOLCK}] il sistema non ha le risorse per il blocco: ci + sono troppi segmenti di \textit{lock} aperti, si è esaurita la tabella + dei \textit{file lock}, o il protocollo per il blocco remoto è fallito. + \end{errlist} + ed inoltre \errval{EBADF}, \errval{EFAULT} nel loro significato generico.} +\end{funcproto} + +Al contrario di quanto avviene con l'interfaccia basata su \func{flock} con +\func{fcntl} è possibile bloccare anche delle singole sezioni di un file, fino +al singolo byte. Inoltre la funzione permette di ottenere alcune informazioni +relative agli eventuali blocchi preesistenti. Per poter fare tutto questo la +funzione utilizza come terzo argomento una apposita struttura \struct{flock} +(la cui definizione è riportata in fig.~\ref{fig:struct_flock}) nella quale +inserire tutti i dati relativi ad un determinato blocco. Si tenga presente poi +che un \textit{file lock} fa sempre riferimento ad una regione, per cui si +potrà avere un conflitto anche se c'è soltanto una sovrapposizione parziale +con un'altra regione bloccata. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{0.90\textwidth} + \includestruct{listati/flock.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{flock}, usata da \func{fcntl} per il + \textit{file locking}.} + \label{fig:struct_flock} +\end{figure} + +I primi tre campi della struttura, \var{l\_whence}, \var{l\_start} e +\var{l\_len}, servono a specificare la sezione del file a cui fa riferimento +il blocco: \var{l\_start} specifica il byte di partenza, \var{l\_len} la +lunghezza della sezione e infine \var{l\_whence} imposta il riferimento da cui +contare \var{l\_start}. Il valore di \var{l\_whence} segue la stessa semantica +dell'omonimo argomento di \func{lseek}, coi tre possibili valori +\const{SEEK\_SET}, \const{SEEK\_CUR} e \const{SEEK\_END}, (si vedano le +relative descrizioni in tab.~\ref{tab:lseek_whence_values}). + +Si tenga presente che un \textit{file lock} può essere richiesto anche per una +regione al di là della corrente fine del file, così che una eventuale +estensione dello stesso resti coperta dal blocco. Inoltre se si specifica un +valore nullo per \var{l\_len} il blocco si considera esteso fino alla +dimensione massima del file; in questo modo è possibile bloccare una qualunque +regione a partire da un certo punto fino alla fine del file, coprendo +automaticamente quanto eventualmente aggiunto in coda allo stesso. + +Lo standard POSIX non richiede che \var{l\_len} sia positivo, ed a partire dal +kernel 2.4.21 è possibile anche indicare valori di \var{l\_len} negativi, in +tal caso l'intervallo coperto va da \var{l\_start}$+$\var{l\_len} a +\var{l\_start}$-1$, mentre per un valore positivo l'intervallo va da +\var{l\_start} a \var{l\_start}$+$\var{l\_len}$-1$. Si può però usare un +valore negativo soltanto se l'inizio della regione indicata non cade prima +dell'inizio del file, mentre come accennato con un valore positivo si +può anche indicare una regione che eccede la dimensione corrente del file. + +Il tipo di \textit{file lock} richiesto viene specificato dal campo +\var{l\_type}, esso può assumere i tre valori definiti dalle costanti +riportate in tab.~\ref{tab:file_flock_type}, che permettono di richiedere +rispettivamente uno \textit{shared lock}, un \textit{esclusive lock}, e la +rimozione di un blocco precedentemente acquisito. Infine il campo \var{l\_pid} +viene usato solo in caso di lettura, quando si chiama \func{fcntl} con +\const{F\_GETLK}, e riporta il \ids{PID} del processo che detiene il +\textit{file lock}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \constd{F\_RDLCK} & Richiede un blocco condiviso (\textit{read lock}).\\ + \constd{F\_WRLCK} & Richiede un blocco esclusivo (\textit{write lock}).\\ + \constd{F\_UNLCK} & Richiede l'eliminazione di un \textit{file lock}.\\ + \hline + \end{tabular} + \caption{Valori possibili per il campo \var{l\_type} di \struct{flock}.} + \label{tab:file_flock_type} +\end{table} + +Oltre a quanto richiesto tramite i campi di \struct{flock}, l'operazione +effettivamente svolta dalla funzione è stabilita dal valore dall'argomento +\param{cmd} che, come già riportato in sez.~\ref{sec:file_fcntl_ioctl}, +specifica l'azione da compiere; i valori utilizzabili relativi al \textit{file + locking} sono tre: +\begin{basedescript}{\desclabelwidth{2.0cm}} +\item[\constd{F\_GETLK}] verifica se il \textit{file lock} specificato dalla + struttura puntata da \param{lock} può essere acquisito: in caso negativo + sovrascrive la struttura \param{flock} con i valori relativi al blocco già + esistente che ne blocca l'acquisizione, altrimenti si limita a impostarne il + campo \var{l\_type} con il valore \const{F\_UNLCK}. +\item[\constd{F\_SETLK}] se il campo \var{l\_type} della struttura puntata da + \param{lock} è \const{F\_RDLCK} o \const{F\_WRLCK} richiede il + corrispondente \textit{file lock}, se è \const{F\_UNLCK} lo rilascia; nel + caso la richiesta non possa essere soddisfatta a causa di un blocco + preesistente la funzione ritorna immediatamente con un errore di + \errcode{EACCES} o di \errcode{EAGAIN}. +\item[\constd{F\_SETLKW}] è identica a \const{F\_SETLK}, ma se la richiesta di + non può essere soddisfatta per la presenza di un altro blocco, mette il + processo in stato di attesa fintanto che il blocco precedente non viene + rilasciato; se l'attesa viene interrotta da un segnale la funzione ritorna + con un errore di \errcode{EINTR}. +\end{basedescript} + +Si noti che per quanto detto il comando \const{F\_GETLK} non serve a rilevare +una presenza generica di blocco su un file, perché se ne esistono altri +compatibili con quello richiesto, la funzione ritorna comunque impostando +\var{l\_type} a \const{F\_UNLCK}. Inoltre a seconda del valore di +\var{l\_type} si potrà controllare o l'esistenza di un qualunque tipo di +blocco (se è \const{F\_WRLCK}) o di \textit{write lock} (se è +\const{F\_RDLCK}). Si consideri poi che può esserci più di un blocco che +impedisce l'acquisizione di quello richiesto (basta che le regioni si +sovrappongano), ma la funzione ne riporterà sempre soltanto uno, impostando +\var{l\_whence} a \const{SEEK\_SET} ed i valori \var{l\_start} e \var{l\_len} +per indicare quale è la regione bloccata. + +Infine si tenga presente che effettuare un controllo con il comando +\const{F\_GETLK} e poi tentare l'acquisizione con \const{F\_SETLK} non è una +operazione atomica (un altro processo potrebbe acquisire un blocco fra le due +chiamate) per cui si deve sempre verificare il codice di ritorno di +\func{fcntl}\footnote{controllare il codice di ritorno delle funzioni invocate + è comunque una buona norma di programmazione, che permette di evitare un + sacco di errori difficili da tracciare proprio perché non vengono rilevati.} +quando la si invoca con \const{F\_SETLK}, per controllare che il blocco sia +stato effettivamente acquisito. + +\begin{figure}[!htb] + \centering \includegraphics[width=9cm]{img/file_lock_dead} + \caption{Schema di una situazione di \textit{deadlock}.} + \label{fig:file_flock_dead} +\end{figure} + +Non operando a livello di interi file, il \textit{file locking} POSIX +introduce un'ulteriore complicazione; consideriamo la situazione illustrata in +fig.~\ref{fig:file_flock_dead}, in cui il processo A blocca la regione 1 e il +processo B la regione 2. Supponiamo che successivamente il processo A richieda +un lock sulla regione 2 che non può essere acquisito per il preesistente lock +del processo 2; il processo 1 si bloccherà fintanto che il processo 2 non +rilasci il blocco. Ma cosa accade se il processo 2 nel frattempo tenta a sua +volta di ottenere un lock sulla regione A? Questa è una tipica situazione che +porta ad un \textit{deadlock}, dato che a quel punto anche il processo 2 si +bloccherebbe, e niente potrebbe sbloccare l'altro processo. Per questo motivo +il kernel si incarica di rilevare situazioni di questo tipo, ed impedirle +restituendo un errore di \errcode{EDEADLK} alla funzione che cerca di +acquisire un blocco che porterebbe ad un \textit{deadlock}. + +Per capire meglio il funzionamento del \textit{file locking} in semantica +POSIX (che differisce alquanto rispetto da quello di BSD, visto +sez.~\ref{sec:file_flock}) esaminiamo più in dettaglio come viene gestito dal +kernel. Lo schema delle strutture utilizzate è riportato in +fig.~\ref{fig:file_posix_lock}; come si vede esso è molto simile all'analogo +di fig.~\ref{fig:file_flock_struct}. In questo caso nella figura si sono +evidenziati solo i campi di \kstructd{file\_lock} significativi per la +semantica POSIX, in particolare adesso ciascuna struttura contiene, oltre al +\ids{PID} del processo in \var{fl\_pid}, la sezione di file che viene bloccata +grazie ai campi \var{fl\_start} e \var{fl\_end}. La struttura è comunque la +stessa, solo che in questo caso nel campo \var{fl\_flags} è impostato il bit +\const{FL\_POSIX} ed il campo \var{fl\_file} non viene usato. Il blocco è +sempre associato all'\textit{inode}, solo che in questo caso la titolarità non +viene identificata con il riferimento ad una voce nella \textit{file table}, +ma con il valore del \ids{PID} del processo. + +\begin{figure}[!htb] + \centering \includegraphics[width=12cm]{img/file_posix_lock} + \caption{Schema dell'architettura del \textit{file locking}, nel caso + particolare del suo utilizzo secondo l'interfaccia standard POSIX.} + \label{fig:file_posix_lock} +\end{figure} + +Quando si richiede un \textit{file lock} il kernel effettua una scansione di +tutti i blocchi presenti sul file\footnote{scandisce cioè la \textit{linked + list} delle strutture \kstruct{file\_lock}, scartando automaticamente + quelle per cui \var{fl\_flags} non è \const{FL\_POSIX}, così che le due + interfacce restano ben separate.} per verificare se la regione richiesta +non si sovrappone ad una già bloccata, in caso affermativo decide in base al +tipo di blocco, in caso negativo il nuovo blocco viene comunque acquisito ed +aggiunto alla lista. + +Nel caso di rimozione invece questa viene effettuata controllando che il +\ids{PID} del processo richiedente corrisponda a quello contenuto nel blocco. +Questa diversa modalità ha delle conseguenze precise riguardo il comportamento +dei \textit{file lock} POSIX. La prima conseguenza è che un \textit{file lock} +POSIX non viene mai ereditato attraverso una \func{fork}, dato che il processo +figlio avrà un \ids{PID} diverso, mentre passa indenne attraverso una +\func{exec} in quanto il \ids{PID} resta lo stesso. Questo comporta che, al +contrario di quanto avveniva con la semantica BSD, quando un processo termina +tutti i \textit{file lock} da esso detenuti vengono immediatamente rilasciati. + +La seconda conseguenza è che qualunque file descriptor che faccia riferimento +allo stesso file (che sia stato ottenuto con una \func{dup} o con una +\func{open} in questo caso non fa differenza) può essere usato per rimuovere +un blocco, dato che quello che conta è solo il \ids{PID} del processo. Da +questo deriva una ulteriore sottile differenza di comportamento: dato che alla +chiusura di un file i blocchi ad esso associati vengono rimossi, nella +semantica POSIX basterà chiudere un file descriptor qualunque per cancellare +tutti i blocchi relativi al file cui esso faceva riferimento, anche se questi +fossero stati creati usando altri file descriptor che restano aperti. + +Dato che il controllo sull'accesso ai blocchi viene eseguito sulla base del +\ids{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 blocchi su regioni che si sovrappongono fra loro all'interno +stesso processo. Siccome il controllo, come nel caso della rimozione, si basa +solo sul \ids{PID} del processo che chiama la funzione, queste richieste +avranno sempre successo. Nel caso della semantica BSD, essendo i lock +relativi a tutto un file e non accumulandosi,\footnote{questa ultima + caratteristica è vera in generale, se cioè si richiede più volte lo stesso + \textit{file lock}, o più blocchi sulla stessa sezione di file, le richieste + non si cumulano e basta una sola richiesta di rilascio per cancellare il + blocco.} la cosa non ha alcun effetto; la funzione ritorna con successo, +senza che il kernel debba modificare la lista dei \textit{file lock}. + +Con i \textit{file lock} POSIX invece si possono avere una serie di situazioni +diverse: ad esempio è possibile rimuovere con una sola chiamata più +\textit{file lock} distinti (indicando in una regione che si sovrapponga +completamente a quelle di questi ultimi), o rimuovere solo una parte di un +blocco preesistente (indicando una regione contenuta in quella di un altro +blocco), creando un buco, o coprire con un nuovo blocco altri \textit{file + lock} già ottenuti, e così via, a secondo di come si sovrappongono le +regioni richieste e del tipo di operazione richiesta. + +Il comportamento seguito in questo caso è che la funzione ha successo ed +esegue l'operazione richiesta sulla regione indicata; è compito del kernel +preoccuparsi di accorpare o dividere le voci nella lista dei \textit{file + lock} per far sì che le regioni bloccate da essa risultanti siano coerenti +con quanto necessario a soddisfare l'operazione richiesta. + +\begin{figure}[!htbp] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/Flock.c} + \end{minipage} + \normalsize + \caption{Sezione principale del codice del programma \file{Flock.c}.} + \label{fig:file_flock_code} +\end{figure} + +Per fare qualche esempio sul \textit{file locking} si è scritto un programma che +permette di bloccare una sezione di un file usando la semantica POSIX, o un +intero file usando la semantica BSD; in fig.~\ref{fig:file_flock_code} è +riportata il corpo principale del codice del programma, (il testo completo è +allegato nella directory dei sorgenti, nel file \texttt{Flock.c}). + +La sezione relativa alla gestione delle opzioni al solito si è omessa, come la +funzione che stampa le istruzioni per l'uso del programma, essa si cura di +impostare le variabili \var{type}, \var{start} e \var{len}; queste ultime due +vengono inizializzate al valore numerico fornito rispettivamente tramite gli +switch \code{-s} e \cmd{-l}, mentre il valore della prima viene impostato con +le opzioni \cmd{-w} e \cmd{-r} si richiede rispettivamente o un \textit{write + lock} o \textit{read lock} (i due valori sono esclusivi, la variabile +assumerà quello che si è specificato per ultimo). Oltre a queste tre vengono +pure impostate la variabile \var{bsd}, che abilita la semantica omonima quando +si invoca l'opzione \cmd{-f} (il valore preimpostato è nullo, ad indicare la +semantica POSIX), e la variabile \var{cmd} che specifica la modalità di +richiesta del \textit{file lock} (bloccante o meno), a seconda dell'opzione +\cmd{-b}. + +Il programma inizia col controllare (\texttt{\small 11-14}) che venga passato +un argomento (il file da bloccare), che sia stato scelto (\texttt{\small + 15-18}) il tipo di blocco, dopo di che apre (\texttt{\small 19}) il file, +uscendo (\texttt{\small 20-23}) in caso di errore. A questo punto il +comportamento dipende dalla semantica scelta; nel caso sia BSD occorre +reimpostare il valore di \var{cmd} per l'uso con \func{flock}; infatti il +valore preimpostato fa riferimento alla semantica POSIX e vale rispettivamente +\const{F\_SETLKW} o \const{F\_SETLK} a seconda che si sia impostato o meno la +modalità bloccante. + +Nel caso si sia scelta la semantica BSD (\texttt{\small 25-34}) prima si +controlla (\texttt{\small 27-31}) il valore di \var{cmd} per determinare se +si vuole effettuare una chiamata bloccante o meno, reimpostandone il valore +opportunamente, dopo di che a seconda del tipo di blocco al valore viene +aggiunta la relativa opzione, con un OR aritmetico, dato che \func{flock} +vuole un argomento \param{operation} in forma di maschera binaria. Nel caso +invece che si sia scelta la semantica POSIX le operazioni sono molto più +immediate si prepara (\texttt{\small 36-40}) la struttura per il lock, e lo +si esegue (\texttt{\small 41}). + +In entrambi i casi dopo aver richiesto il blocco viene controllato il +risultato uscendo (\texttt{\small 44-46}) in caso di errore, o stampando un +messaggio (\texttt{\small 47-49}) in caso di successo. Infine il programma si +pone in attesa (\texttt{\small 50}) finché un segnale (ad esempio un \cmd{C-c} +dato da tastiera) non lo interrompa; in questo caso il programma termina, e +tutti i blocchi vengono rilasciati. + +Con il programma possiamo fare varie verifiche sul funzionamento del +\textit{file locking}; cominciamo con l'eseguire un \textit{read lock} su un +file, ad esempio usando all'interno di un terminale il seguente comando: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -r Flock.c} +Lock acquired +\end{Console} +%$ +il programma segnalerà di aver acquisito un blocco e si bloccherà; in questo +caso si è usato il \textit{file locking} POSIX e non avendo specificato niente +riguardo alla sezione che si vuole bloccare sono stati usati i valori +preimpostati che bloccano tutto il file. A questo punto se proviamo ad +eseguire lo stesso comando in un altro terminale, e avremo lo stesso +risultato. Se invece proviamo ad eseguire un \textit{write lock} avremo: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -w Flock.c} +Failed lock: Resource temporarily unavailable +\end{Console} +%$ +come ci aspettiamo il programma terminerà segnalando l'indisponibilità del +blocco, dato che il file è bloccato dal precedente \textit{read lock}. Si noti +che il risultato è lo stesso anche se si richiede il blocco su una sola parte +del file con il comando: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -w -s0 -l10 Flock.c} +Failed lock: Resource temporarily unavailable +\end{Console} +%$ +se invece blocchiamo una regione con: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -r -s0 -l10 Flock.c} +Lock acquired +\end{Console} +%$ +una volta che riproviamo ad acquisire il \textit{write lock} i risultati +dipenderanno dalla regione richiesta; ad esempio nel caso in cui le due +regioni si sovrappongono avremo che: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -w -s5 -l15 Flock.c} +Failed lock: Resource temporarily unavailable +\end{Console} +%$ +ed il blocco viene rifiutato, ma se invece si richiede una regione distinta +avremo che: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -w -s11 -l15 Flock.c} +Lock acquired +\end{Console} +%$ +ed il blocco viene acquisito. Se a questo punto si prova ad eseguire un +\textit{read lock} che comprende la nuova regione bloccata in scrittura: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -r -s10 -l20 Flock.c} +Failed lock: Resource temporarily unavailable +\end{Console} +%$ +come ci aspettiamo questo non sarà consentito. + +Il programma di norma esegue il tentativo di acquisire il lock in modalità non +bloccante, se però usiamo l'opzione \cmd{-b} possiamo impostare la modalità +bloccante, riproviamo allora a ripetere le prove precedenti con questa +opzione: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -r -b -s0 -l10 Flock.c} Lock acquired +\end{Console} +%$ +il primo comando acquisisce subito un \textit{read lock}, e quindi non cambia +nulla, ma se proviamo adesso a richiedere un \textit{write lock} che non potrà +essere acquisito otterremo: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -w -s0 -l10 Flock.c} +\end{Console} +%$ +il programma cioè si bloccherà nella chiamata a \func{fcntl}; se a questo +punto rilasciamo il precedente blocco (terminando il primo comando un +\texttt{C-c} sul terminale) potremo verificare che sull'altro terminale il +blocco viene acquisito, con la comparsa di una nuova riga: + +\begin{Console} +[piccardi@gont sources]$ \textbf{./flock -w -s0 -l10 Flock.c} +Lock acquired +\end{Console} +%$ + +Un'altra cosa che si può controllare con il nostro programma è l'interazione +fra i due tipi di blocco; se ripartiamo dal primo comando con cui si è +ottenuto un blocco in lettura sull'intero file, possiamo verificare cosa +succede quando si cerca di ottenere un blocco in scrittura con la semantica +BSD: + +\begin{Console} +[root@gont sources]# \textbf{./flock -f -w Flock.c} +Lock acquired +\end{Console} +%$ +che ci mostra come i due tipi di blocco siano assolutamente indipendenti; per +questo motivo occorre sempre tenere presente quale, fra le due semantiche +disponibili, stanno usando i programmi con cui si interagisce, dato che i +blocchi applicati con l'altra non avrebbero nessun effetto. + +% \subsection{La funzione \func{lockf}} +% \label{sec:file_lockf} + +Abbiamo visto come l'interfaccia POSIX per il \textit{file locking} sia molto +più potente e flessibile di quella di BSD, questo comporta anche una maggiore +complessità per via delle varie opzioni da passare a \func{fcntl}. Per questo +motivo è disponibile anche una interfaccia semplificata che utilizza la +funzione \funcd{lockf},\footnote{la funzione è ripresa da System V e per + poterla utilizzare è richiesta che siano definite le opportune macro, una + fra \macro{\_BSD\_SOURCE} o \macro{\_SVID\_SOURCE}, oppure + \macro{\_XOPEN\_SOURCE} ad un valore di almeno 500, oppure + \macro{\_XOPEN\_SOURCE} e \macro{\_XOPEN\_SOURCE\_EXTENDED}.} il cui +prototipo è: + +\begin{funcproto}{ +\fhead{unistd.h} +\fdecl{int lockf(int fd, int cmd, off\_t len)} +\fdesc{Applica, controlla o rimuove un \textit{file lock}.} +} + +{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{EAGAIN}] il file è bloccato, e si sono richiesti + \const{F\_TLOCK} o \const{F\_TEST} (in alcuni casi può dare anche + \errcode{EACCESS}. + \item[\errcode{EBADF}] \param{fd} non è un file descriptor aperto o si sono + richiesti \const{F\_LOCK} o \const{F\_TLOCK} ma il file non è scrivibile. + \item[\errcode{EINVAL}] si è usato un valore non valido per \param{cmd}. + \end{errlist} + ed inoltre \errcode{EDEADLK} e \errcode{ENOLCK} con lo stesso significato + che hanno con \func{fcntl}. +} +\end{funcproto} + +La funzione opera sul file indicato dal file descriptor \param{fd}, che deve +essere aperto in scrittura, perché utilizza soltanto \textit{lock} +esclusivi. La sezione di file bloccata viene controllata dal valore +di \param{len}, che indica la lunghezza della stessa, usando come riferimento +la posizione corrente sul file. La sezione effettiva varia a secondo del +segno, secondo lo schema illustrato in fig.~\ref{fig:file_lockf_boundary}, se +si specifica un valore nullo il file viene bloccato a partire dalla posizione +corrente fino alla sua fine presente o futura (nello schema corrisponderebbe +ad un valore infinito positivo). + +\begin{figure}[!htb] + \centering + \includegraphics[width=10cm]{img/lockf_boundary} + \caption{Schema della sezione di file bloccata con \func{lockf}.} + \label{fig:file_lockf_boundary} +\end{figure} + +Il comportamento della funzione viene controllato dal valore +dell'argomento \param{cmd}, che specifica quale azione eseguire, i soli valori +consentiti sono i seguenti: + +\begin{basedescript}{\desclabelwidth{2.0cm}} +\item[\constd{F\_LOCK}] Richiede un \textit{lock} esclusivo sul file, e blocca + il processo chiamante se, anche parzialmente, la sezione indicata si + sovrappone ad una che è già stata bloccata da un altro processo; in caso di + sovrapposizione con un altro blocco già ottenuto le sezioni vengono unite. +\item[\constd{F\_TLOCK}] Richiede un \textit{exclusive lock}, in maniera + identica a \const{F\_LOCK}, ma in caso di indisponibilità non blocca il + processo restituendo un errore di \errval{EAGAIN}. +\item[\constd{F\_ULOCK}] Rilascia il blocco sulla sezione indicata, questo può + anche causare la suddivisione di una sezione bloccata in precedenza nelle + due parti eccedenti nel caso si sia indicato un intervallo più limitato. +\item[\constd{F\_TEST}] Controlla la presenza di un blocco sulla sezione di + file indicata, \func{lockf} ritorna $0$ se la sezione è libera o bloccata + dal processo stesso, o $-1$ se è bloccata da un altro processo, nel qual + caso \var{errno} assume il valore \errval{EAGAIN} (ma su alcuni sistemi può + essere restituito anche \errval{EACCESS}). +\end{basedescript} + +La funzione è semplicemente una diversa interfaccia al \textit{file locking} +POSIX ed è realizzata utilizzando \func{fcntl}; pertanto la semantica delle +operazioni è la stessa di quest'ultima e quindi la funzione presenta lo stesso +comportamento riguardo gli effetti della chiusura dei file, ed il +comportamento sui file duplicati e nel passaggio attraverso \func{fork} ed +\func{exec}. Per questo stesso motivo la funzione non è equivalente a +\func{flock} e può essere usata senza interferenze insieme a quest'ultima. + + +\subsection{Gli \textit{open file descriptor locks}} +\label{sec:open_file_descriptor_locks} + +Come illustrato in dettaglio nella precedente sez.~\ref{sec:file_posix_lock}, +la chiusura di un file su cui sono presenti dei \textit{file lock} comporta +l'immediato rilascio degli stessi, anche se questi sono stati acquisiti da un +processo diverso. + +da finire. + +% TODO trattare i POSIX file-private lock introdotti con il 3.15, +% vedi http://lwn.net/Articles/586904/ correlato: +% http://www.samba.org/samba/news/articles/low_point/tale_two_stds_os2.html + +\subsection{Il \textit{mandatory locking}} +\label{sec:file_mand_locking} + +\itindbeg{mandatory~locking} + +Il \textit{mandatory locking} è una opzione introdotta inizialmente in SVr4, +per introdurre un \textit{file locking} che, come dice il nome, fosse +effettivo indipendentemente dai controlli eseguiti da un processo. Con il +\textit{mandatory locking} infatti è possibile far eseguire il blocco del file +direttamente al sistema, così che, anche qualora non si predisponessero le +opportune verifiche nei processi, questo verrebbe comunque rispettato. + +Per poter utilizzare il \textit{mandatory locking} è stato introdotto un +utilizzo particolare del bit \acr{sgid} dei permessi dei file. Se si ricorda +quanto esposto in sez.~\ref{sec:file_special_perm}), esso viene di norma +utilizzato per cambiare il \ids{GID} 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_perm_management} 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 l'amministratore può passare sopra ad un \textit{file 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 blocco. Per +questo motivo l'abilitazione del \textit{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 +sez.~\ref{sec:filesystem_mounting}, 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 +ha nessun effetto sui \textit{file lock} richiesti con l'interfaccia di +\func{flock}, e che la granularità del blocco è quella del singolo byte, come +per \func{fcntl}. + +La sintassi di acquisizione dei blocchi è esattamente la stessa vista in +precedenza per \func{fcntl} e \func{lockf}, la differenza è che in caso di +\textit{mandatory lock} attivato non è più necessario controllare la +disponibilità di accesso al file, ma si potranno usare direttamente le +ordinarie funzioni di lettura e scrittura e sarà compito del kernel gestire +direttamente il \textit{file locking}. + +Questo significa che in caso di \textit{read lock} la lettura dal file potrà +avvenire normalmente con \func{read}, mentre una \func{write} si bloccherà +fino al rilascio del blocco, a meno di non aver aperto il file con +\const{O\_NONBLOCK}, nel qual caso essa ritornerà immediatamente con un errore +di \errcode{EAGAIN}. + +Se invece si è acquisito un \textit{write lock} tutti i tentativi di leggere o +scrivere sulla regione del file bloccata fermeranno il processo fino al +rilascio del blocco, a meno che il file non sia stato aperto con +\const{O\_NONBLOCK}, nel qual caso di nuovo si otterrà un ritorno immediato +con l'errore di \errcode{EAGAIN}. + +Infine occorre ricordare che le funzioni di lettura e scrittura non sono le +sole ad operare sui contenuti di un file, e che sia \func{creat} che +\func{open} (quando chiamata con \const{O\_TRUNC}) effettuano dei cambiamenti, +così come \func{truncate}, riducendone le dimensioni (a zero nei primi due +casi, a quanto specificato nel secondo). Queste operazioni sono assimilate a +degli accessi in scrittura e pertanto non potranno essere eseguite (fallendo +con un errore di \errcode{EAGAIN}) su un file su cui sia presente un qualunque +blocco (le prime due sempre, la terza solo nel caso che la riduzione delle +dimensioni del file vada a sovrapporsi ad una regione bloccata). + +L'ultimo aspetto della interazione del \textit{mandatory locking} con le +funzioni di accesso ai file è quello relativo ai file mappati in memoria (vedi +sez.~\ref{sec:file_memory_map}); anche in tal caso infatti, quando si esegue +la mappatura con l'opzione \const{MAP\_SHARED}, si ha un accesso al contenuto +del file. Lo standard SVID prevede che sia impossibile eseguire il +\textit{memory mapping} di un file su cui sono presenti dei +blocchi\footnote{alcuni sistemi, come HP-UX, sono ancora più restrittivi e lo + impediscono anche in caso di \textit{advisory locking}, anche se questo + comportamento non ha molto senso, dato che comunque qualunque accesso + diretto al file è consentito.} in Linux è stata però fatta la scelta +implementativa\footnote{per i dettagli si possono leggere le note relative + all'implementazione, mantenute insieme ai sorgenti del kernel nel file + \file{Documentation/mandatory.txt}.} di seguire questo comportamento +soltanto quando si chiama \func{mmap} con l'opzione \const{MAP\_SHARED} (nel +qual caso la funzione fallisce con il solito \errcode{EAGAIN}) che comporta la +possibilità di modificare il file. + +Si tenga conto infine che su Linux l'implementazione corrente del +\textit{mandatory locking} è difettosa e soffre di una \textit{race + condition}, per cui una scrittura con \func{write} che si sovrapponga alla +richiesta di un \textit{read lock} può modificare i dati anche dopo che questo +è stato ottenuto, ed una lettura con \func{read} può restituire dati scritti +dopo l'ottenimento di un \textit{write lock}. Lo stesso tipo di problema si +può presentare anche con l'uso di file mappati in memoria; pertanto allo stato +attuale delle cose è sconsigliabile fare affidamento sul \textit{mandatory + locking}. + +% TODO il supporto è stato reso opzionale nel 4.5, verrà eliminato nel futuro +% (vedi http://lwn.net/Articles/667210/) + +\itindend{file~locking} + +\itindend{mandatory~locking} + + +\section{L'\textit{I/O multiplexing}} +\label{sec:file_multiplexing} + + +Uno dei problemi che si presentano quando si deve operare contemporaneamente +su molti file usando le funzioni illustrate in +sez.~\ref{sec:file_unix_interface} e sez.~\ref{sec:files_std_interface} è che +si può essere bloccati nelle operazioni su un file mentre un altro potrebbe +essere disponibile. L'\textit{I/O multiplexing} nasce risposta a questo +problema. In questa sezione forniremo una introduzione a questa problematica +ed analizzeremo le varie funzioni usate per implementare questa modalità di +I/O. + + +\subsection{La problematica dell'\textit{I/O multiplexing}} +\label{sec:file_noblocking} + +Abbiamo visto in sez.~\ref{sec:sig_gen_beha}, affrontando la suddivisione fra +\textit{fast} e \textit{slow} \textit{system call}, 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 file di dispositivo, come ad esempio una seriale o un terminale, o con +l'uso di file descriptor collegati a meccanismi di intercomunicazione come le +\textit{pipe} (vedi sez.~\ref{sec:ipc_unix}) ed i socket (vedi +sez.~\ref{sec:sock_socket_def}). In casi come questi ad esempio una operazione +di lettura potrebbe bloccarsi se non ci sono dati disponibili sul descrittore +su cui la si sta effettuando. + +Questo comportamento è alla radice di una delle problematiche più comuni che +ci si trova ad affrontare nella gestione delle operazioni di I/O: la necessità +di operare su più file descriptor eseguendo funzioni che possono bloccarsi +indefinitamente senza che sia possibile prevedere quando questo può +avvenire. Un caso classico è quello di un server di rete (tratteremo la +problematica in dettaglio nella seconda parte della guida) in attesa di dati +in ingresso prevenienti da vari client. + +In un caso di questo tipo, se si andasse ad operare sui vari file descriptor +aperti uno dopo l'altro, potrebbe accadere di restare bloccati nell'eseguire +una lettura su uno di quelli che non è ``\textsl{pronto}'', quando ce ne +potrebbe essere un altro con dati disponibili. 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 +dell'operazione bloccata dipende da quanto si otterrebbe dal file descriptor +``\textsl{disponibile}'', si potrebbe addirittura arrivare ad un +\textit{deadlock}. + +\itindbeg{polling} + +Abbiamo già accennato in sez.~\ref{sec:file_open_close} che è possibile +prevenire questo tipo di comportamento delle funzioni di I/O aprendo un file +in \textsl{modalità non-bloccante}, attraverso l'uso del flag +\const{O\_NONBLOCK} nella chiamata di \func{open}. In questo caso le funzioni +di lettura o scrittura eseguite sul file che si sarebbero bloccate ritornano +immediatamente, restituendo l'errore \errcode{EAGAIN}. L'utilizzo di questa +modalità di I/O permette di risolvere il problema controllando a turno i vari +file descriptor, in un ciclo in cui si ripete l'accesso fintanto che esso non +viene garantito. Ovviamente questa tecnica, detta \textit{polling}, è +estremamente inefficiente: si tiene costantemente impiegata la CPU solo per +eseguire in continuazione delle \textit{system call} che nella gran parte dei +casi falliranno. + +\itindend{polling} + +É appunto per superare questo problema è stato introdotto il concetto di +\textit{I/O multiplexing}, una nuova modalità per la gestione dell'I/O che +consente di tenere sotto controllo più file descriptor in contemporanea, +permettendo di bloccare un processo quando le operazioni di lettura o +scrittura non sono immediatamente effettuabili, e di riprenderne l'esecuzione +una volta che almeno una di quelle che erano state richieste diventi +possibile, in modo da poterla eseguire con la sicurezza di non restare +bloccati. + +Dato che, come abbiamo già accennato, per i normali file su disco non si ha +mai un accesso bloccante, l'uso più comune delle funzioni che esamineremo nei +prossimi paragrafi è per i server di rete, in cui esse vengono utilizzate per +tenere sotto controllo dei socket; pertanto ritorneremo su di esse con +ulteriori dettagli e qualche esempio di utilizzo concreto in +sez.~\ref{sec:TCP_sock_multiplexing}. + + +\subsection{Le funzioni \func{select} e \func{pselect}} +\label{sec:file_select} + +Il primo kernel unix-like ad introdurre una interfaccia per l'\textit{I/O + multiplexing} è stato BSD, con la funzione \funcd{select} che è apparsa in +BSD4.2 ed è stata standardizzata in BSD4.4, in seguito è stata portata su +tutti i sistemi che supportano i socket, compreso le varianti di System V ed +inserita in POSIX.1-2001; il suo prototipo è:\footnote{l'header + \texttt{sys/select.h} è stato introdotto con POSIX.1-2001, è ed presente con + la \acr{glibc} a partire dalla versione 2.0, in precedenza, con le + \acr{libc4} e \acr{libc5}, occorreva includere \texttt{sys/time.h}, + \texttt{sys/types.h} e \texttt{unistd.h}.} + +\begin{funcproto}{ +\fhead{sys/select.h} +\fdecl{int select(int ndfs, fd\_set *readfds, fd\_set *writefds, fd\_set + *exceptfds, \\ +\phantom{int select(}struct timeval *timeout)} +\fdesc{Attende che uno fra i file descriptor degli insiemi specificati diventi + attivo.} +} +{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}] si è specificato un file descriptor non valido + (chiuso o con errori) in uno degli insiemi. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. + \item[\errcode{EINVAL}] si è specificato per \param{ndfs} un valore negativo + o un valore non valido per \param{timeout}. + \end{errlist} + ed inoltre \errval{ENOMEM} nel suo significato generico.} +\end{funcproto} + +La funzione mette il processo in stato di \textit{sleep} (vedi +tab.~\ref{tab:proc_proc_states}) fintanto che almeno uno dei file descriptor +degli insiemi specificati (\param{readfds}, \param{writefds} e +\param{exceptfds}), non diventa attivo, per un tempo massimo specificato da +\param{timeout}. + +\itindbeg{file~descriptor~set} + +Per specificare quali file descriptor si intende selezionare la funzione usa +un particolare oggetto, il \textit{file descriptor set}, identificato dal tipo +\typed{fd\_set}, che serve ad identificare un insieme di file descriptor, in +maniera analoga a come un \textit{signal set} (vedi sez.~\ref{sec:sig_sigset}) +identifica un insieme di segnali. Per la manipolazione di questi \textit{file + descriptor set} si possono usare delle opportune macro di preprocessore: + +{\centering +\vspace{3pt} +\begin{funcbox}{ +\fhead{sys/select.h} +\fdecl{void \macrod{FD\_ZERO}(fd\_set *set)} +\fdesc{Inizializza l'insieme (vuoto).} +\fdecl{void \macrod{FD\_SET}(int fd, fd\_set *set)} +\fdesc{Inserisce il file descriptor \param{fd} nell'insieme.} +\fdecl{void \macrod{FD\_CLR}(int fd, fd\_set *set)} +\fdesc{Rimuove il file descriptor \param{fd} dall'insieme.} +\fdecl{int \macrod{FD\_ISSET}(int fd, fd\_set *set)} +\fdesc{Controlla se il file descriptor \param{fd} è nell'insieme.} +} +\end{funcbox}} + + +In genere un \textit{file descriptor set} può contenere fino ad un massimo di +\macrod{FD\_SETSIZE} file descriptor. Questo valore in origine corrispondeva +al limite per il numero massimo di file aperti (ad esempio in Linux, fino alla +serie 2.0.x, c'era un limite di 256 file per processo), ma da quando, nelle +versioni più recenti del kernel, questo limite è stato rimosso, esso indica le +dimensioni massime dei numeri usati nei \textit{file descriptor set}, ed il +suo valore, secondo lo standard POSIX 1003.1-2001, è definito in +\headfile{sys/select.h}, ed è pari a 1024. + +Si tenga presente che i \textit{file descriptor set} devono sempre essere +inizializzati con \macro{FD\_ZERO}; passare a \func{select} un valore non +inizializzato può dar luogo a comportamenti non prevedibili. Allo stesso modo +usare \macro{FD\_SET} o \macro{FD\_CLR} con un file descriptor il cui valore +eccede \macro{FD\_SETSIZE} può dare luogo ad un comportamento indefinito. + +La funzione richiede di specificare tre insiemi distinti di file descriptor; +il primo, \param{readfds}, verrà osservato per rilevare la disponibilità di +effettuare una lettura,\footnote{per essere precisi la funzione ritornerà in + tutti i casi in cui la successiva esecuzione di \func{read} risulti non + bloccante, quindi anche in caso di \textit{end-of-file}.} il secondo, +\param{writefds}, per verificare la possibilità di effettuare una scrittura ed +il terzo, \param{exceptfds}, per verificare l'esistenza di eccezioni come i +dati urgenti su un socket, (vedi sez.~\ref{sec:TCP_urgent_data}). + +Dato che in genere non si tengono mai sotto controllo fino a +\macro{FD\_SETSIZE} file contemporaneamente, la funzione richiede di +specificare qual è il valore più alto fra i file descriptor indicati nei tre +insiemi precedenti. Questo viene fatto per efficienza, per evitare di passare +e far controllare al kernel una quantità di memoria superiore a quella +necessaria. Questo limite viene indicato tramite l'argomento \param{ndfs}, che +deve corrispondere al valore massimo aumentato di uno. Si ricordi infatti che +i file descriptor sono numerati progressivamente a partire da zero, ed il +valore indica il numero più alto fra quelli da tenere sotto controllo, +dimenticarsi di aumentare di uno il valore di \param{ndfs} è un errore comune. + +Infine l'argomento \param{timeout}, espresso con il puntatore ad una struttura +di tipo \struct{timeval} (vedi fig.~\ref{fig:sys_timeval_struct}) specifica un +tempo massimo di attesa prima che la funzione ritorni; se impostato a +\val{NULL} la funzione attende indefinitamente. Si può specificare anche un +tempo nullo (cioè una struttura \struct{timeval} con i campi impostati a +zero), qualora si voglia semplicemente controllare lo stato corrente dei file +descriptor, e così può essere utilizzata eseguire il \textit{polling} su un +gruppo di file descriptor. Usare questo argomento con tutti i \textit{file + descriptor set} vuoti è un modo portabile, disponibile anche su sistemi in +cui non sono disponibili le funzioni avanzate di sez.~\ref{sec:sig_timer_adv}, +per tenere un processo in stato di \textit{sleep} con precisioni inferiori al +secondo. + +In caso di successo la funzione restituisce il numero di file descriptor +pronti, seguendo il comportamento previsto dallo standard +POSIX.1-2001,\footnote{si tenga però presente che esistono alcune versioni di + Unix che non si comportano in questo modo, restituendo un valore positivo + generico.} e ciascun insieme viene sovrascritto per indicare quali sono i +file descriptor pronti per le operazioni ad esso relative, in modo da poterli +controllare con \macro{FD\_ISSET}. Se invece scade il tempo indicato +da \param{timout} viene restituito un valore nullo e i \textit{file descriptor + set} non vengono modificati. In caso di errore la funzione restituisce $-1$, i +valori dei tre insiemi e di \param{timeout} sono indefiniti e non si può fare +nessun affidamento sul loro contenuto; nelle versioni più recenti della +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{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 +conseguente possibile errore nel caso lo si usi senza che sia stato +riaperto. Lo standard non prevede niente al riguardo e non si deve dare per +assunto nessuno dei due comportamenti se si vogliono scrivere programmi +portabili. + +\itindend{file~descriptor~set} + +Una volta ritornata la funzione, si potrà controllare quali sono i file +descriptor pronti, ed operare su di essi. Si tenga presente però che +\func{select} fornisce solo di un suggerimento, esistono infatti condizioni in +cui \func{select} può riportare in maniera spuria che un file descriptor è +pronto, ma l'esecuzione di una operazione di I/O si bloccherebbe: ad esempio +con Linux questo avviene quando su un socket arrivano dei dati che poi vengono +scartati perché corrotti (ma sono possibili pure altri casi); in tal caso pur +risultando il relativo file descriptor pronto in lettura una successiva +esecuzione di una \func{read} si bloccherebbe. Per questo motivo quando si usa +l'\textit{I/O multiplexing} è sempre raccomandato l'uso delle funzioni di +lettura e scrittura in modalità non bloccante. + +Su Linux quando la \textit{system call} \func{select} viene interrotta da un +segnale modifica il valore nella struttura puntata da \param{timeout}, +impostandolo al tempo restante. In tal caso infatti si ha un errore di +\errcode{EINTR} ed occorre rilanciare la funzione per proseguire l'attesa, ed +in questo modo non è necessario ricalcolare tutte le volte il tempo +rimanente. Questo può causare problemi di portabilità sia quando si usa codice +scritto su Linux che legge questo valore, sia quando si usano programmi +scritti per altri sistemi che non dispongono di questa caratteristica e +ricalcolano \param{timeout} tutte le volte. In genere questa caratteristica è +disponibile nei sistemi che derivano da System V e non è disponibile per +quelli che derivano da BSD; lo standard POSIX.1-2001 non permette questo +comportamento e per questo motivo la \acr{glibc} nasconde il comportamento +passando alla \textit{system call} una copia dell'argomento \param{timeout}. + +Uno dei problemi che si presentano con l'uso di \func{select} è che il suo +comportamento dipende dal valore del file descriptor che si vuole tenere sotto +controllo. Infatti il kernel riceve con \param{ndfs} un limite massimo per +tale valore, e per capire quali sono i file descriptor da tenere sotto +controllo dovrà effettuare una scansione su tutto l'intervallo, che può anche +essere molto ampio anche se i file descriptor sono solo poche unità; tutto ciò +ha ovviamente delle conseguenze ampiamente negative per le prestazioni. + +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 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. + +Lo standard POSIX è rimasto a lungo senza primitive per l'\textit{I/O + multiplexing}, introdotto solo con le ultime revisioni dello standard (POSIX +1003.1g-2000 e POSIX 1003.1-2001). La scelta è stata quella di seguire +l'interfaccia creata da BSD, ma prevede che tutte le funzioni ad esso relative +vengano dichiarate nell'header \headfiled{sys/select.h}, che sostituisce i +precedenti, ed inoltre aggiunge a \func{select} una nuova funzione +\funcd{pselect},\footnote{il supporto per lo standard POSIX 1003.1-2001, ed + l'header \headfile{sys/select.h}, compaiono in Linux a partire dalla + \acr{glibc} 2.1. Le \acr{libc4} e \acr{libc5} non contengono questo header, + la \acr{glibc} 2.0 contiene una definizione sbagliata di \func{psignal}, + senza l'argomento \param{sigmask}, la definizione corretta è presente dalle + \acr{glibc} 2.1-2.2.1 se si è definito \macro{\_GNU\_SOURCE} e nelle + \acr{glibc} 2.2.2-2.2.4 se si è definito \macro{\_XOPEN\_SOURCE} con valore + maggiore di 600.} il cui prototipo è: + +\begin{funcproto}{ +\fhead{sys/select.h} +\fdecl{int pselect(int n, fd\_set *readfds, fd\_set *writefds, + fd\_set *exceptfds, \\ +\phantom{int pselect(}struct timespec *timeout, sigset\_t *sigmask)} +\fdesc{Attende che uno dei file descriptor degli insiemi specificati diventi + attivo.} +} +{La funzione ritorna il numero (anche nullo) di file descriptor che sono + attivi in caso di successo e $-1$ per un errore, nel qual caso \var{errno} + assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] si è specificato un file descriptor sbagliato in uno + degli insiemi. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. + \item[\errcode{EINVAL}] si è specificato per \param{ndfs} un valore negativo + o un valore non valido per \param{timeout}. + \end{errlist} + ed inoltre \errval{ENOMEM} nel suo significato generico. +} +\end{funcproto} + +La funzione è sostanzialmente identica a \func{select}, solo che usa una +struttura \struct{timespec} (vedi fig.~\ref{fig:sys_timespec_struct}) per +indicare con maggiore precisione il timeout e non ne aggiorna il valore in +caso di interruzione. In realtà anche in questo caso la \textit{system call} +di Linux aggiorna il valore al tempo rimanente, ma la funzione fornita dalla +\acr{glibc} modifica questo comportamento passando alla \textit{system call} +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 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} 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 +impostare una variabile globale e controllare questa nel corpo principale del +programma; abbiamo visto in quell'occasione come questo lasci spazio a +possibili \textit{race condition}, per cui diventa essenziale utilizzare +\func{sigprocmask} per disabilitare la ricezione del segnale prima di eseguire +il controllo e riabilitarlo dopo l'esecuzione delle relative operazioni, onde +evitare l'arrivo di un segnale immediatamente dopo il controllo, che andrebbe +perso. + +Nel nostro caso il problema si pone quando, oltre al segnale, si devono tenere +sotto controllo anche dei file descriptor con \func{select}, in questo caso si +può fare conto sul fatto che all'arrivo di un segnale essa verrebbe interrotta +e si potrebbero eseguire di conseguenza le operazioni relative al segnale e +alla gestione dati con un ciclo del tipo: +\includecodesnip{listati/select_race.c} +qui però emerge una \textit{race condition}, perché se il segnale arriva prima +della chiamata a \func{select}, questa non verrà interrotta, e la ricezione +del segnale non sarà rilevata. + +Per questo è stata introdotta \func{pselect} che attraverso l'argomento +\param{sigmask} permette di riabilitare la ricezione il segnale +contestualmente all'esecuzione della funzione,\footnote{in Linux però, fino al + kernel 2.6.16, non era presente la relativa \textit{system call}, e la + funzione era implementata nella \acr{glibc} attraverso \func{select} (vedi + \texttt{man select\_tut}) per cui la possibilità di \textit{race condition} + permaneva; in tale situazione si può ricorrere ad una soluzione alternativa, + chiamata \itindex{self-pipe~trick} \textit{self-pipe trick}, che consiste + nell'aprire una \textit{pipe} (vedi sez.~\ref{sec:ipc_pipes}) ed usare + \func{select} sul capo in lettura della stessa; si può indicare l'arrivo di + un segnale scrivendo sul capo in scrittura all'interno del gestore dello + stesso; in questo modo anche se il segnale va perso prima della chiamata di + \func{select} questa lo riconoscerà comunque dalla presenza di dati sulla + \textit{pipe}.} ribloccandolo non appena essa ritorna, così che il +precedente codice potrebbe essere riscritto nel seguente modo: +\includecodesnip{listati/pselect_norace.c} +in questo caso utilizzando \var{oldmask} durante l'esecuzione di +\func{pselect} la ricezione del segnale sarà abilitata, ed in caso di +interruzione si potranno eseguire le relative operazioni. + + +\subsection{Le funzioni \func{poll} e \func{ppoll}} +\label{sec:file_poll} + +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 \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 \typed{nfds\_t}.} il +cui prototipo è: + +\begin{funcproto}{ +\fhead{sys/poll.h} +\fdecl{int poll(struct pollfd *ufds, nfds\_t nfds, int timeout)} +\fdesc{Attende un cambiamento di stato su un insieme di file + descriptor.} +} + +{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}] si è specificato un file descriptor sbagliato in uno + degli insiemi. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. + \item[\errcode{EINVAL}] il valore di \param{nfds} eccede il limite + \const{RLIMIT\_NOFILE}. + \end{errlist} + ed inoltre \errval{EFAULT} e \errval{ENOMEM} nel loro significato generico.} +\end{funcproto} + +La funzione permette di tenere sotto controllo contemporaneamente \param{ndfs} +file descriptor, specificati attraverso il puntatore \param{ufds} ad un +vettore di strutture \struct{pollfd}. Come con \func{select} si può +interrompere l'attesa dopo un certo tempo, questo deve essere specificato con +l'argomento \param{timeout} in numero di millisecondi: un valore negativo +indica un'attesa indefinita, mentre un valore nullo comporta il ritorno +immediato, e può essere utilizzato per impiegare \func{poll} in modalità +\textsl{non-bloccante}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{0.90\textwidth} + \includestruct{listati/pollfd.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{pollfd}, utilizzata per specificare le + modalità di controllo di un file descriptor alla funzione \func{poll}.} + \label{fig:file_pollfd} +\end{figure} + +Per ciascun file da controllare deve essere inizializzata una struttura +\struct{pollfd} nel vettore indicato dall'argomento \param{ufds}. La +struttura, la cui definizione è riportata in fig.~\ref{fig:file_pollfd}, +prevede tre campi: in \var{fd} deve essere indicato il numero del file +descriptor da controllare, in \var{events} deve essere specificata una +maschera binaria di flag che indichino il tipo di evento che si vuole +controllare, mentre in \var{revents} il kernel restituirà il relativo +risultato. + +Usando un valore negativo per \param{fd} la corrispondente struttura sarà +ignorata da \func{poll} ed il campo \var{revents} verrà azzerato, questo +consente di eliminare temporaneamente un file descriptor dalla lista senza +dover modificare il vettore \param{ufds}. Dato che i dati in ingresso sono del +tutto indipendenti da quelli in uscita (che vengono restituiti in +\var{revents}) non è necessario reinizializzare tutte le volte il valore delle +strutture \struct{pollfd} a meno di non voler cambiare qualche condizione. + +Le costanti che definiscono i valori relativi ai bit usati nelle maschere +binarie dei campi \var{events} e \var{revents} sono riportate in +tab.~\ref{tab:file_pollfd_flags}, insieme al loro significato. Le si sono +suddivise in tre gruppi principali, nel primo gruppo si sono indicati i bit +utilizzati per controllare l'attività in ingresso, nel secondo quelli per +l'attività in uscita, infine il terzo gruppo contiene dei valori che vengono +utilizzati solo nel campo \var{revents} per notificare delle condizioni di +errore. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Flag} & \textbf{Significato} \\ + \hline + \hline + \constd{POLLIN} & È possibile la lettura.\\ + \constd{POLLRDNORM}& Sono disponibili in lettura dati normali.\\ + \constd{POLLRDBAND}& Sono disponibili in lettura dati prioritari.\\ + \constd{POLLPRI} & È possibile la lettura di dati urgenti.\\ + \hline + \constd{POLLOUT} & È possibile la scrittura immediata.\\ + \constd{POLLWRNORM}& È possibile la scrittura di dati normali.\\ + \constd{POLLWRBAND}& È possibile la scrittura di dati prioritari.\\ + \hline + \constd{POLLERR} & C'è una condizione di errore.\\ + \constd{POLLHUP} & Si è verificato un hung-up.\\ + \constd{POLLRDHUP} & Si è avuta una \textsl{half-close} su un + socket.\footnotemark\\ + \constd{POLLNVAL} & Il file descriptor non è aperto.\\ + \hline + \constd{POLLMSG} & Definito per compatibilità con SysV.\\ + \hline + \end{tabular} + \caption{Costanti per l'identificazione dei vari bit dei campi + \var{events} e \var{revents} di \struct{pollfd}.} + \label{tab:file_pollfd_flags} +\end{table} + +\footnotetext{si tratta di una estensione specifica di Linux, disponibile a + partire dal kernel 2.6.17 definendo la marco \macro{\_GNU\_SOURCE}, che + consente di riconoscere la chiusura in scrittura dell'altro capo di un + socket, situazione che si viene chiamata appunto \textit{half-close} + (\textsl{mezza chiusura}) su cui torneremo con maggiori dettagli in + sez.~\ref{sec:TCP_shutdown}.} + +Il valore \const{POLLMSG} non viene utilizzato ed è definito solo per +compatibilità con l'implementazione di System V che usa i cosiddetti +``\textit{stream}''. Si tratta di una interfaccia specifica di SysV non +presente in Linux, che non ha nulla a che fare con gli \textit{stream} delle +librerie standard del C visti in sez.~\ref{sec:file_stream}. Da essa derivano +i nomi di alcune costanti poiché per quegli \textit{stream} sono definite tre +classi di dati: \textsl{normali}, \textit{prioritari} ed \textit{urgenti}. In +Linux la distinzione ha senso solo per i dati urgenti dei socket (vedi +sez.~\ref{sec:TCP_urgent_data}), ma su questo e su come \func{poll} reagisce +alle varie condizioni dei socket torneremo in sez.~\ref{sec:TCP_serv_poll}, +dove vedremo anche un esempio del suo utilizzo. + +Le costanti relative ai diversi tipi di dati normali e prioritari che fanno +riferimento alle implementazioni in stile System V sono \const{POLLRDNORM}, +\const{POLLWRNORM}, \const{POLLRDBAND} e \const{POLLWRBAND}. Le prime due sono +equivalenti rispettivamente a \const{POLLIN} e \const{POLLOUT}, +\const{POLLRDBAND} non viene praticamente mai usata su Linux mentre +\const{POLLWRBAND} ha senso solo sui socket. In ogni caso queste costanti sono +utilizzabili soltanto qualora si sia definita la macro +\macro{\_XOPEN\_SOURCE}. + +In caso di successo \func{poll} ritorna restituendo il numero di file (un +valore positivo) per i quali si è verificata una delle condizioni di attesa +richieste o per i quali si è verificato un errore, avvalorando i relativi bit +di \var{revents}. In caso di errori sui file vengono utilizzati i valori della +terza sezione di tab.~\ref{tab:file_pollfd_flags} che hanno significato solo +per \var{revents} (se specificati in \var{events} vengono ignorati). Un valore +di ritorno nullo indica che si è raggiunto il timeout, mentre un valore +negativo indica un errore nella chiamata, il cui codice viene riportato al +solito tramite \var{errno}. + +L'uso di \func{poll} consente di superare alcuni dei problemi illustrati in +precedenza per \func{select}; anzitutto, dato che in questo caso si usa un +vettore di strutture \struct{pollfd} di dimensione arbitraria, non esiste il +limite introdotto dalle dimensioni massime di un \textit{file descriptor set} +e la dimensione dei dati passati al kernel dipende solo dal numero dei file +descriptor che si vogliono controllare, non dal loro valore. Infatti, anche se +usando dei bit un \textit{file descriptor set} può essere più efficiente di un +vettore di strutture \struct{pollfd}, qualora si debba osservare un solo file +descriptor con un valore molto alto ci si troverà ad utilizzare inutilmente un +maggiore quantitativo di memoria. + +Inoltre con \func{select} lo stesso \textit{file descriptor set} è usato sia +in ingresso che in uscita, e questo significa che tutte le volte che si vuole +ripetere l'operazione occorre reinizializzarlo da capo. Questa operazione, che +può essere molto onerosa se i file descriptor da tenere sotto osservazione +sono molti, non è invece necessaria con \func{poll}. + +Abbiamo visto in sez.~\ref{sec:file_select} come lo standard POSIX preveda una +variante di \func{select} che consente di gestire correttamente la ricezione +dei segnali nell'attesa su un file descriptor. Con l'introduzione di una +implementazione reale di \func{pselect} nel kernel 2.6.16, è stata aggiunta +anche una analoga funzione che svolga lo stesso ruolo per \func{poll}. + +In questo caso si tratta di una estensione che è specifica di Linux e non è +prevista da nessuno standard; essa può essere utilizzata esclusivamente se si +definisce la macro \macro{\_GNU\_SOURCE} ed ovviamente non deve essere usata +se si ha a cuore la portabilità. La funzione è \funcd{ppoll}, ed il suo +prototipo è: + +\begin{funcproto}{ +\fhead{sys/poll.h} +\fdecl{int ppoll(struct pollfd *fds, nfds\_t nfds, + const struct timespec *timeout, \\ +\phantom{int ppoll(}const sigset\_t *sigmask)} + +\fdesc{Attende un cambiamento di stato su un insieme di file descriptor.} +} + +{La funzione ritorna il numero di file descriptor con attività in caso di + successo, $0$ se c'è stato un timeout e $-1$ per un errore, nel qual caso + \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] si è specificato un file descriptor sbagliato in uno + degli insiemi. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. + \item[\errcode{EINVAL}] il valore di \param{nfds} eccede il limite + \const{RLIMIT\_NOFILE}. + \end{errlist} +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 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 +puntatore ad una struttura \struct{timespec}, gli altri argomenti comuni con +\func{poll} hanno lo stesso significato, e la funzione restituisce gli stessi +risultati illustrati in precedenza. Come nel caso di \func{pselect} la +\textit{system call} che implementa \func{ppoll} restituisce, se la funzione +viene interrotta da un segnale, il tempo mancante in \param{timeout}, e come +per \func{pselect} la funzione di libreria fornita dalla \acr{glibc} maschera +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 disponibilità di +accesso ai file descriptor illustrate per \func{select} in +sez.~\ref{sec:file_select}, che non staremo a ripetere qui. + +\subsection{L'interfaccia di \textit{epoll}} +\label{sec:file_epoll} + +\itindbeg{epoll} + +Nonostante \func{poll} presenti alcuni vantaggi rispetto a \func{select}, +anche questa funzione non è molto efficiente quando deve essere utilizzata con +un gran numero di file descriptor,\footnote{in casi del genere \func{select} + viene scartata a priori, perché può avvenire che il numero di file + descriptor ecceda le dimensioni massime di un \textit{file descriptor set}.} +in particolare nel caso in cui solo pochi di questi diventano attivi. Il +problema in questo caso è che il tempo impiegato da \func{poll} a trasferire i +dati da e verso il kernel è proporzionale al numero di file descriptor +osservati, non a quelli che presentano attività. + +Quando ci sono decine di migliaia di file descriptor osservati e migliaia di +eventi al secondo (il caso classico è quello di un server web di un sito con +molti accessi) l'uso di \func{poll} comporta la necessità di trasferire avanti +ed indietro da \textit{user space} a \textit{kernel space} una lunga lista di +strutture \struct{pollfd} migliaia di volte al secondo. A questo poi si +aggiunge il fatto che la maggior parte del tempo di esecuzione sarà impegnato +ad eseguire una scansione su tutti i file descriptor tenuti sotto controllo +per determinare quali di essi (in genere una piccola percentuale) sono +diventati attivi. In una situazione come questa l'uso delle funzioni classiche +dell'interfaccia dell'\textit{I/O multiplexing} viene a costituire un collo di +bottiglia che degrada irrimediabilmente le prestazioni. + +Per risolvere questo tipo di situazioni sono state ideate delle interfacce +specialistiche (come \texttt{/dev/poll} in Solaris, o \texttt{kqueue} in BSD) +il cui scopo fondamentale è quello di restituire solamente le informazioni +relative ai file descriptor osservati che presentano una attività, evitando +così le problematiche appena illustrate. In genere queste prevedono che si +registrino una sola volta i file descriptor da tenere sotto osservazione, e +forniscono un meccanismo che notifica quali di questi presentano attività. + +Le modalità con cui avviene la notifica sono due, la prima è quella classica +(quella usata da \func{poll} e \func{select}) che viene chiamata \textit{level + triggered}.\footnote{la nomenclatura è stata introdotta da Jonathan Lemon in + un articolo su \texttt{kqueue} al BSDCON 2000, e deriva da quella usata + nell'elettronica digitale.} In questa modalità vengono notificati i file +descriptor che sono \textsl{pronti} per l'operazione richiesta, e questo +avviene indipendentemente dalle operazioni che possono essere state fatte su +di essi a partire dalla precedente notifica. Per chiarire meglio il concetto +ricorriamo ad un esempio: se su un file descriptor sono diventati disponibili +in lettura 2000 byte ma dopo la notifica ne sono letti solo 1000 (ed è quindi +possibile eseguire una ulteriore lettura dei restanti 1000), in modalità +\textit{level triggered} questo sarà nuovamente notificato come +\textsl{pronto}. + +La seconda modalità, è detta \textit{edge triggered}, e prevede che invece +vengano notificati solo i file descriptor che hanno subito una transizione da +\textsl{non pronti} a \textsl{pronti}. Questo significa che in modalità +\textit{edge triggered} nel caso del precedente esempio il file descriptor +diventato pronto da cui si sono letti solo 1000 byte non verrà nuovamente +notificato come pronto, nonostante siano ancora disponibili in lettura 1000 +byte. Solo una volta che si saranno esauriti tutti i dati disponibili, e che +il file descriptor sia tornato non essere pronto, si potrà ricevere una +ulteriore notifica qualora ritornasse pronto. + +Nel caso di Linux al momento la sola interfaccia che fornisce questo tipo di +servizio è chiamata \textit{epoll},\footnote{l'interfaccia è stata creata da + Davide Libenzi, ed è stata introdotta per la prima volta nel kernel 2.5.44, + ma la sua forma definitiva è stata raggiunta nel kernel 2.5.66, il supporto + è stato aggiunto nella \acr{glibc} a partire dalla versione 2.3.2.} anche se +sono state in discussione altre interfacce con le quali effettuare lo stesso +tipo di operazioni; \textit{epoll} è in grado di operare sia in modalità +\textit{level triggered} che \textit{edge triggered}. + +La prima versione di \textit{epoll} prevedeva l'uso di uno speciale file di +dispositivo, \texttt{/dev/epoll}, per ottenere un file descriptor da +utilizzare con le funzioni dell'interfaccia ma poi si è passati all'uso di +apposite \textit{system call}. Il primo passo per usare l'interfaccia di +\textit{epoll} è pertanto quello ottenere detto file descriptor chiamando una +delle due funzioni di sistema \funcd{epoll\_create} e \funcd{epoll\_create1}, +i cui prototipi sono: + +\begin{funcproto}{ +\fhead{sys/epoll.h} +\fdecl{int epoll\_create(int size)} +\fdecl{int epoll\_create1(int flags)} + +\fdesc{Apre un file descriptor per \textit{epoll}.} +} +{Le funzioni ritornano un file descriptor per \textit{epoll} 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 di \param{size} non + positivo o non valido per \param{flags}. + \item[\errcode{EMFILE}] si è raggiunto il limite sul numero massimo di + istanze di \textit{epoll} per utente stabilito da + \sysctlfiled{fs/epoll/max\_user\_instances}. + \item[\errcode{ENFILE}] si è raggiunto il massimo di file descriptor aperti + nel sistema. + \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel per creare + l'istanza. + \end{errlist} +} +\end{funcproto} + +Entrambe le funzioni restituiscono un file descriptor, detto anche +\textit{epoll descriptor}; si tratta di un file descriptor speciale (per cui +\func{read} e \func{write} non sono supportate) che viene associato alla +infrastruttura utilizzata dal kernel per gestire la notifica degli eventi, e +che può a sua volta essere messo sotto osservazione con una chiamata a +\func{select}, \func{poll} o \func{epoll\_ctl}; in tal caso risulterà pronto +quando saranno disponibili eventi da notificare riguardo i file descriptor da +lui osservati.\footnote{è anche possibile inviarlo ad un altro processo + attraverso un socket locale (vedi sez.~\ref{sec:sock_fd_passing}) ma + l'operazione non ha alcun senso dato che il nuovo processo non avrà a + disposizione le copie dei file descriptor messe sotto osservazione tramite + esso.} Una volta che se ne sia terminato l'uso si potranno rilasciare tutte +le risorse allocate chiudendolo semplicemente con \func{close}. + +Nel caso di \func{epoll\_create} l'argomento \param{size} serviva a dare +l'indicazione del numero di file descriptor che si vorranno tenere sotto +controllo, e costituiva solo un suggerimento per semplificare l'allocazione di +risorse sufficienti, non un valore massimo, ma a partire dal kernel 2.6.8 esso +viene totalmente ignorato e l'allocazione è sempre dinamica. + +La seconda versione della funzione, \func{epoll\_create1} è stata introdotta +come estensione della precedente (è disponibile solo a partire dal kernel +2.6.27) per poter passare dei flag di controllo come maschera binaria in fase +di creazione del file descriptor. Al momento l'unico valore legale per +\param{flags} (a parte lo zero) è \constd{EPOLL\_CLOEXEC}, che consente di +impostare in maniera atomica sul file descriptor il flag di +\textit{close-on-exec} (vedi sez.~\ref{sec:proc_exec} e +sez.~\ref{sec:file_shared_access}) senza che sia necessaria una successiva +chiamata a \func{fcntl}. + +Una volta ottenuto un file descriptor per \textit{epoll} il passo successivo è +indicare quali file descriptor mettere sotto osservazione e quali operazioni +controllare, per questo si deve usare la seconda funzione di sistema +dell'interfaccia, \funcd{epoll\_ctl}, il cui prototipo è: + +\begin{funcproto}{ +\fhead{sys/epoll.h} +\fdecl{int epoll\_ctl(int epfd, int op, int fd, struct epoll\_event *event)} + +\fdesc{Esegue le operazioni di controllo di \textit{epoll}.} +} + +{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}] i file descriptor \param{epfd} o \param{fd} non sono + validi. + \item[\errcode{EEXIST}] l'operazione richiesta è \const{EPOLL\_CTL\_ADD} ma + \param{fd} è già stato inserito in \param{epfd}. + \item[\errcode{EINVAL}] il file descriptor \param{epfd} non è stato ottenuto + con \func{epoll\_create}, o \param{fd} è lo stesso \param{epfd} o + l'operazione richiesta con \param{op} non è supportata. + \item[\errcode{ENOENT}] l'operazione richiesta è \const{EPOLL\_CTL\_MOD} o + \const{EPOLL\_CTL\_DEL} ma \param{fd} non è inserito in \param{epfd}. + \item[\errcode{ENOMEM}] non c'è sufficiente memoria nel kernel gestire + l'operazione richiesta. + \item[\errcode{ENOSPC}] si è raggiunto il limite massimo di registrazioni + per utente di file descriptor da osservare imposto da + \sysctlfiled{fs/epoll/max\_user\_watches}. + \item[\errcode{EPERM}] il file associato a \param{fd} non supporta l'uso di + \textit{epoll}. + \end{errlist} + } +\end{funcproto} + +La funzione prende sempre come primo argomento un file descriptor di +\textit{epoll}, \param{epfd}, che indica quale istanza di \textit{epoll} usare +e deve pertanto essere stato ottenuto in precedenza con una chiamata a +\func{epoll\_create} o \func{epoll\_create1}. L'argomento \param{fd} indica +invece il file descriptor che si vuole tenere sotto controllo, quest'ultimo +può essere un qualunque file descriptor utilizzabile con \func{poll}, ed anche +un altro file descriptor di \textit{epoll}, ma non lo stesso \param{epfd}. + +Il comportamento della funzione viene controllato dal valore dall'argomento +\param{op} che consente di specificare quale operazione deve essere eseguita. +Le costanti che definiscono i valori utilizzabili per \param{op} +sono riportate in tab.~\ref{tab:epoll_ctl_operation}, assieme al significato +delle operazioni cui fanno riferimento. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{8cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \constd{EPOLL\_CTL\_ADD}& Aggiunge un nuovo file descriptor da osservare + \param{fd} alla lista dei file descriptor + controllati tramite \param{epfd}, in + \param{event} devono essere specificate le + modalità di osservazione.\\ + \constd{EPOLL\_CTL\_MOD}& Modifica le modalità di osservazione del file + descriptor \param{fd} secondo il contenuto di + \param{event}.\\ + \constd{EPOLL\_CTL\_DEL}& Rimuove il file descriptor \param{fd} dalla lista + dei file controllati tramite \param{epfd}.\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{op} che consentono di scegliere quale + operazione di controllo effettuare con la funzione \func{epoll\_ctl}.} + \label{tab:epoll_ctl_operation} +\end{table} + +% era stata aggiunta EPOLL_CTL_DISABLE in previsione del kernel 3.7, vedi +% http://lwn.net/Articles/520012/ e http://lwn.net/Articles/520198/ +% ma non è mai stata inserita. + +Le modalità di utilizzo di \textit{epoll} prevedono che si definisca qual'è +l'insieme dei file descriptor da tenere sotto controllo utilizzando una serie +di chiamate a \const{EPOLL\_CTL\_ADD}.\footnote{un difetto dell'interfaccia è + che queste chiamate devono essere ripetute per ciascun file descriptor, + incorrendo in una perdita di prestazioni qualora il numero di file + descriptor sia molto grande; per questo è stato proposto di introdurre come + estensione una funzione \code{epoll\_ctlv} che consenta di effettuare con + una sola chiamata le impostazioni per un blocco di file descriptor.} L'uso +di \const{EPOLL\_CTL\_MOD} consente in seguito di modificare le modalità di +osservazione di un file descriptor che sia già stato aggiunto alla lista di +osservazione. Qualora non si abbia più interesse nell'osservazione di un file +descriptor lo si può rimuovere dalla lista associata a \param{epfd} con +\const{EPOLL\_CTL\_DEL}. + +Anche se è possibile tenere sotto controllo lo stesso file descriptor in due +istanze distinte di \textit{epoll} in genere questo è sconsigliato in quanto +entrambe riceveranno le notifiche, e gestire correttamente le notifiche +multiple richiede molta attenzione. Se invece si cerca di inserire due volte +lo stesso file descriptor nella stessa istanza di \textit{epoll} la funzione +fallirà con un errore di \errval{EEXIST}. Tuttavia è possibile inserire nella +stessa istanza file descriptor duplicati (si ricordi quanto visto in +sez.~\ref{sec:file_dup}), una tecnica che può essere usata per registrarli con +un valore diverso per \param{events} e classificare così diversi tipi di +eventi. + +Si tenga presente che quando si chiude un file descriptor questo, se era stato +posto sotto osservazione da una istanza di \textit{epoll}, viene rimosso +automaticamente solo nel caso esso sia l'unico riferimento al file aperto +sottostante (più precisamente alla struttura \kstruct{file}, si ricordi +fig.~\ref{fig:file_dup}) e non è necessario usare +\const{EPOLL\_CTL\_DEL}. Questo non avviene qualora esso sia stato duplicato +(perché la suddetta struttura non viene disallocata) e si potranno ricevere +eventi ad esso relativi anche dopo che lo si è chiuso; per evitare +l'inconveniente è necessario rimuoverlo esplicitamente con +\const{EPOLL\_CTL\_DEL}. + +L'ultimo argomento, \param{event}, deve essere un puntatore ad una struttura +di tipo \struct{epoll\_event}, ed ha significato solo con le operazioni +\const{EPOLL\_CTL\_MOD} e \const{EPOLL\_CTL\_ADD}, per le quali serve ad +indicare quale tipo di evento relativo ad \param{fd} si vuole che sia tenuto +sotto controllo. L'argomento viene ignorato con l'operazione +\const{EPOLL\_CTL\_DEL}.\footnote{fino al kernel 2.6.9 era comunque richiesto + che questo fosse un puntatore valido, anche se poi veniva ignorato; a + partire dal 2.6.9 si può specificare anche un valore \val{NULL} ma se si + vuole mantenere la compatibilità con le versioni precedenti occorre usare un + puntatore valido.} + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{0.90\textwidth} + \includestruct{listati/epoll_event.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{epoll\_event}, che consente di specificare + gli eventi associati ad un file descriptor controllato con + \textit{epoll}.} + \label{fig:epoll_event} +\end{figure} + +La struttura \struct{epoll\_event} è l'analoga di \struct{pollfd} e come +quest'ultima serve sia in ingresso (quando usata con \func{epoll\_ctl}) ad +impostare quali eventi osservare, che in uscita (nei risultati ottenuti con +\func{epoll\_wait}) per ricevere le notifiche degli eventi avvenuti. La sua +definizione è riportata in fig.~\ref{fig:epoll_event}. + +Il primo campo, \var{events}, è una maschera binaria in cui ciascun bit +corrisponde o ad un tipo di evento, o una modalità di notifica; detto campo +deve essere specificato come OR aritmetico delle costanti riportate in +tab.~\ref{tab:epoll_events}. Nella prima parte della tabella si sono indicate +le costanti che permettono di indicare il tipo di evento, che sono le +equivalenti delle analoghe di tab.~\ref{tab:file_pollfd_flags} per +\func{poll}. Queste sono anche quelle riportate nella struttura +\struct{epoll\_event} restituita da \func{epoll\_wait} per indicare il tipo di +evento presentatosi, insieme a quelle della seconda parte della tabella, che +vengono comunque riportate anche se non le si sono impostate con +\func{epoll\_ctl}. La terza parte della tabella contiene le costanti che +modificano le modalità di notifica. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{10cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \constd{EPOLLIN} & Il file è pronto per le operazioni di lettura + (analogo di \const{POLLIN}).\\ + \constd{EPOLLOUT} & Il file è pronto per le operazioni di scrittura + (analogo di \const{POLLOUT}).\\ + \constd{EPOLLRDHUP} & L'altro capo di un socket di tipo + \const{SOCK\_STREAM} (vedi sez.~\ref{sec:sock_type}) + ha chiuso la connessione o il capo in scrittura + della stessa (vedi + sez.~\ref{sec:TCP_shutdown}).\footnotemark\\ + \constd{EPOLLPRI} & Ci sono dati urgenti disponibili in lettura (analogo + di \const{POLLPRI}); questa condizione viene comunque + riportata in uscita, e non è necessaria impostarla + in ingresso.\\ + \hline + \constd{EPOLLERR} & Si è verificata una condizione di errore + (analogo di \const{POLLERR}); questa condizione + viene comunque riportata in uscita, e non è + necessaria impostarla in ingresso.\\ + \constd{EPOLLHUP} & Si è verificata una condizione di hung-up; questa + condizione viene comunque riportata in uscita, e non + è necessaria impostarla in ingresso.\\ + \hline + \constd{EPOLLET} & Imposta la notifica in modalità \textit{edge + triggered} per il file descriptor associato.\\ + \constd{EPOLLONESHOT}& Imposta la modalità \textit{one-shot} per il file + descriptor associato (questa modalità è disponibile + solo a partire dal kernel 2.6.2).\\ + \constd{EPOLLWAKEUP} & Attiva la prevenzione della sospensione del sistema + se il file descriptor che si è marcato con esso + diventa pronto (aggiunto a partire dal kernel 3.5), + può essere impostato solo dall'amministratore (o da + un processo con la capacità + \const{CAP\_BLOCK\_SUSPEND}).\\ + \hline + \end{tabular} + \caption{Costanti che identificano i bit del campo \param{events} di + \struct{epoll\_event}.} + \label{tab:epoll_events} +\end{table} + +\footnotetext{questa modalità è disponibile solo a partire dal kernel 2.6.17, + ed è utile per riconoscere la chiusura di una connessione dall'altro capo di + un socket quando si lavora in modalità \textit{edge triggered}.} + +% TODO aggiunto con il kernel 4.5 EPOLLEXCLUSIVE, vedi +% http://lwn.net/Articles/633422/#excl + +Il secondo campo, \var{data}, è una \dirct{union} che serve a identificare il +file descriptor a cui si intende fare riferimento, ed in astratto può +contenere un valore qualsiasi (specificabile in diverse forme) che ne permetta +una indicazione univoca. Il modo più comune di usarlo però è quello in cui si +specifica il terzo argomento di \func{epoll\_ctl} nella forma +\var{event.data.fd}, assegnando come valore di questo campo lo stesso valore +dell'argomento \param{fd}, cosa che permette una immediata identificazione del +file descriptor. + +% TODO verificare se prima o poi epoll_ctlv verrà introdotta + +Le impostazioni di default prevedono che la notifica degli eventi richiesti +sia effettuata in modalità \textit{level triggered}, a meno che sul file +descriptor non si sia impostata la modalità \textit{edge triggered}, +registrandolo con \const{EPOLLET} attivo nel campo \var{events}. + +Infine una particolare modalità di notifica è quella impostata con +\const{EPOLLONESHOT}: a causa dell'implementazione di \textit{epoll} infatti +quando si è in modalità \textit{edge triggered} l'arrivo in rapida successione +di dati in blocchi separati (questo è tipico con i socket di rete, in quanto i +dati arrivano a pacchetti) può causare una generazione di eventi (ad esempio +segnalazioni di dati in lettura disponibili) anche se la condizione è già +stata rilevata (si avrebbe cioè una rottura della logica \textit{edge + triggered}). + +Anche se la situazione è facile da gestire, la si può evitare utilizzando +\const{EPOLLONESHOT} per impostare la modalità \textit{one-shot}, in cui la +notifica di un evento viene effettuata una sola volta, dopo di che il file +descriptor osservato, pur restando nella lista di osservazione, viene +automaticamente disattivato (la cosa avviene contestualmente al ritorno di +\func{epoll\_wait} a causa dell'evento in questione) e per essere riutilizzato +dovrà essere riabilitato esplicitamente con una successiva chiamata con +\const{EPOLL\_CTL\_MOD}. + +Una volta impostato l'insieme di file descriptor che si vogliono osservare con +i relativi eventi, la funzione di sistema che consente di attendere +l'occorrenza di uno di tali eventi è \funcd{epoll\_wait}, il cui prototipo è: + +\begin{funcproto}{ +\fhead{sys/epoll.h} +\fdecl{int epoll\_wait(int epfd, struct epoll\_event * events, int maxevents, + int timeout)} + +\fdesc{Attende che uno dei file descriptor osservati sia pronto.} +} + +{La funzione ritorna il numero di file descriptor pronti in caso di successo e + $-1$ per un errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] il file descriptor \param{epfd} non è valido. + \item[\errcode{EFAULT}] il puntatore \param{events} non è valido. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima + della scadenza di \param{timeout}. + \item[\errcode{EINVAL}] il file descriptor \param{epfd} non è stato ottenuto + con \func{epoll\_create}, o \param{maxevents} non è maggiore di zero. + \end{errlist} +} +\end{funcproto} + +La funzione si blocca in attesa di un evento per i file descriptor registrati +nella lista di osservazione di \param{epfd} fino ad un tempo massimo +specificato in millisecondi tramite l'argomento \param{timeout}. Gli eventi +registrati vengono riportati in un vettore di strutture \struct{epoll\_event} +(che deve essere stato allocato in precedenza) all'indirizzo indicato +dall'argomento \param{events}, fino ad un numero massimo di eventi impostato +con l'argomento \param{maxevents}. + +La funzione ritorna il numero di eventi rilevati, o un valore nullo qualora +sia scaduto il tempo massimo impostato con \param{timeout}. Per quest'ultimo, +oltre ad un numero di millisecondi, si può utilizzare il valore nullo, che +indica di non attendere e ritornare immediatamente (anche in questo caso il +valore di ritorno sarà nullo) o il valore $-1$, che indica un'attesa +indefinita. L'argomento \param{maxevents} dovrà invece essere sempre un intero +positivo. + +Come accennato la funzione restituisce i suoi risultati nel vettore di +strutture \struct{epoll\_event} puntato da \param{events}; in tal caso nel +campo \param{events} di ciascuna di esse saranno attivi i flag relativi agli +eventi accaduti, mentre nel campo \var{data} sarà restituito il valore che era +stato impostato per il file descriptor per cui si è verificato l'evento quando +questo era stato registrato con le operazioni \const{EPOLL\_CTL\_MOD} o +\const{EPOLL\_CTL\_ADD}, in questo modo il campo \var{data} consente di +identificare il file descriptor, ed è per questo che, come accennato, è +consuetudine usare per \var{data} il valore del file descriptor stesso. + +Si ricordi che le occasioni per cui \func{epoll\_wait} ritorna dipendono da +come si è impostata la modalità di osservazione (se \textit{level triggered} o +\textit{edge triggered}) del singolo file descriptor. L'interfaccia assicura +che se arrivano più eventi fra due chiamate successive ad \func{epoll\_wait} +questi vengano combinati. Inoltre qualora su un file descriptor fossero +presenti eventi non ancora notificati, e si effettuasse una modifica +dell'osservazione con \const{EPOLL\_CTL\_MOD}, questi verrebbero riletti alla +luce delle modifiche. + +Si tenga presente infine che con l'uso della modalità \textit{edge triggered} +il ritorno di \func{epoll\_wait} avviene solo quando il file descriptor ha +cambiato stato diventando pronto. Esso non sarà riportato nuovamente fino ad +un altro cambiamento di stato, per cui occorre assicurarsi di aver +completamente esaurito le operazioni su di esso. Questa condizione viene +generalmente rilevata dall'occorrere di un errore di \errcode{EAGAIN} al +ritorno di una \func{read} o una \func{write}, (è opportuno ricordare ancora +una volta che l'uso dell'\textit{I/O multiplexing} richiede di operare sui +file in modalità non bloccante) ma questa non è la sola modalità possibile, ad +esempio la condizione può essere riconosciuta anche per il fatto che sono +stati restituiti meno dati di quelli richiesti. + +Si tenga presente che in modalità \textit{edge triggered}, dovendo esaurire le +attività di I/O dei file descriptor risultati pronti per poter essere +rinotificati, la gestione elementare per cui li si trattano uno per uno in +sequenza può portare ad un effetto denominato \textit{starvation} +(``\textsl{carestia}''). Si rischia cioè di concentrare le operazioni sul +primo file descriptor che dispone di molti dati, prolungandole per tempi molto +lunghi con un ritardo che può risultare eccessivo nei confronti di quelle da +eseguire sugli altri che verrebbero dopo. Per evitare questo tipo di +problematiche viene consigliato di usare \func{epoll\_wait} per registrare un +elenco dei file descriptor da gestire, e di trattarli a turno in maniera più +equa. + +Come già per \func{select} e \func{poll} anche per l'interfaccia di +\textit{epoll} si pone il problema di gestire l'attesa di segnali e di dati +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 +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} +\fdecl{int epoll\_pwait(int epfd, struct epoll\_event * events, int maxevents, + int timeout, \\ +\phantom{int epoll\_pwait(}const sigset\_t *sigmask)} + +\fdesc{Attende che uno dei file descriptor osservati sia pronto, mascherando + i segnali.} } + +{La funzione ritorna il numero di file descriptor pronti in caso di successo e + $-1$ per un errore, nel qual caso \var{errno} assumerà uno dei valori già + visti con \func{epoll\_wait}. + +} +\end{funcproto} + +La funzione è del tutto analoga \func{epoll\_wait}, soltanto che alla sua +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} +anche le funzioni dell'interfaccia di \textit{epoll} vengono utilizzate +prevalentemente con i server di rete, quando si devono tenere sotto +osservazione un gran numero di socket; per questo motivo rimandiamo anche in +questo caso la trattazione di un esempio concreto a quando avremo esaminato in +dettaglio le caratteristiche dei socket; in particolare si potrà trovare un +programma che utilizza questa interfaccia in sez.~\ref{sec:TCP_serv_epoll}. + +\itindend{epoll} + + +\subsection{La notifica di eventi tramite file descriptor} +\label{sec:sig_signalfd_eventfd} + +Abbiamo visto in sez.~\ref{sec:file_select} come il meccanismo classico delle +notifiche di eventi tramite i segnali, presente da sempre nei sistemi +unix-like, porti a notevoli problemi nell'interazione con le funzioni per +l'\textit{I/O multiplexing}, tanto che per evitare possibili \textit{race + condition} sono state introdotte estensioni dello standard POSIX e funzioni +apposite come \func{pselect}, \func{ppoll} e \func{epoll\_pwait}. + +Benché i segnali siano il meccanismo più usato per effettuare notifiche ai +processi, la loro interfaccia di programmazione, che comporta l'esecuzione di +una funzione di gestione in maniera asincrona e totalmente scorrelata +dall'ordinario flusso di esecuzione del processo, si è però dimostrata quasi +subito assai problematica. Oltre ai limiti relativi ai limiti al cosa si può +fare all'interno della funzione del gestore di segnali (quelli illustrati in +sez.~\ref{sec:sig_signal_handler}), c'è il problema più generale consistente +nel fatto che questa modalità di funzionamento cozza con altre interfacce di +programmazione previste dal sistema in cui si opera in maniera +\textsl{sincrona}, come quelle dell'\textit{I/O multiplexing} appena +illustrate. + +In questo tipo di interfacce infatti ci si aspetta che il processo gestisca +gli eventi a cui deve reagire in maniera sincrona generando le opportune +risposte, mentre con l'arrivo di un segnale si possono avere interruzioni +asincrone in qualunque momento. Questo comporta la necessità di dover +gestire, quando si deve tener conto di entrambi i tipi di eventi, le +interruzioni delle funzioni di attesa sincrone, ed evitare possibili +\textit{race conditions}. In sostanza se non ci fossero i segnali non ci +sarebbe da preoccuparsi, fintanto che si effettuano operazioni all'interno di +un processo, della non atomicità delle \textit{system call} lente che vengono +interrotte e devono essere riavviate. + +Abbiamo visto però in sez.~\ref{sec:sig_real_time} che insieme ai segnali +\textit{real-time} sono state introdotte anche delle interfacce di gestione +sincrona dei segnali, con la funzione \func{sigwait} e le sue affini. Queste +funzioni consentono di gestire i segnali bloccando un processo fino alla +avvenuta ricezione e disabilitando l'esecuzione asincrona rispetto al resto +del programma del gestore del segnale. Questo consente di risolvere i problemi +di atomicità nella gestione degli eventi associati ai segnali, avendo tutto il +controllo nel flusso principale del programma, ottenendo così una gestione +simile a quella dell'\textit{I/O multiplexing}, ma non risolve i problemi +delle interazioni con quest'ultimo, perché o si aspetta la ricezione di un +segnale o si aspetta che un file descriptor sia accessibile e nessuna delle +rispettive funzioni consente di fare contemporaneamente entrambe le cose. + +Per risolvere questo problema nello sviluppo del kernel si è pensato di +introdurre un meccanismo alternativo per la notifica dei segnali (esteso anche +ad altri eventi generici) che, ispirandosi di nuovo alla filosofia di Unix per +cui tutto è un file, consentisse di eseguire la notifica con l'uso di +opportuni file descriptor. Ovviamente si tratta di una funzionalità specifica +di Linux, non presente in altri sistemi unix-like, e non prevista da nessuno +standard, per cui va evitata se si ha a cuore la portabilità. + +In sostanza, come per \func{sigwait}, si può disabilitare l'esecuzione di un +gestore in occasione dell'arrivo di un segnale, e rilevarne l'avvenuta +ricezione leggendone la notifica tramite l'uso di uno speciale file +descriptor. Trattandosi di un file descriptor questo potrà essere tenuto sotto +osservazione con le ordinarie funzioni dell'\textit{I/O multiplexing} (vale a +dire con le solite \func{select}, \func{poll} e \func{epoll\_wait}) allo +stesso modo di quelli associati a file o socket, per cui alla fine si potrà +attendere in contemporanea sia l'arrivo del segnale che la disponibilità di +accesso ai dati relativi a questi ultimi. + +La funzione di sistema che permette di abilitare la ricezione dei segnali +tramite file descriptor è \funcd{signalfd},\footnote{in realtà quella + riportata è l'interfaccia alla funzione fornita dalla \acr{glibc}, esistono + infatti due versioni diverse della \textit{system call}; una prima versione, + \func{signalfd}, introdotta nel kernel 2.6.22 e disponibile con la + \acr{glibc} 2.8 che non supporta l'argomento \texttt{flags}, ed una seconda + versione, \funcm{signalfd4}, introdotta con il kernel 2.6.27 e che è quella + che viene sempre usata a partire dalla \acr{glibc} 2.9, che prende un + argomento aggiuntivo \code{size\_t sizemask} che indica la dimensione della + maschera dei segnali, il cui valore viene impostato automaticamente dalla + \acr{glibc}.} il cui prototipo è: + +\begin{funcproto}{ +\fhead{sys/signalfd.h} +\fdecl{int signalfd(int fd, const sigset\_t *mask, int flags)} + +\fdesc{Crea o modifica un file descriptor per la ricezione dei segnali.} +} + +{La funzione ritorna un numero di file descriptor in caso di successo e $-1$ + per un errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] il valore \param{fd} non indica un file descriptor. + \item[\errcode{EINVAL}] il file descriptor \param{fd} non è stato ottenuto + con \func{signalfd} o il valore di \param{flags} non è valido. + \item[\errcode{ENODEV}] il kernel non può montare internamente il + dispositivo per la gestione anonima degli \textit{inode} + associati al file descriptor. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per creare un nuovo file + descriptor di \func{signalfd}. + \end{errlist} + ed inoltre \errval{EMFILE} e \errval{ENFILE} nel loro significato generico. + +} +\end{funcproto} + +La funzione consente di creare o modificare le caratteristiche di un file +descriptor speciale su cui ricevere le notifiche della ricezione di +segnali. Per creare ex-novo uno di questi file descriptor è necessario passare +$-1$ come valore per l'argomento \param{fd}, ogni altro valore positivo verrà +invece interpretato come il numero del file descriptor (che deve esser stato +precedentemente creato sempre con \func{signalfd}) di cui si vogliono +modificare le caratteristiche. Nel primo caso la funzione ritornerà il valore +del nuovo file descriptor e nel secondo caso il valore indicato +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 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 +impostare con una creazione ordinaria con \func{open}, evitando una +impostazione successiva con \func{fcntl} (si ricordi che questo è un argomento +aggiuntivo, introdotto con la versione fornita a partire dal kernel 2.6.27, +per kernel precedenti il valore deve essere nullo). L'argomento deve essere +specificato come maschera binaria dei valori riportati in +tab.~\ref{tab:signalfd_flags}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{8cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \constd{SFD\_NONBLOCK}&imposta sul file descriptor il flag di + \const{O\_NONBLOCK} per renderlo non bloccante.\\ + \constd{SFD\_CLOEXEC}& imposta il flag di \const{O\_CLOEXEC} per la + chiusura automatica del file descriptor nella + esecuzione di \func{exec}.\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{flags} per la funzione \func{signalfd} + che consentono di impostare i flag del file descriptor.} + \label{tab:signalfd_flags} +\end{table} + +Si tenga presente che la chiamata a \func{signalfd} non disabilita la gestione +ordinaria dei segnali indicati da \param{mask}; questa, se si vuole effettuare +la ricezione tramite il file descriptor, dovrà essere disabilitata +esplicitamente bloccando gli stessi segnali con \func{sigprocmask}, altrimenti +verranno comunque eseguite le azioni di default (o un eventuale gestore +installato in precedenza). Il blocco non ha invece nessun effetto sul file +descriptor restituito da \func{signalfd}, dal quale sarà possibile pertanto +ricevere qualunque segnale, anche se questo risultasse bloccato. + +Si tenga presente inoltre che la lettura di una struttura +\struct{signalfd\_siginfo} relativa ad un segnale pendente è equivalente alla +esecuzione di un gestore, vale a dire che una volta letta il segnale non sarà +più pendente e non potrà essere ricevuto, qualora si ripristino le normali +condizioni di gestione, né da un gestore, né dalla funzione \func{sigwaitinfo}. + +Come anticipato, essendo questo lo scopo principale della nuova interfaccia, +il file descriptor può essere tenuto sotto osservazione tramite le funzioni +dell'\textit{I/O multiplexing} (vale a dire con le solite \func{select}, +\func{poll} e \func{epoll\_wait}), e risulterà accessibile in lettura quando +uno o più dei segnali indicati tramite \param{mask} sarà pendente. + +La funzione può essere chiamata più volte dallo stesso processo, consentendo +così di tenere sotto osservazione segnali diversi tramite file descriptor +diversi. Inoltre è anche possibile tenere sotto osservazione lo stesso segnale +con più file descriptor, anche se la pratica è sconsigliata; in tal caso la +ricezione del segnale potrà essere effettuata con una lettura da uno qualunque +dei file descriptor a cui è associato, ma questa potrà essere eseguita +soltanto una volta. Questo significa che tutti i file descriptor su cui è +presente lo stesso segnale risulteranno pronti in lettura per le funzioni di +\textit{I/O multiplexing}, ma una volta eseguita la lettura su uno di essi il +segnale sarà considerato ricevuto ed i relativi dati non saranno più +disponibili sugli altri file descriptor, che (a meno di una ulteriore +occorrenza del segnale nel frattempo) di non saranno più pronti. + +Quando il file descriptor per la ricezione dei segnali non serve più potrà +essere chiuso con \func{close} liberando tutte le risorse da esso allocate. In +tal caso qualora vi fossero segnali pendenti questi resteranno tali, e +potranno essere ricevuti normalmente una volta che si rimuova il blocco +imposto con \func{sigprocmask}. + +Oltre a poter essere usato con le funzioni dell'\textit{I/O multiplexing}, il +file descriptor restituito da \func{signalfd} cerca di seguire la semantica di +un sistema unix-like anche con altre \textit{system call}; in particolare esso +resta aperto (come ogni altro file descriptor) attraverso una chiamata ad +\func{exec}, a meno che non lo si sia creato con il flag di +\const{SFD\_CLOEXEC} o si sia successivamente impostato il +\textit{close-on-exec} con \func{fcntl}. Questo comportamento corrisponde +anche alla ordinaria semantica relativa ai segnali bloccati, che restano +pendenti attraverso una \func{exec}. + +Analogamente il file descriptor resta sempre disponibile attraverso una +\func{fork} per il processo figlio, che ne riceve una copia; in tal caso però +il figlio potrà leggere dallo stesso soltanto i dati relativi ai segnali +ricevuti da lui stesso. Nel caso di \textit{thread} viene nuovamente seguita +la semantica ordinaria dei segnali, che prevede che un singolo \textit{thread} +possa ricevere dal file descriptor solo le notifiche di segnali inviati +direttamente a lui o al processo in generale, e non quelli relativi ad altri +\textit{thread} appartenenti allo stesso processo. + +L'interfaccia fornita da \func{signalfd} prevede che la ricezione dei segnali +sia eseguita leggendo i dati relativi ai segnali pendenti dal file descriptor +restituito dalla funzione con una normalissima \func{read}. Qualora non vi +siano segnali pendenti la \func{read} si bloccherà a meno di non aver +impostato la modalità di I/O non bloccante sul file descriptor, o direttamente +in fase di creazione con il flag \const{SFD\_NONBLOCK}, o in un momento +successivo con \func{fcntl}. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{0.95\textwidth} + \includestruct{listati/signalfd_siginfo.h} + \end{minipage} + \normalsize + \caption{La struttura \structd{signalfd\_siginfo}, restituita in lettura da + un file descriptor creato con \func{signalfd}.} + \label{fig:signalfd_siginfo} +\end{figure} + +I dati letti dal file descriptor vengono scritti sul buffer indicato come +secondo argomento di \func{read} nella forma di una sequenza di una o più +strutture \struct{signalfd\_siginfo} (la cui definizione si è riportata in +fig.~\ref{fig:signalfd_siginfo}) a seconda sia della dimensione del buffer che +del numero di segnali pendenti. Per questo motivo il buffer deve essere almeno +di dimensione pari a quella di \struct{signalfd\_siginfo}, qualora sia di +dimensione maggiore potranno essere letti in unica soluzione i dati relativi +ad eventuali più segnali pendenti, fino al numero massimo di strutture +\struct{signalfd\_siginfo} che possono rientrare nel buffer. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/FifoReporter-init.c} + \end{minipage} + \normalsize + \caption{Sezione di inizializzazione del codice del programma + \file{FifoReporter.c}.} + \label{fig:fiforeporter_code_init} +\end{figure} + +Il contenuto di \struct{signalfd\_siginfo} ricalca da vicino quella +dell'analoga struttura \struct{siginfo\_t} (illustrata in +fig.~\ref{fig:sig_siginfo_t}) usata dall'interfaccia ordinaria dei segnali, e +restituisce dati simili. Come per \struct{siginfo\_t} i campi che vengono +avvalorati dipendono dal tipo di segnale e ricalcano i valori che abbiamo già +illustrato in sez.~\ref{sec:sig_sigaction}.\footnote{si tenga presente però + che per un bug i kernel fino al 2.6.25 non avvalorano correttamente i campi + \var{ssi\_ptr} e \var{ssi\_int} per segnali inviati con \func{sigqueue}.} + +Come esempio di questa nuova interfaccia ed anche come esempio di applicazione +della interfaccia di \textit{epoll}, si è scritto un programma elementare che +stampi sullo \textit{standard output} sia quanto viene scritto da terzi su una +\textit{named fifo}, che l'avvenuta ricezione di alcuni segnali. Il codice +completo si trova al solito nei sorgenti allegati alla guida (nel file +\texttt{FifoReporter.c}). + +In fig.~\ref{fig:fiforeporter_code_init} si è riportata la parte iniziale del +programma in cui vengono effettuate le varie inizializzazioni necessarie per +l'uso di \textit{epoll} e \func{signalfd}, a partire (\texttt{\small 12-16}) +dalla definizione delle varie variabili e strutture necessarie. Al solito si è +tralasciata la parte dedicata alla decodifica delle opzioni che consentono ad +esempio di cambiare il nome del file associato alla \textit{fifo}. + +Il primo passo (\texttt{\small 19-20}) è la creazione di un file descriptor +\texttt{epfd} di \textit{epoll} con \func{epoll\_create} che è 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 +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 volta +fatto questo sarà necessario aggiungere il relativo file descriptor +(\var{fifofd}) a quelli osservati da \textit{epoll} in maniera del tutto +analoga a quanto fatto con quello relativo alla notifica dei segnali. + +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/FifoReporter-main.c} + \end{minipage} + \normalsize + \caption{Ciclo principale del codice del programma \file{FifoReporter.c}.} + \label{fig:fiforeporter_code_body} +\end{figure} + +Una volta completata l'inizializzazione verrà eseguito indefinitamente il +ciclo principale del programma (\texttt{\small 2-45}) che si è riportato in +fig.~\ref{fig:fiforeporter_code_body}, fintanto che questo non riceva un +segnale di \signal{SIGINT} (ad esempio con la pressione di \texttt{C-c}). Il +ciclo prevede che si attenda (\texttt{\small 2-3}) la presenza di un file +descriptor pronto in lettura con \func{epoll\_wait} (si ricordi che entrambi i +file descriptor \var{fifofd} e \var{sigfd} sono stati posti in osservazioni +per eventi di tipo \const{EPOLLIN}) che si bloccherà fintanto che non siano +stati scritti dati sulla \textit{fifo} o che non sia arrivato un +segnale.\footnote{per semplificare il codice non si è trattato il caso in cui + \func{epoll\_wait} viene interrotta da un segnale, assumendo che tutti + quelli che possano interessare siano stati predisposti per la notifica + tramite file descriptor, per gli altri si otterrà semplicemente l'uscita dal + programma.} + +Anche se in questo caso i file descriptor pronti possono essere al più due, si +è comunque adottato un approccio generico in cui questi verranno letti +all'interno di un opportuno ciclo (\texttt{\small 5-44}) sul numero +restituito da \func{epoll\_wait}, esaminando i risultati presenti nel vettore +\var{events} all'interno di una catena di condizionali alternativi sul valore +del file descriptor riconosciuto come pronto, controllando cioè a quale dei +due file descriptor possibili corrisponde il campo relativo, +\var{events[i].data.fd}. + +Il primo condizionale (\texttt{\small 6-24}) è relativo al caso che si sia +ricevuto un segnale e che il file descriptor pronto corrisponda +(\texttt{\small 6}) a \var{sigfd}. Dato che in generale si possono ricevere +anche notifiche relativi a più di un singolo segnale, si è scelto di leggere +una struttura \struct{signalfd\_siginfo} alla volta, eseguendo la lettura +all'interno di un ciclo (\texttt{\small 8-24}) che prosegue fintanto che vi +siano dati da leggere. + +Per questo ad ogni lettura si esamina (\texttt{\small 9-14}) se il valore di +ritorno della funzione \func{read} è negativo, uscendo dal programma +(\texttt{\small 11}) in caso di errore reale, o terminando il ciclo +(\texttt{\small 13}) con un \texttt{break} qualora si ottenga un errore di +\errcode{EAGAIN} per via dell'esaurimento dei dati. Si ricordi infatti come +sia la \textit{fifo} che il file descriptor per i segnali siano stati aperti in +modalità non-bloccante, come previsto per l’\textit{I/O multiplexing}, +pertanto ci si aspetta di ricevere un errore di \errcode{EAGAIN} quando non vi +saranno più dati da leggere. + +In presenza di dati invece il programma proseguirà l'esecuzione stampando +(\texttt{\small 19-20}) il nome del segnale ottenuto all'interno della +struttura \struct{signalfd\_siginfo} letta in \var{siginf} ed il \textit{pid} +del processo da cui lo ha ricevuto;\footnote{per la stampa si è usato il + vettore \var{sig\_names} a ciascun elemento del quale corrisponde il nome + del segnale avente il numero corrispondente, la cui definizione si è omessa + dal codice di fig.~\ref{fig:fiforeporter_code_init} per brevità.} inoltre +(\texttt{\small 21-24}) si controllerà anche se il segnale ricevuto è +\signal{SIGINT}, che si è preso come segnale da utilizzare per la terminazione +del programma, che verrà eseguita dopo aver rimosso il file della \textit{name + fifo}. + +Il secondo condizionale (\texttt{\small 26-39}) è invece relativo al caso in +cui ci siano dati pronti in lettura sulla \textit{fifo} e che il file +descriptor pronto corrisponda (\texttt{\small 26}) a \var{fifofd}. Di nuovo si +effettueranno le letture in un ciclo (\texttt{\small 28-39}) ripetendole fin +tanto che la funzione \func{read} non restituisce un errore di +\errcode{EAGAIN} (\texttt{\small 29-35}). Il procedimento è lo stesso adottato +per il file descriptor associato al segnale, in cui si esce dal programma in +caso di errore reale, in questo caso però alla fine dei dati prima di uscire +si stampa anche (\texttt{\small 32}) un messaggio di chiusura. + +Se invece vi sono dati validi letti dalla \textit{fifo} si inserirà +(\texttt{\small 36}) una terminazione di stringa sul buffer e si stamperà il +tutto (\texttt{\small 37-38}) sullo \textit{standard output}. L'ultimo +condizionale (\texttt{\small 40-44}) è semplicemente una condizione di cattura +per una eventualità che comunque non dovrebbe mai verificarsi, e che porta +alla uscita dal programma con una opportuna segnalazione di errore. + +A questo punto si potrà eseguire il comando lanciandolo su un terminale, ed +osservarne le reazioni agli eventi generati da un altro terminale; lanciando +il programma otterremo qualcosa del tipo: +\begin{Console} +piccardi@hain:~/gapil/sources$ \textbf{./a.out} +FifoReporter starting, pid 4568 +\end{Console} +%$ +e scrivendo qualcosa sull'altro terminale con: +\begin{Console} +root@hain:~# \textbf{echo prova > /tmp/reporter.fifo} +\end{Console} +si otterrà: +\begin{Console} +Message from fifo: +prova +end message +\end{Console} +mentre inviando un segnale: +\begin{Console} +root@hain:~# \textbf{kill 4568} +\end{Console} +si avrà: +\begin{Console} +Signal received: +Got SIGTERM +From pid 3361 +\end{Console} +ed infine premendo \texttt{C-\bslash} sul terminale in cui è in esecuzione si +vedrà: +\begin{Console} +^\\Signal received: +Got SIGQUIT +From pid 0 +\end{Console} +e si potrà far uscire il programma con \texttt{C-c} ottenendo: +\begin{Console} +^CSignal received: +Got SIGINT +From pid 0 +SIGINT means exit +\end{Console} + +Lo stesso paradigma di notifica tramite file descriptor usato per i segnali è +stato adottato anche per i timer. In questo caso, rispetto a quanto visto in +sez.~\ref{sec:sig_timer_adv}, la scadenza di un timer potrà essere letta da un +file descriptor senza dover ricorrere ad altri meccanismi di notifica come un +segnale o un \textit{thread}. Di nuovo questo ha il vantaggio di poter +utilizzare le funzioni dell'\textit{I/O multiplexing} per attendere allo +stesso tempo la disponibilità di dati o la ricezione della scadenza di un +timer. In realtà per questo sarebbe già sufficiente \func{signalfd} per +ricevere i segnali associati ai timer, ma la nuova interfaccia semplifica +notevolmente la gestione e consente di fare tutto con una sola \textit{system + call}. + +Le funzioni di questa nuova interfaccia ricalcano da vicino la struttura delle +analoghe versioni ordinarie introdotte con lo standard POSIX.1-2001, che +abbiamo già illustrato in sez.~\ref{sec:sig_timer_adv}.\footnote{questa + interfaccia è stata introdotta in forma considerata difettosa con il kernel + 2.6.22, per cui è stata immediatamente tolta nel successivo 2.6.23 e + reintrodotta in una forma considerata adeguata nel kernel 2.6.25, il + supporto nella \acr{glibc} è stato introdotto a partire dalla versione + 2.8.6, la versione del kernel 2.6.22, presente solo su questo kernel, non è + supportata e non deve essere usata.} La prima funzione di sistema prevista, +quella che consente di creare un timer, è \funcd{timerfd\_create}, il cui +prototipo è: + +\begin{funcproto}{ +\fhead{sys/timerfd.h} +\fdecl{int timerfd\_create(int clockid, int flags)} + +\fdesc{Crea un timer associato ad un file descriptor di notifica.} +} + +{La funzione ritorna un numero di file descriptor in caso di successo e $-1$ + per un errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EINVAL}] l'argomento \param{clockid} non è + \const{CLOCK\_MONOTONIC} o \const{CLOCK\_REALTIME}, o + l'argomento \param{flag} non è valido, o è diverso da zero per kernel + precedenti il 2.6.27. + \item[\errcode{ENODEV}] il kernel non può montare internamente il + dispositivo per la gestione anonima degli \textit{inode} associati al file + descriptor. + \item[\errcode{ENOMEM}] non c'è memoria sufficiente per creare un nuovo file + descriptor di \func{signalfd}. + \end{errlist} + ed inoltre \errval{EMFILE} e \errval{ENFILE} nel loro significato generico. +} +\end{funcproto} + +La funzione prende come primo argomento un intero che indica il tipo di +orologio a cui il timer deve fare riferimento, i valori sono gli stessi delle +funzioni dello standard POSIX-1.2001 già illustrati in +tab.~\ref{tab:sig_timer_clockid_types}, ma al momento i soli utilizzabili sono +\const{CLOCK\_REALTIME} e \const{CLOCK\_MONOTONIC}. L'argomento \param{flags}, +come l'analogo di \func{signalfd}, consente di impostare i flag per l'I/O non +bloccante ed il \textit{close-on-exec} sul file descriptor +restituito,\footnote{il flag è stato introdotto a partire dal kernel 2.6.27, + per le versioni precedenti deve essere passato un valore nullo.} e deve +essere specificato come una maschera binaria delle costanti riportate in +tab.~\ref{tab:timerfd_flags}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{8cm}|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \constd{TFD\_NONBLOCK}& imposta sul file descriptor il flag di + \const{O\_NONBLOCK} per renderlo non bloccante.\\ + \constd{TFD\_CLOEXEC} & imposta il flag di \const{O\_CLOEXEC} per la + chiusura automatica del file descriptor nella + esecuzione di \func{exec}.\\ + \hline + \end{tabular} + \caption{Valori dell'argomento \param{flags} per la funzione + \func{timerfd\_create} che consentono di impostare i flag del file + descriptor.} + \label{tab:timerfd_flags} +\end{table} + +In caso di successo la funzione restituisce un file descriptor sul quale +verranno notificate le scadenze dei timer. Come per quelli restituiti da +\func{signalfd} anche questo file descriptor segue la semantica dei sistemi +unix-like, in particolare resta aperto attraverso una \func{exec} (a meno che +non si sia impostato il flag di \textit{close-on exec} con +\const{TFD\_CLOEXEC}) e viene duplicato attraverso una \func{fork}; questa +ultima caratteristica comporta però che anche il figlio può utilizzare i dati +di un timer creato nel padre, a differenza di quanto avviene invece con i +timer impostati con le funzioni ordinarie. Si ricordi infatti che, come +illustrato in sez.~\ref{sec:proc_fork}, allarmi, timer e segnali pendenti nel +padre vengono cancellati per il figlio dopo una \func{fork}. + +Una volta creato il timer con \func{timerfd\_create} per poterlo utilizzare +occorre \textsl{armarlo} impostandone un tempo di scadenza ed una eventuale +periodicità di ripetizione, per farlo si usa una funzione di sistema omologa +di \func{timer\_settime} per la nuova interfaccia; questa è +\funcd{timerfd\_settime} ed il suo prototipo è: + +\begin{funcproto}{ +\fhead{sys/timerfd.h} +\fdecl{int timerfd\_settime(int fd, int flags, + const struct itimerspec *new\_value,\\ +\phantom{int timerfd\_settime(}struct itimerspec *old\_value)} + +\fdesc{Arma un timer associato ad un file descriptor di notifica.} +} + +{La funzione ritorna un numero di file descriptor 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 corrisponde ad un file + descriptor. + \item[\errcode{EFAULT}] o \param{new\_value} o \param{old\_value} non sono + puntatori validi. + \item[\errcode{EINVAL}] il file descriptor \param{fd} non è stato ottenuto + con \func{timerfd\_create}, o i valori di \param{flag} o dei campi + \var{tv\_nsec} in \param{new\_value} non sono validi. + \end{errlist} +} +\end{funcproto} + +In questo caso occorre indicare su quale timer si intende operare specificando +come primo argomento il file descriptor ad esso associato, che deve essere +stato ottenuto da una precedente chiamata a \func{timerfd\_create}. I restanti +argomenti sono del tutto analoghi a quelli della omologa funzione +\func{timer\_settime}, e prevedono l'uso di strutture \struct{itimerspec} +(vedi fig.~\ref{fig:struct_itimerspec}) per le indicazioni di temporizzazione. + +I valori ed il significato di questi argomenti sono gli stessi che sono già +stati illustrati in dettaglio in sez.~\ref{sec:sig_timer_adv} e non staremo a +ripetere quanto detto in quell'occasione; per brevità si ricordi che +con \param{new\_value.it\_value} si indica la prima scadenza del timer e +con \param{new\_value.it\_interval} la sua periodicità. L'unica differenza +riguarda l'argomento \param{flags} che serve sempre ad indicare se il tempo di +scadenza del timer è da considerarsi relativo o assoluto rispetto al valore +corrente dell'orologio associato al timer, ma che in questo caso ha come +valori possibili rispettivamente soltanto $0$ e \constd{TFD\_TIMER\_ABSTIME} +(l'analogo di \const{TIMER\_ABSTIME}). + +L'ultima funzione di sistema prevista dalla nuova interfaccia è +\funcd{timerfd\_gettime}, che è l'analoga di \func{timer\_gettime}, il suo +prototipo è: + +\begin{funcproto}{ +\fhead{sys/timerfd.h} +\fdecl{int timerfd\_gettime(int fd, struct itimerspec *curr\_value)} + +\fdesc{Legge l'impostazione di un timer associato ad un file descriptor di + notifica.} +} + +{La funzione ritorna un numero di file descriptor 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 corrisponde ad un file + descriptor. + \item[\errcode{EINVAL}] il file descriptor \param{fd} non è stato ottenuto + con \func{timerfd\_create}. + \item[\errcode{EFAULT}] o \param{curr\_value} non è un puntatore valido. + \end{errlist} +} +\end{funcproto} + +La funzione consente di rileggere le impostazioni del timer associato al file +descriptor \param{fd} nella struttura \struct{itimerspec} puntata +da \param{curr\_value}. Il campo \var{it\_value} riporta il tempo rimanente +alla prossima scadenza del timer, che viene sempre espresso in forma relativa, +anche se lo si è armato specificando \const{TFD\_TIMER\_ABSTIME}. Un valore +nullo (di entrambi i campi di \var{it\_value}) indica invece che il timer non +è stato ancora armato. Il campo \var{it\_interval} riporta la durata +dell'intervallo di ripetizione del timer, ed un valore nullo (di entrambi i +campi) indica che il timer è stato impostato per scadere una sola volta. + +Il timer creato con \func{timerfd\_create} notificherà la sua scadenza +rendendo pronto per la lettura il file descriptor ad esso associato, che +pertanto potrà essere messo sotto controllo con una qualunque delle varie +funzioni dell'I/O multiplexing viste in precedenza. Una volta che il file +descriptor risulta pronto sarà possibile leggere il numero di volte che il +timer è scaduto con una ordinaria \func{read}. + +La funzione legge il valore in un dato di tipo \typed{uint64\_t}, e necessita +pertanto che le si passi un buffer di almeno 8 byte, fallendo con +\errval{EINVAL} in caso contrario, in sostanza la lettura deve essere +effettuata con una istruzione del tipo: +\includecodesnip{listati/readtimerfd.c} + +Il valore viene restituito da \func{read} seguendo l'ordinamento dei bit +(\textit{big-endian} o \textit{little-endian}) nativo della macchina in uso, +ed indica il numero di volte che il timer è scaduto dall'ultima lettura +eseguita con successo, o, se lo si legge per la prima volta, da quando lo si è +impostato con \func{timerfd\_settime}. Se il timer non è scaduto la funzione +si blocca fino alla prima scadenza, a meno di non aver creato il file +descriptor in modalità non bloccante con \const{TFD\_NONBLOCK} o aver +impostato la stessa con \func{fcntl}, nel qual caso fallisce con l'errore di +\errval{EAGAIN}. + + +% TODO trattare qui eventfd introdotto con il 2.6.22 + + +\section{L'accesso \textsl{asincrono} ai file} +\label{sec:file_asyncronous_operation} + +Benché l'\textit{I/O multiplexing} sia stata la prima, e sia tutt'ora una fra +le più diffuse modalità di gestire l'I/O in situazioni complesse in cui si +debba operare su più file contemporaneamente, esistono altre modalità di +gestione delle stesse problematiche. In particolare sono importanti in questo +contesto le modalità di accesso ai file eseguibili in maniera +\textsl{asincrona}, quelle cioè in cui un processo non deve bloccarsi in +attesa della disponibilità dell'accesso al file, ma può proseguire +nell'esecuzione utilizzando invece un meccanismo di notifica asincrono (di +norma un segnale, ma esistono anche altre interfacce, come \textit{inotify}), +per essere avvisato della possibilità di eseguire le operazioni di I/O volute. + + +\subsection{Il \textit{Signal driven I/O}} +\label{sec:signal_driven_io} + +\itindbeg{signal~driven~I/O} + +Abbiamo accennato in sez.~\ref{sec:file_open_close} che è definito un flag +\const{O\_ASYNC}, che consentirebbe di aprire un file in modalità asincrona, +anche se in realtà è opportuno attivare in un secondo tempo questa modalità +impostando questo flag attraverso l'uso di \func{fcntl} con il comando +\const{F\_SETFL} (vedi sez.~\ref{sec:file_fcntl_ioctl}).\footnote{l'uso del + flag di \const{O\_ASYNC} e dei comandi \const{F\_SETOWN} e \const{F\_GETOWN} + per \func{fcntl} è specifico di Linux e BSD.} In realtà parlare di apertura +in modalità asincrona non significa che le operazioni di lettura o scrittura +del file vengono eseguite in modo asincrono (tratteremo questo, che è ciò che +più propriamente viene chiamato \textsl{I/O asincrono}, in +sez.~\ref{sec:file_asyncronous_io}), quanto dell'attivazione un meccanismo di +notifica asincrona delle variazione dello stato del file descriptor aperto in +questo modo. + +Quello che succede è che per tutti i file posti in questa modalità il sistema +genera un apposito segnale, \signal{SIGIO}, tutte le volte che diventa +possibile leggere o scrivere dal file descriptor; si tenga presente però che +essa non è utilizzabile con i file ordinari ma solo con socket, file di +terminale o pseudo terminale, ed anche, a partire dal kernel 2.6, per +\textit{fifo} e \textit{pipe}. Inoltre è possibile, come illustrato in +sez.~\ref{sec:file_fcntl_ioctl}, selezionare con il comando \const{F\_SETOWN} +di \func{fcntl} quale processo o quale gruppo di processi dovrà ricevere il +segnale. In questo modo diventa possibile effettuare le operazioni di I/O in +risposta alla ricezione del segnale, e non ci sarà più la necessità di restare +bloccati in attesa della disponibilità di accesso ai file. + +% TODO: per i thread l'uso di F_SETOWN ha un significato diverso + +Per questo motivo Stevens, ed anche le pagine di manuale di Linux, chiamano +questa modalità ``\textit{Signal driven I/O}''. Si tratta di un'altra +modalità di gestione dell'I/O, alternativa all'uso di +\textit{epoll},\footnote{anche se le prestazioni ottenute con questa tecnica + sono inferiori, il vantaggio è che questa modalità è utilizzabile anche con + kernel che non supportano \textit{epoll}, come quelli della serie 2.4, + ottenendo comunque prestazioni superiori a quelle che si hanno con + \func{poll} e \func{select}.} che consente di evitare l'uso delle funzioni +\func{poll} o \func{select} che, come illustrato in sez.~\ref{sec:file_epoll}, +quando vengono usate con un numero molto grande di file descriptor, non hanno +buone prestazioni. + +Tuttavia con l'implementazione classica dei segnali questa modalità di I/O +presenta notevoli problemi, dato che non è possibile determinare, quando i +file descriptor sono più di uno, qual è quello responsabile dell'emissione del +segnale. Inoltre dato che i segnali normali non si accodano (si ricordi quanto +illustrato in sez.~\ref{sec:sig_notification}), in presenza di più file +descriptor attivi contemporaneamente, più segnali emessi nello stesso momento +verrebbero notificati una volta sola. + +Linux però supporta le estensioni POSIX.1b dei segnali \textit{real-time}, che +vengono accodati e che permettono di 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 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 +\textit{real-time} (vedi sez.~\ref{sec:sig_real_time}) impostando +esplicitamente con il comando \const{F\_SETSIG} di \func{fcntl} un segnale +\textit{real-time} da inviare in caso di I/O asincrono (il segnale predefinito +è \signal{SIGIO}). In questo caso il gestore, tutte le volte che riceverà +\const{SI\_SIGIO} come valore del campo \var{si\_code} di \struct{siginfo\_t}, +troverà nel campo \var{si\_fd} il valore del file descriptor che ha generato +il segnale. Si noti che il valore di\var{si\_code} resta \const{SI\_SIGIO} +qualunque sia il segnale che si è associato all'I/O, in quanto indica che il +segnale è stato generato a causa di attività di I/O. + +Un secondo vantaggio dell'uso dei segnali \textit{real-time} è che essendo +questi ultimi dotati di una coda di consegna ogni segnale sarà associato ad +uno solo file descriptor; inoltre sarà possibile stabilire delle priorità +nella risposta a seconda del segnale usato, dato che i segnali +\textit{real-time} supportano anche questa funzionalità. In questo modo si può +identificare immediatamente un file su cui l'accesso è diventato possibile +evitando completamente l'uso di funzioni come \func{poll} e \func{select}, +almeno fintanto che non si satura la coda. + +Se infatti si eccedono le dimensioni di quest'ultima, il kernel, non potendo +più assicurare il comportamento corretto per un segnale \textit{real-time}, +invierà al suo posto un solo \signal{SIGIO}, su cui si saranno accumulati +tutti i segnali in eccesso, e si dovrà allora determinare con un ciclo quali +sono i file diventati attivi. L'unico modo per essere sicuri che questo non +avvenga è di impostare la lunghezza della coda dei segnali \textit{real-time} +ad una dimensione identica al valore massimo del numero di file descriptor +utilizzabili, vale a dire impostare il contenuto di +\sysctlfile{kernel/rtsig-max} allo stesso valore del contenuto di +\sysctlfile{fs/file-max}. + +% TODO fare esempio che usa O_ASYNC + +\itindend{signal~driven~I/O} + + + +\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, 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 classico non prevedeva +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 se il loro file di configurazione è stato modificato, perché possano +rileggerlo e riconoscere le modifiche; in genere questo vien fatto inviandogli +un segnale di \signal{SIGHUP} che, per una convenzione adottata dalla gran +parte di detti programmi, causa la rilettura della configurazione. + +Questa scelta è stata fatta perché provvedere un simile meccanismo a livello +generico per qualunque file comporterebbe un notevole aumento di complessità +dell'architettura della gestione dei file, il tutto per fornire una +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 \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 +interfaccia grafica quando si deve presentare all'utente lo stato del +filesystem, 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 +\textit{polling}. + +Queste nuove funzionalità sono delle estensioni specifiche, non +standardizzate, che sono disponibili soltanto su Linux (anche se altri kernel +supportano meccanismi simili). Alcune di 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_ioctl}), che divengono disponibili soltanto se si è +definita la macro \macro{\_GNU\_SOURCE} prima di includere \headfile{fcntl.h}. + +\itindbeg{file~lease} + +% TODO: questa funzionalità potrebbe essere estesa vedi: +% https://lwn.net/Articles/796000/ + +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 \signal{SIGIO}, ma questo segnale può essere +modificato usando il comando \const{F\_SETSIG} di \func{fcntl} (anche in +questo caso si può rispecificare lo stesso \signal{SIGIO}). + +Se si è fatto questo (ed in genere è opportuno farlo, come in precedenza, per +utilizzare segnali \textit{real-time}) e se inoltre si è installato il gestore +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 in 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_ioctl} 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 + \constd{F\_RDLCK} & Richiede un \textit{read lease}.\\ + \constd{F\_WRLCK} & Richiede un \textit{write lease}.\\ + \constd{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 +(\textit{pipe} e dispositivi sono quindi esclusi). Inoltre un processo non +privilegiato può ottenere un \textit{lease} soltanto per un file appartenente +ad un \ids{UID} corrispondente a quello del processo. Soltanto un processo con +privilegi di amministratore (cioè con la capacità \const{CAP\_LEASE}, vedi +sez.~\ref{sec:proc_capabilities}) 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 @@ -30,7 +2889,7 @@ operazione di lettura, declassando il \textit{lease} a lettura con Se il \textit{lease holder} non provvede a rilasciare il \textit{lease} entro il numero di secondi specificato dal parametro di sistema mantenuto in -\sysctlfile{fs/lease-break-time} sarà il kernel stesso a rimuoverlo o +\sysctlfiled{fs/lease-break-time} sarà il kernel stesso a rimuoverlo o declassarlo automaticamente (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 @@ -74,26 +2933,26 @@ che è stato modificato tramite il contenuto della struttura \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.\\ + \constd{DN\_ACCESS} & Un file è stato acceduto, con l'esecuzione di una fra + \func{read}, \func{pread}, \func{readv}.\\ + \constd{DN\_MODIFY} & Un file è stato modificato, con l'esecuzione di una + fra \func{write}, \func{pwrite}, \func{writev}, + \func{truncate}, \func{ftruncate}.\\ + \constd{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).\\ + \constd{DN\_DELETE} & È stato cancellato un file dalla directory con + l'esecuzione di una fra \func{unlink}, \func{rename} + (su un'altra directory), \func{rmdir}.\\ + \constd{DN\_RENAME} & È stato rinominato un file all'interno della + directory (con \func{rename}).\\ + \constd{DN\_ATTRIB} & È stato modificato un attributo di un file con + l'esecuzione di una fra \func{chown}, \func{chmod}, + \func{utime}.\\ + \constd{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 @@ -149,9 +3008,8 @@ interfaccia per l'osservazione delle modifiche a file o directory, chiamata questa è una interfaccia specifica di Linux (pertanto non deve essere usata se si devono scrivere programmi portabili), ed è basata sull'uso di una coda di notifica degli eventi associata ad un singolo file descriptor, il che permette -di risolvere il principale problema di \itindex{dnotify} \textit{dnotify}. La -coda viene creata attraverso la funzione di sistema \funcd{inotify\_init}, il -cui prototipo è: +di risolvere il principale problema di \textit{dnotify}. La coda viene creata +attraverso la funzione di sistema \funcd{inotify\_init}, il cui prototipo è: \begin{funcproto}{ \fhead{sys/inotify.h} @@ -180,7 +3038,7 @@ notificare gli eventi che sono stati posti in osservazione. Per evitare abusi delle risorse di sistema è previsto che un utente possa utilizzare un numero limitato di istanze di \textit{inotify}; il valore di default del limite è di 128, ma questo valore può essere cambiato con \func{sysctl} o usando il file -\sysctlfile{fs/inotify/max\_user\_instances}. +\sysctlfiled{fs/inotify/max\_user\_instances}. Dato che questo file descriptor non è associato a nessun file o directory reale, l'inconveniente di non poter smontare un filesystem i cui file sono @@ -192,10 +3050,9 @@ stato smontato. Inoltre trattandosi di un file descriptor a tutti gli effetti, esso potrà essere utilizzato come argomento per le funzioni \func{select} e \func{poll} e con l'interfaccia di \textit{epoll}, ed a partire dal kernel 2.6.25 è stato -introdotto anche il supporto per il \itindex{signal~driven~I/O} -\texttt{signal-driven I/O}. Siccome gli eventi vengono notificati come dati -disponibili in lettura, dette funzioni ritorneranno tutte le volte che si avrà -un evento di notifica. +introdotto anche il supporto per il \texttt{signal-driven I/O}. Siccome gli +eventi vengono notificati come dati disponibili in lettura, dette funzioni +ritorneranno tutte le volte che si avrà un evento di notifica. Così, invece di dover utilizzare i segnali, considerati una pessima scelta dal punto di vista dell'interfaccia utente, si potrà gestire l'osservazione degli @@ -243,7 +3100,7 @@ modalità della stessa. L'operazione può essere ripetuta per tutti i file e le directory che si vogliono tenere sotto osservazione,\footnote{anche in questo caso c'è un limite massimo che di default è pari a 8192, ed anche questo valore può essere cambiato con \func{sysctl} o usando il file - \sysctlfile{fs/inotify/max\_user\_watches}.} e si utilizzerà sempre un solo + \sysctlfiled{fs/inotify/max\_user\_watches}.} e si utilizzerà sempre un solo file descriptor. Il tipo di evento che si vuole osservare deve essere specificato @@ -263,41 +3120,41 @@ flag della prima parte. \textbf{Valore} & & \textbf{Significato} \\ \hline \hline - \const{IN\_ACCESS} &$\bullet$& C'è stato accesso al file in - lettura.\\ - \const{IN\_ATTRIB} &$\bullet$& Ci sono stati cambiamenti sui dati - dell'\itindex{inode} \textit{inode} - (o sugli attributi estesi, vedi - sez.~\ref{sec:file_xattr}).\\ - \const{IN\_CLOSE\_WRITE} &$\bullet$& È stato chiuso un file aperto in - scrittura.\\ - \const{IN\_CLOSE\_NOWRITE}&$\bullet$& È stato chiuso un file aperto in - sola lettura.\\ - \const{IN\_CREATE} &$\bullet$& È stato creato un file o una - directory in una directory sotto - osservazione.\\ - \const{IN\_DELETE} &$\bullet$& È stato cancellato un file o una - directory in una directory sotto - osservazione.\\ - \const{IN\_DELETE\_SELF} & -- & È stato cancellato il file (o la - directory) sotto osservazione.\\ - \const{IN\_MODIFY} &$\bullet$& È stato modificato il file.\\ - \const{IN\_MOVE\_SELF} & & È stato rinominato il file (o la + \constd{IN\_ACCESS} &$\bullet$& C'è stato accesso al file in + lettura.\\ + \constd{IN\_ATTRIB} &$\bullet$& Ci sono stati cambiamenti sui dati + dell'\textit{inode} + (o sugli attributi estesi, vedi + sez.~\ref{sec:file_xattr}).\\ + \constd{IN\_CLOSE\_WRITE} &$\bullet$& È stato chiuso un file aperto in + scrittura.\\ + \constd{IN\_CLOSE\_NOWRITE}&$\bullet$& È stato chiuso un file aperto in + sola lettura.\\ + \constd{IN\_CREATE} &$\bullet$& È stato creato un file o una + directory in una directory sotto + osservazione.\\ + \constd{IN\_DELETE} &$\bullet$& È stato cancellato un file o una + directory in una directory sotto + osservazione.\\ + \constd{IN\_DELETE\_SELF} & -- & È stato cancellato il file (o la directory) sotto osservazione.\\ - \const{IN\_MOVED\_FROM} &$\bullet$& Un file è stato spostato fuori dalla - directory sotto osservazione.\\ - \const{IN\_MOVED\_TO} &$\bullet$& Un file è stato spostato nella - directory sotto osservazione.\\ - \const{IN\_OPEN} &$\bullet$& Un file è stato aperto.\\ + \constd{IN\_MODIFY} &$\bullet$& È stato modificato il file.\\ + \constd{IN\_MOVE\_SELF} & & È stato rinominato il file (o la + directory) sotto osservazione.\\ + \constd{IN\_MOVED\_FROM} &$\bullet$& Un file è stato spostato fuori dalla + directory sotto osservazione.\\ + \constd{IN\_MOVED\_TO} &$\bullet$& Un file è stato spostato nella + directory sotto osservazione.\\ + \constd{IN\_OPEN} &$\bullet$& Un file è stato aperto.\\ \hline - \const{IN\_CLOSE} & & Combinazione di - \const{IN\_CLOSE\_WRITE} e - \const{IN\_CLOSE\_NOWRITE}.\\ - \const{IN\_MOVE} & & Combinazione di - \const{IN\_MOVED\_FROM} e - \const{IN\_MOVED\_TO}.\\ - \const{IN\_ALL\_EVENTS} & & Combinazione di tutti i flag - possibili.\\ + \constd{IN\_CLOSE} & & Combinazione di + \const{IN\_CLOSE\_WRITE} e + \const{IN\_CLOSE\_NOWRITE}.\\ + \constd{IN\_MOVE} & & Combinazione di + \const{IN\_MOVED\_FROM} e + \const{IN\_MOVED\_TO}.\\ + \constd{IN\_ALL\_EVENTS} & & Combinazione di tutti i flag + possibili.\\ \hline \end{tabular} \caption{Le costanti che identificano i bit della maschera binaria @@ -324,17 +3181,17 @@ contrario dei precedenti non vengono mai impostati nei risultati in uscita. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{IN\_DONT\_FOLLOW}& Non dereferenzia \param{pathname} se questo è un - link simbolico.\\ - \const{IN\_MASK\_ADD} & Aggiunge a quelli già impostati i flag indicati - nell'argomento \param{mask}, invece di - sovrascriverli.\\ - \const{IN\_ONESHOT} & Esegue l'osservazione su \param{pathname} per una - sola volta, rimuovendolo poi dalla \textit{watch - list}.\\ - \const{IN\_ONLYDIR} & Se \param{pathname} è una directory riporta - soltanto gli eventi ad essa relativi e non - quelli per i file che contiene.\\ + \constd{IN\_DONT\_FOLLOW}& Non dereferenzia \param{pathname} se questo è un + link simbolico.\\ + \constd{IN\_MASK\_ADD} & Aggiunge a quelli già impostati i flag indicati + nell'argomento \param{mask}, invece di + sovrascriverli.\\ + \constd{IN\_ONESHOT} & Esegue l'osservazione su \param{pathname} per + una sola volta, rimuovendolo poi dalla + \textit{watch list}.\\ + \constd{IN\_ONLYDIR} & Se \param{pathname} è una directory riporta + soltanto gli eventi ad essa relativi e non + quelli per i file che contiene.\\ \hline \end{tabular} \caption{Le costanti che identificano i bit della maschera binaria @@ -456,21 +3313,21 @@ registrazione dell'osservatore). \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{IN\_IGNORED} & L'osservatore è stato rimosso, sia in maniera - esplicita con l'uso di \func{inotify\_rm\_watch}, - che in maniera implicita per la rimozione - dell'oggetto osservato o per lo smontaggio del - filesystem su cui questo si trova.\\ - \const{IN\_ISDIR} & L'evento avvenuto fa riferimento ad una directory - (consente così di distinguere, quando si pone - sotto osservazione una directory, fra gli eventi - relativi ad essa e quelli relativi ai file che - essa contiene).\\ - \const{IN\_Q\_OVERFLOW}& Si sono eccedute le dimensioni della coda degli - eventi (\textit{overflow} della coda); in questo - caso il valore di \var{wd} è $-1$.\footnotemark\\ - \const{IN\_UNMOUNT} & Il filesystem contenente l'oggetto posto sotto - osservazione è stato smontato.\\ + \constd{IN\_IGNORED} & L'osservatore è stato rimosso, sia in maniera + esplicita con l'uso di \func{inotify\_rm\_watch}, + che in maniera implicita per la rimozione + dell'oggetto osservato o per lo smontaggio del + filesystem su cui questo si trova.\\ + \constd{IN\_ISDIR} & L'evento avvenuto fa riferimento ad una directory + (consente così di distinguere, quando si pone + sotto osservazione una directory, fra gli eventi + relativi ad essa e quelli relativi ai file che + essa contiene).\\ + \constd{IN\_Q\_OVERFLOW}& Si sono eccedute le dimensioni della coda degli + eventi (\textit{overflow} della coda); in questo + caso il valore di \var{wd} è $-1$.\footnotemark\\ + \constd{IN\_UNMOUNT} & Il filesystem contenente l'oggetto posto sotto + osservazione è stato smontato.\\ \hline \end{tabular} \caption{Le costanti che identificano i bit aggiuntivi usati nella maschera @@ -480,7 +3337,7 @@ registrazione dell'osservatore). \footnotetext{la coda di notifica ha una dimensione massima che viene controllata dal parametro di sistema - \sysctlfile{fs/inotify/max\_queued\_events}, che indica il numero massimo di + \sysctlfiled{fs/inotify/max\_queued\_events}, che indica il numero massimo di eventi che possono essere mantenuti sulla stessa; quando detto valore viene ecceduto gli ulteriori eventi vengono scartati, ma viene comunque generato un evento di tipo \const{IN\_Q\_OVERFLOW}.} @@ -494,15 +3351,15 @@ così all'applicazione di collegare la corrispondente coppia di eventi Infine due campi \var{name} e \var{len} sono utilizzati soltanto quando l'evento è relativo ad un file presente in una directory posta sotto osservazione, in tal caso essi contengono rispettivamente il nome del file -(come \itindsub{pathname}{relativo} \textit{pathname} relativo alla directory -osservata) e la relativa dimensione in byte. Il campo \var{name} viene sempre -restituito come stringa terminata da NUL, con uno o più zeri di terminazione, -a seconda di eventuali necessità di allineamento del risultato, ed il valore -di \var{len} corrisponde al totale della dimensione di \var{name}, zeri -aggiuntivi compresi. La stringa con il nome del file viene restituita nella -lettura subito dopo la struttura \struct{inotify\_event}; questo significa che -le dimensioni di ciascun evento di \textit{inotify} saranno pari a -\code{sizeof(\struct{inotify\_event}) + len}. +(come \textit{pathname} relativo alla directory osservata) e la relativa +dimensione in byte. Il campo \var{name} viene sempre restituito come stringa +terminata da NUL, con uno o più zeri di terminazione, a seconda di eventuali +necessità di allineamento del risultato, ed il valore di \var{len} corrisponde +al totale della dimensione di \var{name}, zeri aggiuntivi compresi. La stringa +con il nome del file viene restituita nella lettura subito dopo la struttura +\struct{inotify\_event}; questo significa che le dimensioni di ciascun evento +di \textit{inotify} saranno pari a \code{sizeof(\struct{inotify\_event}) + + len}. Vediamo allora un esempio dell'uso dell'interfaccia di \textit{inotify} con un semplice programma che permette di mettere sotto osservazione uno o più file e @@ -636,6 +3493,8 @@ raggruppati in un solo evento. % TODO trattare fanotify, vedi http://lwn.net/Articles/339399/ e % http://lwn.net/Articles/343346/ (incluso nel 2.6.36) +% fanotify_mark() ha FAN_MARK_FILESYSTEM dal 4.20 +% fanotify() ha FAN_OPEN_EXEC dal 4.21/5.0 \subsection{L'interfaccia POSIX per l'I/O asincrono} @@ -652,21 +3511,21 @@ poter effettuare in contemporanea le operazioni di calcolo e quelle di I/O. Benché la modalità di apertura asincrona di un file vista in sez.~\ref{sec:signal_driven_io} possa risultare utile in varie occasioni (in particolar modo con i socket e gli altri file per i quali le funzioni di I/O -sono \index{system~call~lente} \textit{system call} lente), essa è comunque -limitata alla notifica della disponibilità del file descriptor per le -operazioni di I/O, e non ad uno svolgimento asincrono delle medesime. Lo -standard POSIX.1b definisce una interfaccia apposita per l'I/O asincrono vero -e proprio,\footnote{questa è stata ulteriormente perfezionata nelle successive - versioni POSIX.1-2001 e POSIX.1-2008.} che prevede un insieme di funzioni -dedicate per la lettura e la scrittura dei file, completamente separate -rispetto a quelle usate normalmente. +sono \textit{system call} lente), essa è comunque limitata alla notifica della +disponibilità del file descriptor per le operazioni di I/O, e non ad uno +svolgimento asincrono delle medesime. Lo standard POSIX.1b definisce una +interfaccia apposita per l'I/O asincrono vero e proprio,\footnote{questa è + stata ulteriormente perfezionata nelle successive versioni POSIX.1-2001 e + POSIX.1-2008.} che prevede un insieme di funzioni 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 \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 +l'uso di \textit{thread}. Per le versioni del kernel meno recenti esiste una +implementazione di questa interfaccia fornita completamente dalla \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. @@ -675,9 +3534,9 @@ Lo standard POSIX 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 \textit{asyncronous I/O control block}), che viene passata come argomento a tutte le funzioni dell'interfaccia. La sua definizione, come -effettuata in \headfile{aio.h}, è riportata in +effettuata in \headfiled{aio.h}, è riportata in fig.~\ref{fig:file_aiocb}. Nello steso file è definita la macro -\macro{\_POSIX\_ASYNCHRONOUS\_IO}, che dichiara la disponibilità +\macrod{\_POSIX\_ASYNCHRONOUS\_IO}, che dichiara la disponibilità dell'interfaccia per l'I/O asincrono. \begin{figure}[!htb] @@ -707,8 +3566,8 @@ del blocco di dati da trasferire. Il campo \var{aio\_reqprio} permette di impostare la priorità delle operazioni di I/O, in generale perché ciò sia possibile occorre che la piattaforma supporti questa caratteristica, questo viene indicato dal fatto che le macro -\macro{\_POSIX\_PRIORITIZED\_IO}, e \macro{\_POSIX\_PRIORITY\_SCHEDULING} sono -definite. La priorità viene impostata a partire da quella del processo +\macrod{\_POSIX\_PRIORITIZED\_IO}, e \macrod{\_POSIX\_PRIORITY\_SCHEDULING} +sono definite. La priorità viene impostata a partire da quella del processo chiamante (vedi sez.~\ref{sec:proc_priority}), cui viene sottratto il valore di questo campo. Il campo \var{aio\_lio\_opcode} è usato solo dalla funzione \func{lio\_listio}, che, come vedremo, permette di eseguire con una sola @@ -717,8 +3576,8 @@ chiamata una serie di operazioni, usando un vettore di \textit{control esse. Infine il campo \var{aio\_sigevent} è una struttura di tipo \struct{sigevent} -(illustrata in in fig.~\ref{fig:struct_sigevent}) che serve a specificare il -modo in cui si vuole che venga effettuata la notifica del completamento delle +(illustrata in fig.~\ref{fig:struct_sigevent}) che serve a specificare il modo +in cui si vuole che venga effettuata la notifica del completamento delle operazioni richieste; per la trattazione delle modalità di utilizzo della stessa si veda quanto già visto in proposito in sez.~\ref{sec:sig_timer_adv}. @@ -762,10 +3621,10 @@ Si tenga inoltre presente che deallocare la memoria indirizzata da operazione può dar luogo a risultati impredicibili, perché l'accesso ai vari campi per eseguire l'operazione può avvenire in un momento qualsiasi dopo la richiesta. Questo comporta che non si devono usare per \param{aiocbp} -\index{variabili!automatiche} variabili automatiche e che non si deve -riutilizzare la stessa struttura per un'altra operazione fintanto che la -precedente non sia stata ultimata. In generale per ogni operazione si deve -utilizzare una diversa struttura \struct{aiocb}. +variabili automatiche e che non si deve riutilizzare la stessa struttura per +un'altra operazione fintanto che la precedente non sia stata ultimata. In +generale per ogni operazione si deve utilizzare una diversa struttura +\struct{aiocb}. Dato che si opera in modalità asincrona, il successo di \func{aio\_read} o \func{aio\_write} non implica che le operazioni siano state effettivamente @@ -901,13 +3760,13 @@ file descriptor diverso da \param{fd} il risultato è indeterminato. In caso di successo, i possibili valori di ritorno per \func{aio\_cancel} (anch'essi definiti in \headfile{aio.h}) sono tre: \begin{basedescript}{\desclabelwidth{3.0cm}} -\item[\const{AIO\_ALLDONE}] indica che le operazioni di cui si è richiesta la +\item[\constd{AIO\_ALLDONE}] indica che le operazioni di cui si è richiesta la cancellazione sono state già completate, -\item[\const{AIO\_CANCELED}] indica che tutte le operazioni richieste sono +\item[\constd{AIO\_CANCELED}] indica che tutte le operazioni richieste sono state cancellate, -\item[\const{AIO\_NOTCANCELED}] indica che alcune delle operazioni erano in +\item[\constd{AIO\_NOTCANCELED}] indica che alcune delle operazioni erano in corso e non sono state cancellate. \end{basedescript} @@ -995,9 +3854,9 @@ Ciascuna struttura \struct{aiocb} della lista deve contenere un ognuna di esse dovrà essere specificato il tipo di operazione con il campo \var{aio\_lio\_opcode}, che può prendere i valori: \begin{basedescript}{\desclabelwidth{2.0cm}} -\item[\const{LIO\_READ}] si richiede una operazione di lettura. -\item[\const{LIO\_WRITE}] si richiede una operazione di scrittura. -na operazione. +\item[\constd{LIO\_READ}] si richiede una operazione di lettura. +\item[\constd{LIO\_WRITE}] si richiede una operazione di scrittura. +\item[\constd{LIO\_NOP}] non si effettua nessuna operazione. \end{basedescript} dove \const{LIO\_NOP} viene usato quando si ha a che fare con un vettore di dimensione fissa, per poter specificare solo alcune operazioni, o quando si @@ -1005,8 +3864,8 @@ sono dovute cancellare delle operazioni e si deve ripetere la richiesta per quelle non completate. L'argomento \param{mode} controlla il comportamento della funzione, se viene -usato il valore \const{LIO\_WAIT} la funzione si blocca fino al completamento -di tutte le operazioni richieste; se si usa \const{LIO\_NOWAIT} la funzione +usato il valore \constd{LIO\_WAIT} la funzione si blocca fino al completamento +di tutte le operazioni richieste; se si usa \constd{LIO\_NOWAIT} la funzione ritorna immediatamente dopo aver messo in coda tutte le richieste. In tal caso il chiamante può richiedere la notifica del completamento di tutte le richieste, impostando l'argomento \param{sig} in maniera analoga a come si fa @@ -1017,7 +3876,17 @@ per il campo \var{aio\_sigevent} di \struct{aiocb}. % http://webfiveoh.com/content/guides/2012/aug/mon-13th/linux-asynchronous-io-and-libaio.html, % https://code.google.com/p/kernel/wiki/AIOUserGuide, % http://bert-hubert.blogspot.de/2012/05/on-linux-asynchronous-file-io.html +% https://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt +% TODO trattare la poll API basata sull'I/O asicrono, introdotta con il kernel +% 4.18, vedi https://lwn.net/Articles/743714/, +% https://lwn.net/Articles/742978/, https://lwn.net/Articles/758324/ +% http://git.infradead.org/users/hch/vfs.git/commit/d2d9e26c7cb6d95d521153897910080cf56c7fad +% Reverted + +% TODO trattare la nuova API per l'I/O asincrono (io_uring), introdotta con il +% kernel 5.1, vedi https://lwn.net/Articles/776703/, +% https://lwn.net/ml/linux-fsdevel/20190112213011.1439-1-axboe@kernel.dk/ \section{Altre modalità di I/O avanzato} \label{sec:file_advanced_io} @@ -1035,10 +3904,11 @@ avanzato. \label{sec:file_memory_map} \itindbeg{memory~mapping} + Una modalità alternativa di I/O, che usa una interfaccia completamente diversa rispetto a quella classica vista in sez.~\ref{sec:file_unix_interface}, è il cosiddetto \textit{memory-mapped I/O}, che attraverso il meccanismo della -\textsl{paginazione} \index{paginazione} usato dalla memoria virtuale (vedi +\textsl{paginazione} usato dalla memoria virtuale (vedi sez.~\ref{sec:proc_mem_gen}) permette di \textsl{mappare} il contenuto di un file in una sezione dello spazio di indirizzi del processo che lo ha allocato. @@ -1150,10 +4020,10 @@ memoria. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{PROT\_EXEC} & Le pagine possono essere eseguite.\\ - \const{PROT\_READ} & Le pagine possono essere lette.\\ - \const{PROT\_WRITE} & Le pagine possono essere scritte.\\ - \const{PROT\_NONE} & L'accesso alle pagine è vietato.\\ + \constd{PROT\_EXEC} & Le pagine possono essere eseguite.\\ + \constd{PROT\_READ} & Le pagine possono essere lette.\\ + \constd{PROT\_WRITE} & Le pagine possono essere scritte.\\ + \constd{PROT\_NONE} & L'accesso alle pagine è vietato.\\ \hline \end{tabular} \caption{Valori dell'argomento \param{prot} di \func{mmap}, relativi alla @@ -1182,64 +4052,65 @@ file. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{MAP\_32BIT} & Esegue la mappatura sui primi 2Gb dello spazio + \constd{MAP\_32BIT} & Esegue la mappatura sui primi 2Gb 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} (dal kernel 2.4.20).\\ - \const{MAP\_ANON} & Sinonimo di \const{MAP\_ANONYMOUS}, deprecato.\\ - \const{MAP\_ANONYMOUS} & La mappatura non è associata a nessun file. Gli + \constd{MAP\_ANON} & Sinonimo di \const{MAP\_ANONYMOUS}, deprecato.\\ + \constd{MAP\_ANONYMOUS}& La mappatura non è associata a nessun file. Gli argomenti \param{fd} e \param{offset} sono ignorati. L'uso di questo flag con \const{MAP\_SHARED} è stato implementato in Linux a partire dai kernel della serie 2.4.x.\\ - \const{MAP\_DENYWRITE} & In Linux viene ignorato per evitare + \constd{MAP\_DENYWRITE}& In Linux viene ignorato per evitare \textit{DoS} (veniva usato per segnalare che tentativi di scrittura sul file dovevano fallire con \errcode{ETXTBSY}).\\ - \const{MAP\_EXECUTABLE}& Ignorato.\\ - \const{MAP\_FILE} & Valore di compatibilità, ignorato.\\ - \const{MAP\_FIXED} & Non permette di restituire un indirizzo diverso + \constd{MAP\_EXECUTABLE}& Ignorato.\\ + \constd{MAP\_FILE} & Valore di compatibilità, ignorato.\\ + \constd{MAP\_FIXED} & Non permette di restituire un indirizzo diverso da \param{start}, se questo non può essere usato \func{mmap} fallisce. Se si imposta questo flag il valore di \param{start} deve essere allineato alle dimensioni di una pagina.\\ - \const{MAP\_GROWSDOWN} & Usato per gli \textit{stack}. + \constd{MAP\_GROWSDOWN}& Usato per gli \textit{stack}. Indica che la mappatura deve essere effettuata con gli indirizzi crescenti verso il basso.\\ - \const{MAP\_HUGETLB} & Esegue la mappatura usando le cosiddette + \constd{MAP\_HUGETLB} & Esegue la mappatura usando le cosiddette ``\textit{huge pages}'' (dal kernel 2.6.32).\\ - \const{MAP\_LOCKED} & Se impostato impedisce lo \textit{swapping} delle + \constd{MAP\_LOCKED} & Se impostato impedisce lo \textit{swapping} delle pagine mappate (dal kernel 2.5.37).\\ - \const{MAP\_NONBLOCK} & Esegue un \textit{prefaulting} più limitato che + \constd{MAP\_NONBLOCK} & Esegue un \textit{prefaulting} più limitato che non causa I/O (dal kernel 2.5.46).\\ - \const{MAP\_NORESERVE} & Si usa con \const{MAP\_PRIVATE}. Non riserva + \constd{MAP\_NORESERVE}& Si usa con \const{MAP\_PRIVATE}. Non riserva delle pagine di \textit{swap} ad uso del meccanismo del \textit{copy on write} per mantenere le modifiche fatte alla regione mappata, in questo caso dopo una scrittura, se non c'è più memoria disponibile, si ha l'emissione di un \signal{SIGSEGV}.\\ - \const{MAP\_POPULATE} & Esegue il \textit{prefaulting} delle pagine di + \constd{MAP\_POPULATE} & Esegue il \textit{prefaulting} delle pagine di memoria necessarie alla mappatura (dal kernel 2.5.46).\\ - \const{MAP\_PRIVATE} & I cambiamenti sulla memoria mappata non vengono + \constd{MAP\_PRIVATE} & I cambiamenti sulla memoria mappata non vengono riportati sul file. Ne viene fatta una copia privata cui solo il processo chiamante ha accesso. Incompatibile con \const{MAP\_SHARED}.\\ - \const{MAP\_SHARED} & I cambiamenti sulla memoria mappata vengono + \constd{MAP\_SHARED} & I cambiamenti sulla memoria mappata vengono riportati sul file e saranno immediatamente visibili agli altri processi che mappano lo stesso file. Incompatibile con \const{MAP\_PRIVATE}.\\ \const{MAP\_STACK} & Al momento è ignorato, è stato fornito (dal kernel 2.6.27) a supporto della implementazione dei - thread nelle \acr{glibc}, per allocare memoria in - uno spazio utilizzabile come \textit{stack} per le - architetture hardware che richiedono un - trattamento speciale di quest'ultimo.\\ - \const{MAP\_UNINITIALIZED}& Specifico per i sistemi embedded ed + \textit{thread} nella \acr{glibc}, per allocare + memoria in uno spazio utilizzabile come + \textit{stack} per le architetture hardware che + richiedono un trattamento speciale di + quest'ultimo.\\ + \constd{MAP\_UNINITIALIZED}& Specifico per i sistemi embedded ed utilizzabile dal kernel 2.6.33 solo se è stata abilitata in fase di compilazione dello stesso l'opzione @@ -1253,7 +4124,7 @@ file. per i sistemi embedded) si ha il completo controllo dell'uso della memoria da parte degli utenti.\\ -% \const{MAP\_DONTEXPAND}& Non consente una successiva espansione dell'area +% \constd{MAP\_DONTEXPAND}& Non consente una successiva espansione dell'area % mappata con \func{mremap}, proposto ma pare non % implementato.\\ \hline @@ -1265,6 +4136,14 @@ file. % TODO trattare MAP_HUGETLB introdotto con il kernel 2.6.32, e modifiche % introdotte con il 3.8 per le dimensioni variabili delle huge pages +% TODO trattare MAP_FIXED_NOREPLACE vedi https://lwn.net/Articles/751651/ e +% https://lwn.net/Articles/741369/ + +% TODO: verificare MAP_SYNC e MAP_SHARED_VALIDATE, vedi +% https://lwn.net/Articles/731706/, https://lwn.net/Articles/758594/ incluse +% con il 4.15 + + L'argomento \param{flags} specifica infine qual è il tipo di oggetto mappato, le opzioni relative alle modalità con cui è effettuata la mappatura e alle modalità con cui le modifiche alla memoria mappata vengono condivise o @@ -1435,7 +4314,6 @@ relativi tempi di modifica. In questo modo si è sicuri che dopo l'esecuzione di \func{msync} le funzioni dell'interfaccia ordinaria troveranno un contenuto del file aggiornato. - \begin{table}[htb] \centering \footnotesize @@ -1444,11 +4322,11 @@ del file aggiornato. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{MS\_SYNC} & richiede una sincronizzazione e ritorna soltanto + \constd{MS\_SYNC} & richiede una sincronizzazione e ritorna soltanto quando questa è stata completata.\\ - \const{MS\_ASYNC} & richiede una sincronizzazione, ma ritorna subito + \constd{MS\_ASYNC} & richiede una sincronizzazione, ma ritorna subito non attendendo che questa sia finita.\\ - \const{MS\_INVALIDATE} & invalida le pagine per tutte le mappature + \constd{MS\_INVALIDATE}& invalida le pagine per tutte le mappature in memoria così da rendere necessaria una rilettura immediata delle stesse.\\ \hline @@ -1570,7 +4448,7 @@ 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} che consente di eseguire +Il solo valore utilizzato è \constd{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 @@ -1760,87 +4638,98 @@ caching dei dati. \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 - \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, - quindi da una parte sarà opportuno eseguire una - lettura anticipata, e dall'altra si potranno - scartare immediatamente le pagine una volta che - queste siano state lette.\\ + \constd{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.\\ + \constd{MADV\_NORMAL} & nessuna indicazione specifica, questo è il valore + di default usato quando non si è chiamato + \func{madvise}.\\ + \constd{MADV\_RANDOM} & ci si aspetta un accesso casuale all'area + indicata, pertanto l'applicazione di una lettura + anticipata con il meccanismo del + \textit{read-ahead} (vedi + sez.~\ref{sec:file_fadvise}) è di + scarsa utilità e verrà disabilitata.\\ + \constd{MADV\_SEQUENTIAL}& ci si aspetta un accesso sequenziale al file, + quindi da una parte sarà opportuno eseguire una + lettura anticipata, e dall'altra si potranno + scartare immediatamente le pagine una volta che + queste siano state lette.\\ \const{MADV\_WILLNEED}& ci si aspetta un accesso nell'immediato futuro, pertanto l'applicazione del \textit{read-ahead} deve essere incentivata.\\ \hline - \const{MADV\_DONTDUMP}& esclude da un \textit{core dump} (vedi - sez.~\ref{sec:sig_standard}) le pagine - specificate, viene usato per evitare di scrivere - su disco dati relativi a zone di memoria che si sa - non essere utili in un \textit{core dump}.\\ - \const{MADV\_DODUMP} & rimuove l'effetto della precedente - \const{MADV\_DONTDUMP} (dal kernel 3.4).\\ - \const{MADV\_DONTFORK}& impedisce che l'intervallo specificato venga - ereditato dal processo figlio dopo una - \func{fork}; questo consente di evitare che il - meccanismo del \textit{copy on write} effettui la - rilocazione delle pagine quando il padre scrive - sull'area di memoria dopo la \func{fork}, cosa che - può causare problemi per l'hardware che esegue - operazioni in DMA su quelle pagine (dal kernel - 2.6.16).\\ - \const{MADV\_DOFORK} & rimuove l'effetto della precedente - \const{MADV\_DONTFORK} (dal kernel 2.6.16).\\ - \const{MADV\_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 (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 + \constd{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}.\\ + \constd{MADV\_DODUMP} & rimuove l'effetto della precedente + \const{MADV\_DONTDUMP} (dal kernel 3.4).\\ + \constd{MADV\_DONTFORK}& impedisce che l'intervallo specificato venga + ereditato dal processo figlio dopo una + \func{fork}; questo consente di evitare che il + meccanismo del \textit{copy on write} effettui la + rilocazione delle pagine quando il padre scrive + sull'area di memoria dopo la \func{fork}, cosa che + può causare problemi per l'hardware che esegue + operazioni in DMA su quelle pagine (dal kernel + 2.6.16).\\ + \constd{MADV\_DOFORK} & rimuove l'effetto della precedente + \const{MADV\_DONTFORK} (dal kernel 2.6.16).\\ + \constd{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).\\ + \constd{MADV\_NOHUGEPAGE}& impedisce che la regione indicata venga + collassata in eventuali \textit{huge page} (dal + kernel 2.6.38).\\ + \constd{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).\\ + \constd{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).\\ + \constd{MADV\_MERGEABLE}& marca la pagina come accorpabile, indicazione + principalmente ad uso dei sistemi di + virtualizzazione\footnotemark (dal kernel + 2.6.32).\\ + \constd{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).\\ + \constd{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} +% TODO aggiunta MADV_FREE dal kernel 4.5 (vedi http://lwn.net/Articles/590991/) +% TODO aggiunta MADV_WIPEONFORK dal kernel 4.14 that causes the affected memory +% region to appear to be full of zeros in the child process after a fork. It +% differs from the existing MADV_DONTFORK in that the address range will +% remain valid in the child (dalla notizia in +% https://lwn.net/Articles/733256/). +% TODO aggiunte MADV_COLD e MADV_PAGEOUT dal kernel 5.4, vedi +% https://git.kernel.org/linus/9c276cc65a58 e +% https://git.kernel.org/linus/1a4e58cce84e + \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 @@ -1896,11 +4785,11 @@ funzione, su Linux un valore nullo di \param{len} è consentito. \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}.\\ + \constd{POSIX\_MADV\_DONTNEED}& analogo a \const{MADV\_DONTNEED}.\\ + \constd{POSIX\_MADV\_NORMAL} & identico a \const{MADV\_NORMAL}.\\ + \constd{POSIX\_MADV\_RANDOM} & identico a \const{MADV\_RANDOM}.\\ + \constd{POSIX\_MADV\_SEQUENTIAL}& identico a \const{MADV\_SEQUENTIAL}.\\ + \constd{POSIX\_MADV\_WILLNEED}& identico a \const{MADV\_WILLNEED}.\\ \hline \end{tabular} \caption{Valori dell'argomento \param{advice} di \func{posix\_madvise}.} @@ -1911,7 +4800,7 @@ funzione, su Linux un valore nullo di \param{len} è consentito. 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}. Infatti a partire dalla \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, @@ -1996,11 +4885,11 @@ stesso valore deve essere ottenibile in esecuzione tramite la funzione \func{sysconf} richiedendo l'argomento \const{\_SC\_IOV\_MAX} (vedi sez.~\ref{sec:sys_limits}). -Nel caso di Linux il limite di sistema è di 1024, però se si usano le -\acr{glibc} queste forniscono un \textit{wrapper} per le \textit{system call} +Nel caso di Linux il limite di sistema è di 1024, però se si usa la +\acr{glibc} essa fornisce un \textit{wrapper} per le \textit{system call} che si accorge se una operazione supererà il precedente limite, in tal caso i dati verranno letti o scritti con le usuali \func{read} e \func{write} usando -un buffer di dimensioni sufficienti appositamente allocato e sufficiente a +un buffer di dimensioni sufficienti appositamente allocato in grado di contenere tutti i dati indicati da \param{vector}. L'operazione avrà successo ma si perderà l'atomicità del trasferimento da e verso la destinazione finale. @@ -2050,13 +4939,17 @@ processi che vi facciano riferimento, non viene alterata. A parte la presenza dell'ulteriore argomento il comportamento delle funzioni è identico alle precedenti \func{readv} e \func{writev}. -Con l'uso di queste funzioni si possono evitare eventuali -\itindex{race~condition} \textit{race condition} quando si deve eseguire la -una operazione di lettura e scrittura vettorizzata a partire da una certa -posizione su un file, mentre al contempo si possono avere in concorrenza -processi che utilizzano lo stesso file descriptor (si ricordi quanto visto in -sez.~\ref{sec:file_adv_func}) con delle chiamate a \func{lseek}. +Con l'uso di queste funzioni si possono evitare eventuali \textit{race + condition} quando si deve eseguire la una operazione di lettura e scrittura +vettorizzata a partire da una certa posizione su un file, mentre al contempo +si possono avere in concorrenza processi che utilizzano lo stesso file +descriptor (si ricordi quanto visto in sez.~\ref{sec:file_adv_func}) con delle +chiamate a \func{lseek}. +% TODO trattare preadv2() e pwritev2(), introdotte con il kernel 4.6, vedi +% http://lwn.net/Articles/670231/ ed il flag RWF_HIPRI, anche l'aggiunta del +% flag RWF_APPEND a pwritev2 con il kernel 4.16, vedi +% https://lwn.net/Articles/746129/ \subsection{L'I/O diretto fra file descriptor: \func{sendfile} e @@ -2078,7 +4971,7 @@ 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 - introdotta con i kernel della serie 2.2, e disponibile dalle \acr{glibc} + introdotta con i kernel della serie 2.2, e disponibile dalla \acr{glibc} 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 @@ -2296,55 +5189,55 @@ descrizioni complete di tutti i valori possibili anche quando, come per \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{SPLICE\_F\_MOVE} & Suggerisce al kernel di spostare le pagine - di memoria contenenti i dati invece di - copiarle: 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 - \textit{pipe}. Nel caso di \func{splice} - questo significa che la funzione potrà - comunque bloccarsi nell'accesso agli altri - file descriptor (a meno che anch'essi non - siano stati aperti in modalità non - bloccante).\\ - \const{SPLICE\_F\_MORE} & Indica al kernel che ci sarà l'invio di - ulteriori dati in una \func{splice} - successiva, questo è un suggerimento utile - che viene usato quando \param{fd\_out} è un - socket. 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; 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 - dati dovranno essere opportunamente allineati - in posizione ed in dimensione alle pagine di - memoria. Viene usato soltanto da - \func{vmsplice}.\\ + \constd{SPLICE\_F\_MOVE} & Suggerisce al kernel di spostare le pagine + di memoria contenenti i dati invece di + 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}.\\ + \constd{SPLICE\_F\_NONBLOCK}& Richiede di operare in modalità non + bloccante; questo flag influisce solo sulle + operazioni che riguardano l'I/O da e verso la + \textit{pipe}. Nel caso di \func{splice} + questo significa che la funzione potrà + comunque bloccarsi nell'accesso agli altri + file descriptor (a meno che anch'essi non + siano stati aperti in modalità non + bloccante).\\ + \constd{SPLICE\_F\_MORE} & Indica al kernel che ci sarà l'invio di + ulteriori dati in una \func{splice} + successiva, questo è un suggerimento utile + che viene usato quando \param{fd\_out} è un + socket. 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}.\\ + \constd{SPLICE\_F\_GIFT} & Le pagine di memoria utente sono + ``\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 + dati dovranno essere opportunamente allineati + in posizione ed in dimensione alle pagine di + memoria. Viene usato soltanto da + \func{vmsplice}.\\ \hline \end{tabular} \caption{Le costanti che identificano i bit della maschera binaria @@ -2598,6 +5491,9 @@ copiati i puntatori. % TODO?? dal 2.6.25 splice ha ottenuto il supporto per la ricezione su rete +% TODO trattare qui copy_file_range (vedi http://lwn.net/Articles/659523/), +% introdotta nel kernel 4.5 + \subsection{Gestione avanzata dell'accesso ai dati dei file} \label{sec:file_fadvise} @@ -2727,19 +5623,19 @@ che utilizza semplicemente l'informazione. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{POSIX\_FADV\_NORMAL} & Non ci sono avvisi specifici da fare + \constd{POSIX\_FADV\_NORMAL} & Non ci sono avvisi specifici da fare riguardo le modalità di accesso, il comportamento sarà identico a quello che si avrebbe senza nessun avviso.\\ - \const{POSIX\_FADV\_SEQUENTIAL}& L'applicazione si aspetta di accedere di + \constd{POSIX\_FADV\_SEQUENTIAL}& L'applicazione si aspetta di accedere di accedere ai dati specificati in maniera sequenziale, a partire dalle posizioni più basse.\\ - \const{POSIX\_FADV\_RANDOM} & I dati saranno letti in maniera + \constd{POSIX\_FADV\_RANDOM} & I dati saranno letti in maniera completamente causale.\\ - \const{POSIX\_FADV\_NOREUSE} & I dati saranno acceduti una sola volta.\\ - \const{POSIX\_FADV\_WILLNEED}& I dati saranno acceduti a breve.\\ - \const{POSIX\_FADV\_DONTNEED}& I dati non saranno acceduti a breve.\\ + \constd{POSIX\_FADV\_NOREUSE} & I dati saranno acceduti una sola volta.\\ + \constd{POSIX\_FADV\_WILLNEED}& I dati saranno acceduti a breve.\\ + \constd{POSIX\_FADV\_DONTNEED}& I dati non saranno acceduti a breve.\\ \hline \end{tabular} \caption{Valori delle costanti usabili per l'argomento \param{advice} di @@ -2829,7 +5725,7 @@ dei buchi.\footnote{si ricordi che occorre scrivere per avere l'allocazione e che l'uso di \func{truncate} per estendere un file creerebbe soltanto uno \textit{sparse file} (vedi sez.~\ref{sec:file_lseek}) senza una effettiva allocazione dello spazio disco.} In realtà questa è la modalità con cui la -funzione veniva realizzata nella prima versione fornita dalle \acr{glibc}, per +funzione veniva realizzata nella prima versione fornita dalla \acr{glibc}, per cui la funzione costituiva in sostanza soltanto una standardizzazione delle modalità di esecuzione di questo tipo di allocazioni. @@ -2846,7 +5742,7 @@ solo a partire dal kernel 2.6.23 in cui è stata introdotta la nuova stato introdotto solo a partire dal kernel 2.6.25.} che consente di realizzare direttamente all'interno del kernel l'allocazione dello spazio disco così da poter realizzare una versione di \func{posix\_fallocate} con -prestazioni molto più elevate; nelle \acr{glibc} la nuova \textit{system call} +prestazioni molto più elevate; nella \acr{glibc} la nuova \textit{system call} viene sfruttata per la realizzazione di \func{posix\_fallocate} a partire dalla versione 2.10. @@ -2855,7 +5751,7 @@ esclusivamente su Linux, inizialmente \funcd{fallocate} non era stata definita come funzione di libreria,\footnote{pertanto poteva essere invocata soltanto in maniera indiretta con l'ausilio di \func{syscall}, vedi sez.~\ref{sec:proc_syscall}, come \code{long fallocate(int fd, int mode, - loff\_t offset, loff\_t len)}.} ma a partire dalle \acr{glibc} 2.10 è + loff\_t offset, loff\_t len)}.} ma a partire dalla \acr{glibc} 2.10 è stato fornito un supporto esplicito; il suo prototipo è: \begin{funcproto}{ @@ -2893,9 +5789,9 @@ quello di \func{posix\_fallocate} e si può considerare \func{fallocate} come l'implementazione ottimale della stessa a livello di kernel. Inizialmente l'unico altro valore possibile per \param{mode} era -\const{FALLOC\_FL\_KEEP\_SIZE} che richiede che la dimensione del file (quella -ottenuta nel campo \var{st\_size} di una struttura \struct{stat} dopo una -chiamata a \texttt{fstat}) non venga modificata anche quando la somma +\const{FALLOC\_FL\_KEEP\_SIZE} che richiede che la dimensione del file +(quella ottenuta nel campo \var{st\_size} di una struttura \struct{stat} dopo +una chiamata a \texttt{fstat}) non venga modificata anche quando la somma di \param{offset} e \param{len} eccede la dimensione corrente, che serve quando si deve comunque preallocare dello spazio per scritture in append. In seguito sono stati introdotti altri valori, riassunti in @@ -2910,16 +5806,16 @@ allocazione dello spazio disco dei file. \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \const{FALLOC\_FL\_INSERT} & .\\ - \const{FALLOC\_FL\_COLLAPSE\_RANGE}& .\\ - \const{FALLOC\_FL\_KEEP\_SIZE} & Mantiene invariata la dimensione del + \constd{FALLOC\_FL\_INSERT} & .\\ + \constd{FALLOC\_FL\_COLLAPSE\_RANGE}& .\\ + \constd{FALLOC\_FL\_KEEP\_SIZE} & Mantiene invariata la dimensione del file, pur allocando lo spazio disco anche oltre la dimensione corrente del file.\\ - \const{FALLOC\_FL\_PUNCH\_HOLE}& Crea un \textsl{buco} nel file (vedi + \constd{FALLOC\_FL\_PUNCH\_HOLE}& Crea un \textsl{buco} nel file (vedi sez.~\ref{sec:file_lseek}) rendendolo una \textit{sparse file} (dal kernel 2.6.38).\\ - \const{FALLOC\_FL\_ZERO\_RANGE}& .\\ + \constd{FALLOC\_FL\_ZERO\_RANGE}& .\\ \hline \end{tabular} \caption{Valori delle costanti usabili per l'argomento \param{mode} di @@ -2939,15 +5835,31 @@ file uno \textit{sparse file} a posteriori. % vedi http://lwn.net/Articles/226710/ e http://lwn.net/Articles/240571/ % http://kernelnewbies.org/Linux_2_6_23 + % TODO aggiungere FALLOC_FL_ZERO_RANGE e FALLOC_FL_COLLAPSE_RANGE, inseriti % 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 aggiungere i file hints di fcntl (F_GET_RW_HINT e compagnia) +% con RWH_WRITE_LIFE_EXTREME e RWH_WRITE_LIFE_SHORT aggiunte con +% il kernel 4.13 (vedi https://lwn.net/Articles/727385/) + +\subsection{Altre funzionalità avanzate} +\label{sec:file_seal_et_al} + +da fare % TODO non so dove trattarli, ma dal 2.6.39 ci sono i file handle, vedi -% http://lwn.net/Articles/432757/ +% http://lwn.net/Articles/432757/ (probabilmente da associare alle +% at-functions) + +% TODO: trattare i file seal, vedi fcntl / F_ADD_SEAL e memfd_create + +% TODO trattare qui ioctl_ficlonerange ? + +% TODO trattare qui close_range, vedi https://lwn.net/Articles/789023/ % LocalWords: dell'I locking multiplexing cap sez system call socket BSD GID