From: Simone Piccardi Date: Sun, 5 Feb 2012 17:47:35 +0000 (+0000) Subject: Altro accorpamento di sezioni e risistemazione dei riferimenti, X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=commitdiff_plain;h=3cf93b3dfc49fbf8b16f908bee85fa2ecaea4e3f;p=gapil.git Altro accorpamento di sezioni e risistemazione dei riferimenti, Completato tutto fino a dupN. --- diff --git a/fileadv.tex b/fileadv.tex index 1de6aeb..a4ca6e2 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -26,12 +26,12 @@ controllo più dettagliato delle modalità di I/O. \itindbeg{file~locking} -In sez.~\ref{sec:file_sharing} abbiamo preso in esame le modalità in cui un -sistema unix-like gestisce la condivisione dei file da parte di processi -diversi. In quell'occasione si è visto come, con l'eccezione dei file aperti -in \itindex{append~mode} \textit{append mode}, quando più processi scrivono -contemporaneamente sullo stesso file non è possibile determinare la sequenza -in cui essi opereranno. +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 \itindex{append~mode} \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 \itindex{race~condition} \textit{race condition}; in generale le situazioni più comuni sono due: l'interazione fra @@ -260,8 +260,8 @@ Questa struttura prevede che, quando si richiede la rimozione di un file descriptor che fa riferimento ad una voce nella \itindex{file~table} \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_sharing}, e cioè che i file descriptor duplicati e quelli -ereditati in un processo figlio puntano sempre alla stessa voce nella +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 \itindex{file~table} \textit{file table}, si può capire immediatamente quali sono le conseguenze nei confronti delle funzioni \func{dup} e \func{fork}. diff --git a/fileio.tex b/fileio.tex index 481d210..d631d40 100644 --- a/fileio.tex +++ b/fileio.tex @@ -294,7 +294,7 @@ nuovo file questo diventerà il nuovo \itindex{standard~input} \textit{standard Al momento dell'apertura il nuovo file descriptor non è condiviso con nessun altro processo (torneremo sul significato della condivisione dei file descriptor, che in genere si ottiene dopo una \func{fork}, in -sez.~\ref{sec:file_sharing}) ed è impostato, come accennato in +sez.~\ref{sec:file_shared_access}) ed è impostato, come accennato in sez.~\ref{sec:proc_exec}, per restare aperto attraverso una \func{exec}. Inoltre la posizione sul file, il cosiddetto \textit{offset}, è impostata all'inizio del file. Una volta aperto un file si potrà operare su di @@ -699,8 +699,8 @@ La funzione chiude il file descriptor \param{fd}. La chiusura rilascia ogni eventuale blocco (il \textit{file locking} \itindex{file~locking} è trattato in sez.~\ref{sec:file_locking}) che il processo poteva avere acquisito su di esso. Se \param{fd} è l'ultimo riferimento (di eventuali copie, vedi -sez.~\ref{sec:file_sharing} e \ref{sec:file_dup}) ad un file aperto, tutte le -risorse nella \itindex{file~table} \textit{file table} vengono +sez.~\ref{sec:file_shared_access} e \ref{sec:file_dup}) ad un file aperto, +tutte le risorse nella \itindex{file~table} \textit{file table} vengono rilasciate. Infine se il file descriptor era l'ultimo riferimento ad un file su disco quest'ultimo viene cancellato. @@ -818,7 +818,7 @@ la successiva scrittura avvenga alla fine del file, infatti se questo è stato aperto anche da un altro processo che vi ha scritto, la fine del file può essersi spostata, ma noi scriveremo alla posizione impostata in precedenza (questa è una potenziale sorgente di \itindex{race~condition} \textit{race - condition}, vedi sez.~\ref{sec:file_atomic}). + condition}, vedi sez.~\ref{sec:file_shared_access}). Non tutti i file supportano la capacità di eseguire una \func{lseek}, in questo caso la funzione ritorna l'errore \errcode{ESPIPE}. Questo, oltre che @@ -1024,7 +1024,7 @@ L'uso di \func{pread} è equivalente all'esecuzione di una \func{read} seguita da una \func{lseek} che riporti al valore precedente la posizione corrente sul file, ma permette di eseguire l'operazione atomicamente. Questo può essere importante quando la posizione sul file viene condivisa da processi diversi -(vedi sez.~\ref{sec:file_sharing}). Il valore di +(vedi sez.~\ref{sec:file_shared_access}). Il valore di \param{offset} fa sempre riferimento all'inizio del file. La funzione \func{pread} è disponibile anche in Linux, però diventa @@ -1115,14 +1115,15 @@ permettono di eseguire alcune operazioni avanzate con i file (il grosso dell'argomento sarà comunque affrontato in cap.~\ref{cha:file_advanced}). -\subsection{La condivisione dei files} -\label{sec:file_sharing} +\subsection{La gestione dell'accesso concorrente ai files} +\label{sec:file_shared_access} In sez.~\ref{sec:file_fd} abbiamo descritto brevemente l'architettura dell'interfaccia con i file da parte di un processo, mostrando in fig.~\ref{fig:file_proc_file} le principali strutture usate dal kernel; esamineremo ora in dettaglio le conseguenze che questa architettura ha nei -confronti dell'accesso allo stesso file da parte di processi diversi. +confronti dell'accesso concorrente allo stesso file da parte di processi +diversi. \begin{figure}[!htb] \centering @@ -1147,20 +1148,20 @@ vengono mantenute nella sua voce della \itindex{file~table} \textit{file azione simultanea sullo stesso file, in particolare occorre tenere presente che: \begin{itemize} -\item ciascun processo può scrivere indipendentemente; dopo ciascuna - \func{write} la posizione corrente sarà cambiata solo nel processo. Se la - scrittura eccede la dimensione corrente del file questo verrà esteso - automaticamente con l'aggiornamento del campo \var{i\_size} \itindex{inode} - nell'\textit{inode}. +\item ciascun processo può scrivere indipendentemente, dopo ciascuna + \func{write} la posizione corrente sarà cambiata solo nel processo + scrivente. Se la scrittura eccede la dimensione corrente del file questo + verrà esteso automaticamente con l'aggiornamento del campo \var{i\_size} + della struttura \kstruct{inode}. \item se un file è in modalità \itindex{append~mode} \const{O\_APPEND} tutte le volte che viene effettuata una scrittura la posizione corrente viene - prima impostata alla dimensione corrente del file letta \itindex{inode} - dall'\textit{inode}. Dopo la scrittura il file viene automaticamente esteso. + prima impostata alla dimensione corrente del file letta dalla struttura + \kstruct{inode}. Dopo la scrittura il file viene automaticamente esteso. \item l'effetto di \func{lseek} è solo quello di cambiare il campo \var{f\_pos} nella struttura \kstruct{file} della \itindex{file~table} \textit{file table}, non c'è nessuna operazione sul file su disco. Quando la si usa per porsi alla fine del file la posizione viene impostata leggendo la - dimensione corrente \itindex{inode} dall'\textit{inode}. + dimensione corrente dalla struttura \kstruct{inode}. \end{itemize} \begin{figure}[!htb] @@ -1171,175 +1172,89 @@ che: \end{figure} Il secondo caso è quello in cui due file descriptor di due processi diversi -puntino alla stessa voce nella \itindex{file~table} \textit{file table}; -questo è ad esempio il caso dei file aperti che vengono ereditati dal processo +puntino alla stessa voce nella \itindex{file~table} \textit{file table}. +Questo è ad esempio il caso dei file aperti che vengono ereditati dal processo figlio all'esecuzione di una \func{fork} (si ricordi quanto detto in sez.~\ref{sec:proc_fork}). La situazione è illustrata in fig.~\ref{fig:file_acc_child}; dato che il processo figlio riceve una copia dello spazio di indirizzi del padre, riceverà anche una copia di -\kstruct{file\_struct} e relativa tabella dei file aperti. - -In questo modo padre e figlio avranno gli stessi file descriptor che faranno -riferimento alla stessa voce nella \textit{file table}, condividendo così la -posizione corrente sul file. Questo ha le conseguenze descritte a suo tempo in -sez.~\ref{sec:proc_fork}: in caso di scrittura contemporanea la posizione -corrente nel file varierà per entrambi i processi (in quanto verrà modificato -\var{f\_pos} che è lo stesso per entrambi). - -Si noti inoltre che anche i \itindex{file~status~flag} flag di stato del file -(quelli impostati dall'argomento \param{flag} di \func{open}) essendo tenuti -nella voce della \textit{file table}, vengono in questo caso condivisi. Ai -file però sono associati anche altri flag, dei quali l'unico usato al momento -è \const{FD\_CLOEXEC}, detti \itindex{file~descriptor~flags} \textit{file - descriptor flags}. Questi ultimi sono tenuti invece in -\kstruct{file\_struct}, e perciò sono specifici di ciascun processo e non +\kstruct{file\_struct} e della relativa tabella dei file aperti. + +Questo significa che il figlio avrà gli stessi file aperti del padre, in +quanto la sua \kstruct{file\_struct}, pur essendo allocata in maniera +indipendente, contiene gli stessi valori di quella del padre e quindi i suoi +file descriptor faranno riferimento alla stessa voce nella +\itindex{file~table} \textit{file table}, condividendo così la posizione +corrente sul file. Questo ha le conseguenze descritte a suo tempo in +sez.~\ref{sec:proc_fork}: in caso di scrittura o lettura da parte di uno dei +due processi, la posizione corrente nel file varierà per entrambi, in quanto +verrà modificato il campo \var{f\_pos} della struttura \kstruct{file}, che è +la stessa per entrambi. Questo consente una sorta di +``\textsl{sincronizzazione}'' automatica della posizione sul file fra padre e +figlio che occorre tenere presente. + +Si noti inoltre che in questo caso anche i \itindex{file~status~flag} flag di +stato del file, essendo mantenuti nella struttura \kstruct{file} della +\textit{file table}, vengono condivisi, per cui una modifica degli stessi con +\func{fcntl} (vedi sez.~\ref{sec:file_fcntl}) si applicherebbe a tutti +processi che condividono la voce nella \itindex{file~table} \textit{file + table}. Ai file però sono associati anche altri flag, dei quali l'unico +usato al momento è \const{FD\_CLOEXEC}, detti \itindex{file~descriptor~flags} +\textit{file descriptor flags}; questi invece sono mantenuti in +\kstruct{file\_struct}, e perciò sono locali per ciascun processo e non vengono modificati dalle azioni degli altri anche in caso di condivisione -della stessa voce della \textit{file table}. - - - -\subsection{Operazioni atomiche con i file} -\label{sec:file_atomic} - -Come si è visto in un sistema unix-like è sempre possibile per più processi -accedere in contemporanea allo stesso file, e che le operazioni di lettura e -scrittura possono essere fatte da ogni processo in maniera autonoma in base -ad una posizione corrente nel file che è locale a ciascuno di essi. - -Se dal punto di vista della lettura dei dati questo non comporta nessun -problema, quando si andrà a scrivere le operazioni potranno mescolarsi in -maniera imprevedibile. Il sistema però fornisce in alcuni casi la possibilità -di eseguire alcune operazioni di scrittura in maniera coordinata anche senza -utilizzare meccanismi di sincronizzazione più complessi (come il -\itindex{file~locking} \textit{file locking}, che esamineremo in -sez.~\ref{sec:file_locking}). +della stessa voce della \itindex{file~table} \textit{file table}. + +Si tenga presente dunque che in un sistema unix-like è sempre possibile per +più processi accedere in contemporanea allo stesso file e che non esistono, a +differenza di altri sistemi operativi, dei meccanismi di blocco o di +restrizione dell'accesso impliciti se più processi vogliono accedere allo +stesso file. Questo significa che le operazioni di lettura e scrittura vengono +sempre fatte da ogni processo in maniera autonoma, utilizzando una posizione +corrente nel file che normalmente (a meno di non trovarsi nella situazione di +fig.~\ref{fig:file_acc_child}) è locale a ciascuno di essi. + +Dal punto di vista della lettura dei dati questo comporta la possibilità di +poter leggere dati non coerenti in caso di scrittura contemporanea da parte di +un altro processo. Dal punto di vista della scrittura invece si potranno avere +sovrapposizioni imprevedibili quando due processi scrivono nella stessa +sezione di file, dato che ciascuno lo farà in maniera indipendente. Il +sistema però fornisce in alcuni casi la possibilità di eseguire alcune +operazioni di scrittura in maniera coordinata anche senza utilizzare dei +meccanismi di sincronizzazione espliciti come il \itindex{file~locking} +\textit{file locking}, che esamineremo in sez.~\ref{sec:file_locking}. Un caso tipico di necessità di accesso condiviso in scrittura è quello in cui vari processi devono scrivere alla fine di un file (ad esempio un file di log). Come accennato in sez.~\ref{sec:file_lseek} impostare la posizione alla fine del file e poi scrivere può condurre ad una \itindex{race~condition} -\textit{race condition}: infatti può succedere che un secondo processo scriva -alla fine del file fra la \func{lseek} e la \func{write}; in questo caso, come -abbiamo appena visto, il file sarà esteso, ma il nostro primo processo avrà -ancora la posizione corrente impostata con la \func{lseek} che non corrisponde -più alla fine del file, e la successiva \func{write} sovrascriverà i dati del -secondo processo. - -Il problema è che usare due \textit{system call} in successione non è -un'operazione atomica; il problema è stato risolto introducendo la modalità -\itindex{append~mode} \const{O\_APPEND}. In questo caso infatti, come abbiamo -descritto in precedenza, è il kernel che aggiorna automaticamente la posizione -alla fine del file prima di effettuare la scrittura, e poi estende il file. -Tutto questo avviene all'interno di una singola \textit{system call} (la -\func{write}) che non essendo interrompibile da un altro processo costituisce -un'operazione atomica. - -Un altro caso tipico in cui è necessaria l'atomicità è quello in cui si vuole -creare un \textsl{file di lock} \index{file!di lock}, bloccandosi se il file -esiste. In questo caso la sequenza logica porterebbe a verificare prima -l'esistenza del file con una \func{stat} per poi crearlo con una \func{creat}; -di nuovo avremmo la possibilità di una \itindex{race~condition} \textit{race - condition} da parte di un altro processo che crea lo stesso file fra il -controllo e la creazione. - -Per questo motivo sono stati introdotti per \func{open} i due flag -\const{O\_CREAT} e \const{O\_EXCL}. In questo modo l'operazione di controllo -dell'esistenza del file (con relativa uscita dalla funzione con un errore) e -creazione in caso di assenza, diventa atomica essendo svolta tutta all'interno -di una singola \textit{system call} (per i dettagli sull'uso di questa -caratteristica si veda sez.~\ref{sec:ipc_file_lock}). - - -\subsection{Le funzioni \func{sync} e \func{fsync}} -\label{sec:file_sync} - -% TODO, aggiungere syncfs, introdotta con il 2.6.39 - -Come accennato in sez.~\ref{sec:file_open_close} tutte le operazioni di -scrittura sono in genere bufferizzate dal kernel, che provvede ad effettuarle -in maniera asincrona (ad esempio accorpando gli accessi alla stessa zona del -disco) in un secondo tempo rispetto al momento della esecuzione della -\func{write}. - -Per questo motivo, quando è necessaria una sincronizzazione dei dati, il -sistema mette a disposizione delle funzioni che provvedono a forzare lo -scarico dei dati dai buffer del kernel.\footnote{come già accennato neanche - questo dà la garanzia assoluta che i dati siano integri dopo la chiamata, - l'hardware dei dischi è in genere dotato di un suo meccanismo interno di - ottimizzazione per l'accesso al disco che può ritardare ulteriormente la - scrittura effettiva.} La prima di queste funzioni è \funcd{sync} il cui -prototipo è: - -\begin{funcproto}{ -\fhead{unistd.h} -\fdecl{int sync(void)} -\fdesc{Sincronizza il buffer della cache dei file col disco.} -} - -{La funzione ritorna sempre $0$ ed ha sempre successo.} -\end{funcproto} - -\noindent i vari standard prevedono che la funzione si limiti a far partire -le operazioni, ritornando immediatamente; in Linux (dal kernel 1.3.20) invece -la funzione aspetta la conclusione delle operazioni di sincronizzazione del -kernel. - -La funzione viene usata dal comando \cmd{sync} quando si vuole forzare -esplicitamente lo scarico dei dati su disco, o dal demone di sistema -\cmd{update} che esegue lo scarico dei dati ad intervalli di tempo fissi: il -valore tradizionale, usato da BSD, per l'update dei dati è ogni 30 secondi, ma -in Linux il valore utilizzato è di 5 secondi; con le nuove versioni\footnote{a - partire dal kernel 2.2.8} poi, è il kernel che si occupa direttamente di -tutto quanto attraverso il demone interno \cmd{bdflush}, il cui comportamento -può essere controllato attraverso il file \sysctlfile{vm/bdflush} (per -il significato dei valori si può leggere la documentazione allegata al kernel -in \file{Documentation/sysctl/vm.txt}). - -Quando si vogliono scaricare soltanto i dati di un file (ad esempio essere -sicuri che i dati di un database sono stati registrati su disco) si possono -usare le due funzioni \funcd{fsync} e \funcd{fdatasync}, i cui prototipi sono: - -\begin{funcproto}{ -\fhead{unistd.h} -\fdecl{int fsync(int fd)} -\fdesc{Sincronizza dati e metadati di un file.} -\fdecl{int fdatasync(int fd)} -\fdesc{Sincronizza i dati di un file.} -} - -{Le funzioni ritornano $0$ in caso di successo e $-1$ per un errore, nel qual - caso \var{errno} assumerà uno dei valori: - \begin{errlist} - \item[\errcode{EINVAL}] \param{fd} è un \index{file!speciali} file speciale - che non supporta la sincronizzazione. - \end{errlist} - ed inoltre \errval{EBADF}, \errval{EROFS} e \errval{EIO} nel loro - significato generico.} -\end{funcproto} - -Entrambe le funzioni forzano la sincronizzazione col disco di tutti i dati del -file specificato, ed attendono fino alla conclusione delle operazioni; -\func{fsync} forza anche la sincronizzazione dei meta-dati del file (che -riguardano sia le modifiche alle tabelle di allocazione dei settori, che gli -altri dati contenuti \itindex{inode} nell'\textit{inode} che si leggono con -\func{fstat}, come i tempi del file). - -Si tenga presente che questo non comporta la sincronizzazione della -directory che contiene il file (e scrittura della relativa voce su -disco) che deve essere effettuata esplicitamente.\footnote{in realtà per - il filesystem \acr{ext2}, quando lo si monta con l'opzione \cmd{sync}, - il kernel provvede anche alla sincronizzazione automatica delle voci - delle directory.} - - -\subsection{Le funzioni \func{dup} e \func{dup2}} +\textit{race condition}l infatti può succedere che un secondo processo scriva +alla fine del file fra la \func{lseek} e la \func{write}. In questo caso, come +abbiamo appena visto, il file sarà esteso, ma il primo processo, che avrà la +posizione corrente che aveva impostato con la \func{lseek} che non corrisponde +più alla fine del file, e la sua successiva \func{write} sovrascriverà i dati +del secondo processo. + +Il problema deriva dal fatto che usare due \textit{system call} in successione +non è mai un'operazione atomica dato che il kernel può interrompere +l'esecuzione del processo fra le due. Nel caso specifico il problema è stato +risolto introducendo la modalità di scrittura \itindex{append~mode} in +\textit{append}, attivabile con il flag \const{O\_APPEND}. In questo caso +infatti, come abbiamo illustrato in sez.~\ref{sec:file_open_close}, è il +kernel che aggiorna automaticamente la posizione alla fine del file prima di +effettuare la scrittura, e poi estende il file. Tutto questo avviene +all'interno di una singola \textit{system call}, la \func{write}, che non +essendo interrompibile da un altro processo realizza un'operazione atomica. + + +\subsection{La duplicazione dei file descriptor} \label{sec:file_dup} -Abbiamo già visto in sez.~\ref{sec:file_sharing} come un processo figlio +Abbiamo già visto in sez.~\ref{sec:file_shared_access} come un processo figlio condivida gli stessi file descriptor del padre; è possibile però ottenere un comportamento analogo all'interno di uno stesso processo \textit{duplicando} -un file descriptor. Per far questo si usa la funzione \funcd{dup} il cui -prototipo è: +un file descriptor. Per far questo si usa la funzione di sistema \funcd{dup}, +il cui prototipo è: \begin{funcproto}{ \fhead{unistd.h} @@ -1360,11 +1275,12 @@ prototipo è: La funzione ritorna, come \func{open}, il primo file descriptor libero. Il file descriptor è una copia esatta del precedente ed entrambi possono essere interscambiati nell'uso. Per capire meglio il funzionamento della funzione si -può fare riferimento a fig.~\ref{fig:file_dup}: l'effetto della funzione è -semplicemente quello di copiare il valore nella struttura -\kstruct{file\_struct}, cosicché anche il nuovo file descriptor fa riferimento -alla stessa voce nella \textit{file table}; per questo si dice che il nuovo -file descriptor è \textsl{duplicato}, da cui il nome della funzione. +può fare riferimento a fig.~\ref{fig:file_dup}. L'effetto della funzione è +semplicemente quello di copiare il valore di un certo file descriptor in +un altro all'interno della struttura \kstruct{file\_struct}, cosicché anche +questo faccia riferimento alla stessa voce nella \textit{file table}. Per +questo motivo si dice che il nuovo file descriptor è ``\textsl{duplicato}'', +da cui il nome della funzione. \begin{figure}[!htb] \centering \includegraphics[width=12cm]{img/filedup} @@ -1373,30 +1289,47 @@ file descriptor è \textsl{duplicato}, da cui il nome della funzione. \end{figure} Si noti che per quanto illustrato in fig.~\ref{fig:file_dup} i file descriptor -duplicati condivideranno eventuali lock, \textit{file status flag}, e -posizione corrente. Se ad esempio si esegue una \func{lseek} per modificare la -posizione su uno dei due file descriptor, essa risulterà modificata anche -sull'altro (dato che quello che viene modificato è lo stesso campo nella voce -della \textit{file table} a cui entrambi fanno riferimento). L'unica -differenza fra due file descriptor duplicati è che ciascuno avrà il suo -\textit{file descriptor flag}; a questo proposito va specificato che nel caso -di \func{dup} il flag di \textit{close-on-exec} \itindex{close-on-exec} (vedi -sez.~\ref{sec:proc_exec} e sez.~\ref{sec:file_fcntl}) viene sempre cancellato -nella copia. - -L'uso principale di questa funzione è per la redirezione dell'input e -dell'output fra l'esecuzione di una \func{fork} e la successiva \func{exec}; -diventa così possibile associare un file (o una pipe) allo standard input o -allo standard output (torneremo sull'argomento in sez.~\ref{sec:ipc_pipe_use}, -quando tratteremo le pipe). Per fare questo in genere occorre prima chiudere -il file che si vuole sostituire, cosicché il suo file descriptor possa esser -restituito alla chiamata di \func{dup}, come primo file descriptor -disponibile. - -Dato che questa è l'operazione più comune, è prevista una diversa versione -della funzione, \funcd{dup2}, che permette di specificare esplicitamente -qual è il valore di file descriptor che si vuole avere come duplicato; il suo -prototipo è: +duplicati condivideranno eventuali lock (vedi sez.~\ref{sec:file_locking}), +\itindex{file~status~flag} i flag di stato, e la posizione corrente sul +file. Se ad esempio si esegue una \func{lseek} per modificare la posizione su +uno dei due file descriptor, essa risulterà modificata anche sull'altro, dato +che quello che viene modificato è lo stesso campo nella voce della +\textit{file table} a cui entrambi fanno riferimento. + +L'unica differenza fra due file descriptor duplicati è che ciascuno avrà un +suo \textit{file descriptor flag} indipendente. A questo proposito deve essere +tenuto presente che nel caso in cui si usi \func{dup} per duplicare un file +descriptor, se questo ha il flag di \textit{close-on-exec} +\itindex{close-on-exec} attivo (vedi sez.~\ref{sec:proc_exec} e +sez.~\ref{sec:file_fcntl}), questo verrà cancellato nel file descriptor +restituito come copia. + +L'uso principale di questa funzione è nella shell per la redirezione dei file +standard di tab.~\ref{tab:file_std_files} fra l'esecuzione di una \func{fork} +e la successiva \func{exec}. Diventa così possibile associare un file (o una +pipe) allo \itindex{standard~input} \textit{standard input} o allo +\itindex{standard~output} \textit{standard output} (vedremo un esempio in +sez.~\ref{sec:ipc_pipe_use}, quando tratteremo le pipe). + +Ci si può chiedere perché non sia in questo caso sufficiente chiudere il file +standard che si vuole redirigere e poi aprire direttamente con \func{open} il +file vi si vuole far corrispondere, invece di duplicare un file descriptor che +si è già aperto. La risposta sta nel fatto che il file che si vuole redirigere +non è detto sia un file regolare, ma potrebbe essere, come accennato, anche +una fifo o un socket, oppure potrebbe essere un file associato ad un file +descriptor che si è ereditato già aperto (ad esempio attraverso un'altra +\func{exec}) da un processo antenato del padre, del quale non si conosce il +nome. Operando direttamente con i file descriptor \func{dup} consente di +ignorare le origini del file descriptor che si duplica e funziona in maniera +generica indipendentemente dall'oggetto a cui questo fa riferimento. + +Per ottenere la redirezione occorre pertanto disporre del file descriptor +associato al file che si vuole usare e chiudere il file descriptor che si +vuole sostituire, cosicché esso possa esser restituito alla successiva +chiamata di \func{dup} come primo file descriptor disponibile. Dato che +questa è l'operazione più comune, è prevista un'altra funzione di sistema, +\funcd{dup2}, che permette di specificare esplicitamente qual è il numero di +file descriptor che si vuole ottenere come duplicato; il suo prototipo è: \begin{funcproto}{ \fhead{unistd.h} @@ -1409,31 +1342,173 @@ prototipo è: \begin{errlist} \item[\errcode{EBADF}] \param{oldfd} non è un file aperto o \param{newfd} ha un valore fuori dall'intervallo consentito per i file descriptor. + \item[\errcode{EBUSY}] si è rilevata la possibilità di una + \itindex{race~condition} \textit{race condition}. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. \item[\errcode{EMFILE}] si è raggiunto il numero massimo consentito di file descriptor aperti. \end{errlist} } \end{funcproto} - -\noindent e qualora il file descriptor \param{newfd} sia già aperto (come -avviene ad esempio nel caso della duplicazione di uno dei file standard) esso -sarà prima chiuso e poi duplicato (così che il file duplicato sarà connesso -allo stesso valore per il file descriptor). +La funzione duplica il file descriptor \param{oldfd} su un altro file +descriptor di valore \param{newfd}. Qualora il file descriptor \param{newfd} +sia già aperto, come avviene ad esempio nel caso della duplicazione di uno dei +file standard di tab.~\ref{tab:file_std_files}, esso sarà prima chiuso e poi +duplicato. Se \param{newfd} è uguale a \param{oldfd} la funzione non fa nulla +e si limita a restituire \param{newfd}. + +L'uso di \func{dup2} ha vari vantaggi rispetto alla combinazione di +\func{close} e \func{dup}; anzitutto se \param{oldfd} è uguale \param{newfd} +questo verrebbe chiuso e \func{dup} fallirebbe, ma soprattutto l'operazione è +atomica e consente di evitare una \itindex{race~condition} \textit{race + condition} in cui dopo la chiusura del file si potrebbe avere la ricezione +di un segnale il cui gestore (vedi sez.~\ref{sec:sig_signal_handler}) potrebbe +a sua volta aprire un file, per cui alla fine \func{dup} restituirebbe un file +descriptor diverso da quello voluto. + +Con Linux inoltre la funzione prevede la possibilità di restituire l'errore +\errcode{EBUSY}, che non è previsto dallo standard, quando viene rilevata la +possibilità di una \itindex{race~condition} \textit{race condition} interna in +cui si cerca di duplicare un file descriptor che è stato allocato ma per il +quale non sono state completate le operazioni di apertura.\footnote{la + condizione è abbastanza peculiare e non attinente al tipo di utilizzo + indicato, quanto piuttosto ad un eventuale tentativo di duplicare file + descriptor non ancora aperti, la condizione di errore non è prevista dallo + standard, ma in condizioni simili FreeBSD risponde con un errore di + \errval{EBADF}, mentre OpenBSD elimina la possibilità di una \textit{race + condition} al costo di una perdita di prestazioni.} In tal caso occorre +ritentare l'operazione. La duplicazione dei file descriptor può essere effettuata anche usando la funzione di controllo dei file \func{fcntl} (che esamineremo in sez.~\ref{sec:file_fcntl}) con il parametro \const{F\_DUPFD}. L'operazione ha la sintassi \code{fcntl(oldfd, F\_DUPFD, newfd)} e se si usa 0 come valore per -\param{newfd} diventa equivalente a \func{dup}. +\param{newfd} diventa equivalente a \func{dup}. La sola differenza fra le due +funzioni (a parte la sintassi ed i diversi codici di errore) è che \func{dup2} +chiude il file descriptor \param{newfd} se questo è già aperto, garantendo che +la duplicazione sia effettuata esattamente su di esso, invece \func{fcntl} +restituisce il primo file descriptor libero di valore uguale o maggiore +di \param{newfd}, per cui se \param{newfd} è aperto la duplicazione avverrà su +un altro file descriptor. + +Su Linux inoltre è presente una terza funzione di sistema non +standard,\footnote{la funzione è stata introdotta con il kernel 2.6.27 e resa + disponibile con la \acr{glibc} 2.9.} \funcd{dup3}, che consente di duplicare +un file descriptor reimpostandone i flag, per usarla occorre definire la macro +\macro{\_GNU\_SOURCE} ed il suo prototipo è: + +\begin{funcproto}{ +\fhead{unistd.h} +\fdecl{int dup3(int oldfd, int newfd, int flags)} +\fdesc{Duplica un file descriptor su un altro.} +} + +{La funzione ritorna il nuovo file descriptor in caso di successo e $-1$ per + un errore, nel qual caso \var{errno} assumerà gli stessi valori di + \func{dup2} più \errcode{EINVAL} qualora \param{flags} contenga un valore + non valido o \param{newfd} sia uguale a \param{oldfd}. +} +\end{funcproto} + +La funzione è identica a \func{dup2} ma prevede la possibilità di mantenere il +flag di \textit{close-on-exec} \itindex{close-on-exec} sul nuovo +file descriptor specificando \const{O\_CLOEXEC} in \param{flags} (che è l'unico +flag usabile in questo caso). Inoltre rileva esplicitamente la possibile +coincidenza fra \param{newfd} e \param{oldfd}, fallendo con un errore di +\errval{EINVAL}. + + +\subsection{Le funzioni di sincronizzazione dei dati} +\label{sec:file_sync} + +Come accennato in sez.~\ref{sec:file_open_close} tutte le operazioni di +scrittura sono in genere bufferizzate dal kernel, che provvede ad effettuarle +in maniera asincrona, ad esempio accorpando gli accessi alla stessa zona del +disco, in un secondo tempo rispetto al momento della esecuzione della +\func{write}. -La sola differenza fra le due funzioni\footnote{a parte la sintassi ed i - diversi codici di errore.} è che \func{dup2} chiude il file descriptor -\param{newfd} se questo è già aperto, garantendo che la duplicazione sia -effettuata esattamente su di esso, invece \func{fcntl} restituisce il primo -file descriptor libero di valore uguale o maggiore di \param{newfd} (e se -\param{newfd} è aperto la duplicazione avverrà su un altro file descriptor). +Per questo motivo quando è necessaria una sincronizzazione dei dati il sistema +mette a disposizione delle funzioni che provvedono a forzare lo scarico dei +dati dai buffer del kernel. La prima di queste funzioni di sistema è +\funcd{sync}, il cui prototipo è:\footnote{questo è il prototipo usato a + partire dalla \acr{glibc} 2.2.2 seguendo gli standard, in precedenza la + funzione era definita come \code{int sync(void)} e ritornava sempre $0$.} +\begin{funcproto}{ +\fhead{unistd.h} +\fdecl{void sync(void)} +\fdesc{Sincronizza il buffer della cache dei file col disco.} +} + +{La funzione non ritorna nulla e non prevede condizioni di errore.} +\end{funcproto} + +I vari standard prevedono che la funzione si limiti a far partire le +operazioni, ritornando immediatamente; con Linux fin dal kernel 1.3.20 invece +la funzione aspetta la conclusione delle operazioni di sincronizzazione. Si +tenga presente comunque che questo non dà la garanzia assoluta che i dati +siano integri dopo la chiamata, l'hardware dei dischi è in genere dotato di un +suo meccanismo interno di bufferizzazione che può ritardare ulteriormente la +scrittura effettiva. + +La funzione viene usata dal comando \cmd{sync} quando si vuole forzare +esplicitamente lo scarico dei dati su disco, un tempo era invocata da un +apposito demone di sistema (in genere chiamato \cmd{update}) che eseguiva lo +scarico dei dati ad intervalli di tempo fissi, ad esempio il valore +tradizionale usato da BSD era di 30 secondi, mentre su Linux il valore +utilizzato è di 5 secondi. + +Con le nuove versioni del kernel le operazioni di scarico periodico dei dati +su disco vengono gestite direttamente dal sistema della memoria virtuale, +attraverso opportuni \textit{task} interni al kernel il cui comportamento può +essere controllato attraverso il file \sysctlfile{vm/bdflush}. Per il +significato dei valori si può leggere la documentazione allegata ai sorgenti +del kernel nel file \file{Documentation/sysctl/vm.txt}. Si tenga presente che +la funzione di sistema \funcm{bdflush} che un tempo veniva usata per queste +impostazioni è deprecata e causa semplicemente la stampa di un messaggio nei +log del kernel, pertanto non la prenderemo in esame. + +Quando si vogliono scaricare soltanto i dati di un singolo file (ad esempio +essere sicuri che i dati di un database sono stati registrati su disco) si +possono usare le due funzioni di sistema \funcd{fsync} e \funcd{fdatasync}, i +cui prototipi sono: + +\begin{funcproto}{ +\fhead{unistd.h} +\fdecl{int fsync(int fd)} +\fdesc{Sincronizza dati e metadati di un file.} +\fdecl{int fdatasync(int fd)} +\fdesc{Sincronizza i dati di un file.} +} + +{Le funzioni ritornano $0$ in caso di successo e $-1$ per un errore, nel qual + caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EINVAL}] \param{fd} è un \index{file!speciali} file speciale + che non supporta la sincronizzazione. + \end{errlist} + ed inoltre \errval{EBADF}, \errval{EROFS} e \errval{EIO} nel loro + significato generico.} +\end{funcproto} + +Entrambe le funzioni forzano la sincronizzazione col disco di tutti i dati del +file specificato, ed attendono fino alla conclusione delle operazioni; +\func{fsync} forza anche la sincronizzazione dei meta-dati del file (che +riguardano sia le modifiche alle tabelle di allocazione dei settori, che gli +altri dati contenuti \itindex{inode} nell'\textit{inode} che si leggono con +\func{fstat}, come i tempi del file). + +Si tenga presente che questo non comporta la sincronizzazione della +directory che contiene il file (e scrittura della relativa voce su +disco) che deve essere effettuata esplicitamente.\footnote{in realtà per + il filesystem \acr{ext2}, quando lo si monta con l'opzione \cmd{sync}, + il kernel provvede anche alla sincronizzazione automatica delle voci + delle directory.} + + + +% TODO, aggiungere syncfs, introdotta con il 2.6.39 \subsection{Le funzioni \func{openat}, \func{mkdirat} e affini} @@ -1775,19 +1850,15 @@ per \var{cmd} è riportata di seguito: (il comportamento predefinito) restano aperti. \item[\const{F\_GETFL}] ritorna il valore del \textit{file status flag} in caso di successo o $-1$ in caso di errore; permette cioè di rileggere quei - bit impostati da \func{open} all'apertura del file che vengono memorizzati - (quelli riportati nella prima e terza sezione di - tab.~\ref{tab:file_open_flags}). -% TODO toglire riferimeto a tabella flag e mettere altro - + bit impostati da \func{open} all'apertura del file che vengono memorizzati, + quelli riportati in tab.~\ref{tab:open_access_mode_flag} e + tab.~\ref{tab:open_operation_flag}). \item[\const{F\_SETFL}] imposta il \textit{file status flag} al valore specificato da \param{arg}, ritorna un valore nullo in caso di successo o - $-1$ in caso di errore. Possono essere impostati solo i bit riportati nella - terza sezione di tab.~\ref{tab:file_open_flags}.\footnote{la pagina di - manuale riporta come impostabili solo \const{O\_APPEND}, - \const{O\_NONBLOCK} e \const{O\_ASYNC}.} -% TODO toglire riferimeto a tabella flag e mettere altro - + $-1$ in caso di errore. Possono essere impostati solo i bit riportati in + tab.~\ref{tab:open_operation_flag}.\footnote{la pagina di manuale riporta + come impostabili solo \const{O\_APPEND}, \const{O\_NONBLOCK} e + \const{O\_ASYNC}.} \item[\const{F\_GETLK}] richiede un controllo sul file lock specificato da \param{lock}, sovrascrivendo la struttura da esso puntata con il risultato; ritorna un valore nullo in caso di successo o $-1$ in caso di errore. Questa @@ -2119,8 +2190,9 @@ A parte i dettagli legati alla gestione delle operazioni di lettura e scrittura (sia per quel che riguarda la bufferizzazione, che le formattazioni), i \textit{file stream} restano del tutto equivalenti ai file descriptor (sui quali sono basati), ed in particolare continua a valere quanto -visto in sez.~\ref{sec:file_sharing} a proposito dell'accesso condiviso ed in -sez.~\ref{sec:file_access_control} per il controllo di accesso. +visto in sez.~\ref{sec:file_shared_access} a proposito dell'accesso +concorrente ed in sez.~\ref{sec:file_access_control} per il controllo di +accesso. \itindend{file~stream} @@ -3867,12 +3939,6 @@ con uno dei valori \const{FSETLOCKING\_INTERNAL} o % LocalWords: locking fsetlocking type Virtual operation dentry unistd sys AT % LocalWords: modification hole functions FSETSIG pathname EEXIST CREAT EINTR % LocalWords: EISDIR EFBIG EOVERFLOW ELOOP NOFOLLOW ENODEV ENOENT ENOTDIR fork - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: "gapil" -%%% End: -% LocalWords: ENXIO NONBLOCK WRONLY EPERM NOATIME ETXTBSY EWOULDBLOCK EACCES% LocalWords: EFAULT % LocalWords: EMFILE ENAMETOOLONG ENFILE ENOMEM ENOSPC EROFS exec access RDWR % LocalWords: RDONLY ioctl AND ACCMODE creation Denial Service DoS opendir NFS % LocalWords: SOURCE LARGEFILE BITS NOCTTY TRUNC SHLOCK shared EXLOCK race SGI @@ -3890,4 +3956,12 @@ con uno dei valori \const{FSETLOCKING\_INTERNAL} o % LocalWords: sigaction SIGINFO siginfo SETLEASE lease GETLEASE NOTIFY request % LocalWords: everything framebuffer ENOTTY argp CDROM lsattr chattr magic % LocalWords: number FIOCLEX FIONCLEX FIOASYNC FIONBIO FIOSETOWN FIOGETOWN -% LocalWords: FIONREAD epoll FIOQSIZE side effects SAFE BYCALLER QUERY +% LocalWords: FIONREAD epoll FIOQSIZE side effects SAFE BYCALLER QUERY EACCES +% LocalWords: EBUSY OpenBSD +% LocalWords: ENXIO NONBLOCK WRONLY EPERM NOATIME ETXTBSY EWOULDBLOCK +% LocalWords: EFAULT + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "gapil" +%%% End: diff --git a/img/filedup.dia b/img/filedup.dia index 1477173..7230f1a 100644 Binary files a/img/filedup.dia and b/img/filedup.dia differ diff --git a/img/filemultacc.dia b/img/filemultacc.dia index 5393ff1..2591109 100644 Binary files a/img/filemultacc.dia and b/img/filemultacc.dia differ diff --git a/img/fileshar.dia b/img/fileshar.dia index 077ceb0..01d2f4a 100644 Binary files a/img/fileshar.dia and b/img/fileshar.dia differ diff --git a/ipc.tex b/ipc.tex index 59c7f83..1d08568 100644 --- a/ipc.tex +++ b/ipc.tex @@ -82,7 +82,7 @@ indicano la direzione del flusso dei dati. \end{figure} Chiaramente creare una pipe all'interno di un singolo processo non serve a -niente; se però ricordiamo quanto esposto in sez.~\ref{sec:file_sharing} +niente; se però ricordiamo quanto esposto in sez.~\ref{sec:file_shared_access} riguardo al comportamento dei file descriptor nei processi figli, è immediato capire come una pipe possa diventare un meccanismo di intercomunicazione. Un processo figlio infatti condivide gli stessi file descriptor del padre, diff --git a/prochand.tex b/prochand.tex index 182ad5f..fa6e1d5 100644 --- a/prochand.tex +++ b/prochand.tex @@ -549,10 +549,10 @@ ultimo) troveremo anche l'output completo del padre. L'esempio ci mostra un altro aspetto fondamentale dell'interazione con i file, valido anche per l'esempio precedente, ma meno evidente: il fatto cioè che non solo processi diversi possono scrivere in contemporanea sullo stesso file -(l'argomento della condivisione dei file è trattato in dettaglio in -sez.~\ref{sec:file_sharing}), ma anche che, a differenza di quanto avviene per -le variabili in memoria, la posizione corrente sul file è condivisa fra il -padre e tutti i processi figli. +(l'argomento dell'accesso concorrente ai file è trattato in dettaglio in +sez.~\ref{sec:file_shared_access}), ma anche che, a differenza di quanto +avviene per le variabili in memoria, la posizione corrente sul file è +condivisa fra il padre e tutti i processi figli. Quello che succede è che quando lo \textit{standard output}\footnote{si chiama così il file su cui un programma scrive i suoi dati in uscita, tratteremo @@ -563,8 +563,9 @@ processi figli tutti i \textit{file descriptor} (vedi sez.~\ref{sec:file_fd}) dei file aperti nel processo padre (allo stesso modo in cui lo fa la funzione \func{dup}, trattata in sez.~\ref{sec:file_dup}), il che comporta che padre e figli condividono le stesse voci della \itindex{file~table} \textit{file - table} (tratteremo in dettagli questi termini in -sez.~\ref{sec:file_sharing}) fra cui c'è anche la posizione corrente nel file. + table} (tratteremo in dettaglio questi termini in +sez.~\ref{sec:file_shared_access}) fra cui c'è anche la posizione corrente nel +file. In questo modo se un processo scrive su un file aggiornerà la posizione corrente sulla \itindex{file~table} \textit{file table}, e tutti gli altri @@ -4148,11 +4149,11 @@ Nel caso dell'interazione fra processi la situazione è molto più semplice, ed occorre preoccuparsi della atomicità delle operazioni solo quando si ha a che fare con meccanismi di intercomunicazione (che esamineremo in dettaglio in cap.~\ref{cha:IPC}) o nelle operazioni con i file (vedremo alcuni esempi in -sez.~\ref{sec:file_atomic}). In questi casi in genere l'uso delle appropriate -funzioni di libreria per compiere le operazioni necessarie è garanzia -sufficiente di atomicità in quanto le \textit{system call} con cui esse sono -realizzate non possono essere interrotte (o subire interferenze pericolose) da -altri processi. +sez.~\ref{sec:file_shared_access}). In questi casi in genere l'uso delle +appropriate funzioni di libreria per compiere le operazioni necessarie è +garanzia sufficiente di atomicità in quanto le \textit{system call} con cui +esse sono realizzate non possono essere interrotte (o subire interferenze +pericolose) da altri processi. Nel caso dei segnali invece la situazione è molto più delicata, in quanto lo stesso processo, e pure alcune \textit{system call}, possono essere interrotti diff --git a/tcpsock.tex b/tcpsock.tex index fb63b0b..4c60c0a 100644 --- a/tcpsock.tex +++ b/tcpsock.tex @@ -1241,9 +1241,9 @@ Come per tutti i file descriptor anche per i socket viene mantenuto un numero di riferimenti, per cui se più di un processo ha lo stesso socket aperto l'emissione del FIN e la sequenza di chiusura di TCP non viene innescata fintanto che il numero di riferimenti non si annulla, questo si applica, come -visto in sez.~\ref{sec:file_sharing}, sia ai file descriptor duplicati che a -quelli ereditati dagli eventuali processi figli, ed è il comportamento che ci -si aspetta in una qualunque applicazione client/server. +visto in sez.~\ref{sec:file_shared_access}, sia ai file descriptor duplicati +che a quelli ereditati dagli eventuali processi figli, ed è il comportamento +che ci si aspetta in una qualunque applicazione client/server. Per attivare immediatamente l'emissione del FIN e la sequenza di chiusura descritta in sez.~\ref{sec:TCP_conn_term}, si può invece usare la funzione