Iniziato l'I/O sui file
authorSimone Piccardi <piccardi@gnulinux.it>
Wed, 31 Oct 2001 00:43:26 +0000 (00:43 +0000)
committerSimone Piccardi <piccardi@gnulinux.it>
Wed, 31 Oct 2001 00:43:26 +0000 (00:43 +0000)
fileunix.tex
prochand.tex

index 4964298bb9861361a2af22366a0e2f86c242d742..772e13ec2746de1f0e063219f8c7bf427f858611 100644 (file)
@@ -1,12 +1,11 @@
-\chapter{I files: l'interfaccia I/O di unix}
+\chapter{L'interfaccia unix di I/O con i file}
 \label{cha:file_unix_interface}
 
 Esamineremo in questo capitolo la prima delle due interfacce di programmazione
 \label{cha:file_unix_interface}
 
 Esamineremo in questo capitolo la prima delle due interfacce di programmazione
-per i file, quella dei file descriptor, nativa di unix. Questa è 
-l'interfaccia di basso livello, che non prevede funzioni evolute come la
-bufferizzazione o funzioni di lettura o scrittura formattata, su cui è
-costruita anche l'interfaccia standard dei file definta dallo standard ANSI
-C. 
+per i file, quella dei file descriptor, nativa di unix. Questa è l'interfaccia
+di basso livello, che non prevede funzioni evolute come la bufferizzazione o
+funzioni di lettura o scrittura formattata, ma è su questa che è costruita
+anche l'interfaccia standard dei file definita dallo standard ANSI C.
 
 
 
 
 
 
@@ -14,23 +13,50 @@ C.
 \label{sec:file_base_arch}
 
 Iniziamo la trattazione con una panoramica sull'architettura base della
 \label{sec:file_base_arch}
 
 Iniziamo la trattazione con una panoramica sull'architettura base della
-intefaccuia dei file descriptor. Esamineremo in questa sezione 
+intefaccia dei file descriptor. Esamineremo in questa sezione la struttura
+base dell'interfaccia con i file di unix, e le modalità con cui i processi
+ed il kernel interagiscono per operare sui file. 
 
 
 
 
-\subsection{I file descriptors}
+\subsection{L'architettura dei \textit{file descriptors}}
 \label{sec:file_fd}
 
 \label{sec:file_fd}
 
-Per poter accedere al contenuto dei file occorre anzitutto aprirlo. Questo
-crea un canale di comunicazione che permette di eseguire una serie di
-operazioni. Una volta terminate le operazioni, il file dovrà essere chiuso, e
-questo chiuderà il canale di comunicazione impedendo ogni ulteriore
-operazione.
+Per poter accedere al contenuto di un file occorre creare un canale di
+comunicazione con il kernel che renda possibile operare su di esso (si ricordi
+quanto visto in \secref{sec:file_vfs_work}), questo si fa aprendo il file con
+la funzione \func{open} che provvederà a localizzare l'inode del file e
+inizializzare le funzioni che il VFS mette a disposizione (riportate in
+\tabref{tab:file_file_operations}). Una volta terminate le operazioni, il file
+dovrà essere chiuso, e questo chiuderà il canale di comunicazione impedendo
+ogni ulteriore operazione.
+
+Per ciascun file aperto nel sistema il kernel mantiene voce nella tabella dei
+file; ciascuna voce di questa tabella contiene:
+\begin{itemize}
+\item lo stato del file (lettura, scrittura, append, etc.).
+\item il valore della posizione corrente (l'\textit{offset}).
+\item un puntatore al 
+\end{itemize}
+
+
+All'interno di ogni processo i file aperti sono identificati da un intero non
+negativo, chiamato appunto \textit{file descriptors}; ciascun processo ha una
+tabella dei file aperti, in cui 
+
+
+
+
+\subsection{La condivisione dei files}
+\label{sec:file_sharing}
+
+
 
 \section{Le funzioni base}
 \label{sec:file_base_func}
 
 
 \section{Le funzioni base}
 \label{sec:file_base_func}
 
-L'interfaccia standard unix per l'input/output sui file è su cinque funzioni
-\texttt{open}, \texttt{read}, \texttt{write}, \texttt{lseek}, \texttt{close}; 
+L'interfaccia standard unix per l'input/output sui file è basata su cinque
+funzioni fondamentali \func{open}, \func{read}, \func{write},
+\func{lseek} e \func{close};
 
 
 \subsection{La funzione \texttt{open}}
 
 
 \subsection{La funzione \texttt{open}}
@@ -51,11 +77,7 @@ L'interfaccia standard unix per l'input/output sui file 
 \subsection{La funzione \texttt{write}}
 \label{sec:file_write}
 
 \subsection{La funzione \texttt{write}}
 \label{sec:file_write}
 
-\section{La condivisione dei files}
-\label{sec:file_sharing}
-
-
-\subsection{Operazioni atomiche}
+\subsection{Operazioni atomiche coi file}
 \label{sec:file_atomic}
 
 \section{Funzioni avanzate}
 \label{sec:file_atomic}
 
 \section{Funzioni avanzate}
index c2af15d637ce024f93bd9c344060da9445b84e20..97b9c5e7d6d35521cf0adb178b303a200e2ccc4a 100644 (file)
@@ -500,10 +500,9 @@ Quello che succede 
 lo stesso avviene anche per tutti i figli; la funzione \func{fork} infatti ha
 la caratteristica di duplicare (allo stesso modo in cui lo fa la funzione
 \func{dup}, trattata in \secref{sec:file_dup}) nei figli tutti i file
 lo stesso avviene anche per tutti i figli; la funzione \func{fork} infatti ha
 la caratteristica di duplicare (allo stesso modo in cui lo fa la funzione
 \func{dup}, trattata in \secref{sec:file_dup}) nei figli tutti i file
-descriptor aperti nel padre, il che comporta che padre e figli condividono
-le stesse voci della file table (per la spiegazione di questi termini si veda
-\secref{sec:file_sharing} e referenza a figura da fare) e quindi anche
-l'offset corrente nel file.
+descriptor aperti nel padre, il che comporta che padre e figli condividono le
+stesse voci della file table (per la spiegazione di questi termini si veda
+\secref{sec:file_sharing}) e quindi anche l'offset corrente nel file.
 
 In questo modo se un processo scrive sul file aggiornerà l'offset sulla file
 table, e tutti gli altri processi che condividono la file table vedranno il
 
 In questo modo se un processo scrive sul file aggiornerà l'offset sulla file
 table, e tutti gli altri processi che condividono la file table vedranno il
@@ -540,8 +539,7 @@ Oltre ai file aperti i processi figli ereditano dal padre una serie di altre
 proprietà; la lista dettagliata delle proprietà che padre e figlio hanno in
 comune dopo l'esecuzione di una \func{fork} è la seguente:
 \begin{itemize*}
 proprietà; la lista dettagliata delle proprietà che padre e figlio hanno in
 comune dopo l'esecuzione di una \func{fork} è la seguente:
 \begin{itemize*}
-\item i file aperti (e gli eventuali flag di \textit{close-on-exec} se
-  settati).
+\item i file aperti e gli eventuali flag di \textit{close-on-exec} se settati.
 \item gli identificatori per il controllo di accesso: il \textit{real user
     id}, il \textit{real group id}, l'\textit{effective user id},
   l'\textit{effective group id} e i \textit{supplementary group id} (vedi
 \item gli identificatori per il controllo di accesso: il \textit{real user
     id}, il \textit{real group id}, l'\textit{effective user id},
   l'\textit{effective group id} e i \textit{supplementary group id} (vedi
@@ -553,12 +551,12 @@ comune dopo l'esecuzione di una \func{fork} 
 \item la directory di lavoro e la directory radice (vedi
   \secref{sec:file_work_dir}).
 \item la maschera dei permessi di creazione (vedi \secref{sec:file_umask}).
 \item la directory di lavoro e la directory radice (vedi
   \secref{sec:file_work_dir}).
 \item la maschera dei permessi di creazione (vedi \secref{sec:file_umask}).
-\item la maschera dei segnali.
+\item la maschera dei segnali bloccati e le azioni installate.
 \item i segmenti di memoria condivisa agganciati al processo. 
 \item i segmenti di memoria condivisa agganciati al processo. 
-\item i limiti sulle risorse
+\item i limiti sulle risorse.
 \item le variabili di ambiente (vedi \secref{sec:proc_environ}).
 \end{itemize*}
 \item le variabili di ambiente (vedi \secref{sec:proc_environ}).
 \end{itemize*}
-le differenze fra padree figlio dopo la \func{fork} invece sono:
+le differenze fra padre e figlio dopo la \func{fork} invece sono:
 \begin{itemize*}
 \item il valore di ritorno di \func{fork}.
 \item il \textit{process id}. 
 \begin{itemize*}
 \item il valore di ritorno di \func{fork}.
 \item il \textit{process id}. 
@@ -567,7 +565,7 @@ le differenze fra padree figlio dopo la \func{fork} invece sono:
 \item i valori dei tempi di esecuzione (\var{tms\_utime}, \var{tms\_stime},
   \var{tms\_cutime}, \var{tms\_uetime}) che nel figlio sono posti a zero.
 \item i \textit{file lock}, che non vengono ereditati dal figlio.
 \item i valori dei tempi di esecuzione (\var{tms\_utime}, \var{tms\_stime},
   \var{tms\_cutime}, \var{tms\_uetime}) che nel figlio sono posti a zero.
 \item i \textit{file lock}, che non vengono ereditati dal figlio.
-\item gli allarmi pendenti, che per il figlio vengono cancellati.
+\item gli allarmi ed i segnali pendenti, che per il figlio vengono cancellati.
 \end{itemize*}
 
 
 \end{itemize*}
 
 
@@ -1667,32 +1665,103 @@ esistono quando si ha a che fare con un sistema in cui viene eseguito un solo
 programma alla volta. 
 
 Pur non essendo tutto questo direttamente legato alla modalità specifica in
 programma alla volta. 
 
 Pur non essendo tutto questo direttamente legato alla modalità specifica in
-cui il multitasking è implementato in un sistema unix-like, siccome la
-gestione dei processi è stata affrontata in questo capitolo, tratteremo in
-questa sezione conclusiva anche queste problematiche, esaminandone le
-caratteristiche fondamentali e le modalità con cui si affrontano.
-
-
-\subsection{Le funzioni rientranti}
-\label{sec:proc_reentrant}
+cui il multitasking è implementato in un sistema unix-like, né al solo
+concetto di multitasking (le stesse problematiche si presentano ad esempio
+nella gestione degli interrupt hardware), in questa sezione conclusiva del
+capitolo in cui abbiamo affrontato la gestione dei processi, introdurremo
+sinteticamente queste problematiche, che ritroveremo a più riprese in capitoli
+successivi, con una breve definizione della terminologia e delle loro
+caratteristiche di fondo.
 
 
 \subsection{Le operazioni atomiche}
 \label{sec:proc_atom_oper}
 
 
 
 \subsection{Le operazioni atomiche}
 \label{sec:proc_atom_oper}
 
+La nozione di \textsl{operazione atomica} deriva dal significato greco della
+parola atomo, cioè indivisibile; si dice infatti che una operazione è atomica
+quando si ha la certezza che, qualora essa venga effettuata, tutti i passaggi
+che devono essere compiuti per realizzarla verranno eseguiti senza possibilità
+di interruzione in una fase intermedia.
+
+In un ambiente multitasking il concetto è esseziale, dato che un processo può
+essere interrotto in qualunque momento dal kernel che mette in esecuzione un
+altro processo o dalla ricezione di un segnale; occorre pertanto essere
+accorti nei confronti delle possibili \textit{race condition} (vedi
+\secref{sec:proc_race_cond}) derivanti da operazioni interrotte in una fase in
+cui non erano ancora state completate.
+
+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
+\capref{cha:ipc}) o nella operazioni con i file (vedremo alcuni esempi in
+\secref{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 system call con cui esse sono realizzate
+non possono essere interrotte (o subire interferenze pericolose) da altri
+processi.
 
 
-\subsection{Le \textit{race condition}}
+Nel caso dei segnali invece la situazione è molto più delicata, in quanto lo
+stesso processo può essere interrotto in qualunque momento, e le operazioni di
+un eventuale \textit{signal handler} saranno compiute nello stesso spazio di
+indirizzi. Per questo anche solo il solo accesso o l'assegnazione di una
+variabile possono non essere più operazioni atomiche (torneremo su questi
+aspetti in \secref{sec:sign_xxx}).
+
+In questo caso il sistema provvede un tipo di dato, il \type{sig\_atomic\_t},
+il cui accesso è assicurato essere atomico.  In pratica comunque si può
+assumere che in ogni piattaforma su cui è implementato Linux il tipo
+\type{int} (e gli altri interi di dimensione inferiore) ed i puntatori sono
+atomici. Non è affatto detto che lo stesso valga per interi di dimensioni
+maggiori (in cui l'accesso può comportare più istruzioni in assembler) o per
+le strutture. In questi casi è anche opportuno marcare come \type{volatile} le
+variabili che possono essere interessate ad accesso condiviso, onde evitare
+problemi con le ottimizzazioni del codice.
+
+
+\subsection{Le \textit{race condition} e i \textit{deadlock}}
 \label{sec:proc_race_cond}
 
 Si definisce una \textit{race condition} il caso in cui diversi processi
 stanno cercando di fare qualcosa con una risorsa comune ed il risultato finale
 viene a dipendere dall'ordine di esecuzione dei medesimi. Ovviamente dato che
 \label{sec:proc_race_cond}
 
 Si definisce una \textit{race condition} il caso in cui diversi processi
 stanno cercando di fare qualcosa con una risorsa comune ed il risultato finale
 viene a dipendere dall'ordine di esecuzione dei medesimi. Ovviamente dato che
-l'ordine di esecuzione di un processo, senza appositi meccanismi di
-sincronizzazione, non è assolutamente prevedibile, queste situazioni sono
-fonti di errori molto subdoli, che possono verificarsi solo in condizioni
-particolari e quindi difficilmente riproducibili.
+l'ordine di esecuzione di un processo rispetto agli altri, senza appositi
+meccanismi di sincronizzazione, non è assolutamente prevedibile, queste
+situazioni sono fonti di errori molto subdoli, che possono verificarsi solo in
+condizioni particolari e quindi difficilmente riproducibili.
+
+Casi tipici di \textit{race condition} si hanno quando diversi processi
+accedono allo stesso file, o nell'accesso a meccanismi di intercomunicazione
+come la memoria condivisa. In questi casi, se non si dispone della possibilità
+di eseguire atomicamente le operazioni necessarie, occorre che le risorse
+condivise siano opportunamente protette da meccanismi di sincronizzazione
+(torneremo su queste problematiche di questo tipo in \secref{sec:ipc_semaph}).
+
+Un caso particolare di \textit{race condition} sono poi i cosidetti
+\textit{deadlock}; l'esempio tipico è quello di un flag di ``occupazione'' che
+viene rilasciato da un evento asincrono fra il controllo (in cui viene trovato
+occupato) e la successiva messa in attesa, attesa che a questo punto diventerà
+perpetua (da cui il nome di \textit{deadlock}) in quanto l'evento di sblocco
+di questa è stato perso.
+
 
 
+\subsection{Le funzioni rientranti}
+\label{sec:proc_reentrant}
 
 
-\subsection{I \textit{deadlock}}
-\label{sec:proc_deadlock}
+Si dice rientrante una funzione che può essere interrotta in qualunque momento
+ed essere chiamata da capo (da questo il nome) da un altro filone di
+esecuzione (thread e manipolatori di segnali sono i casi in cui occorre
+prestare attenzione a questa problematica) senza che questo comporti nessun
+problema.
+
+In genere una funzione non è rientrante se opera direttamente su memoria che
+non è nello stack. Ad esempio una funzione non è rientrante se usa una
+variabile globale o statica od un oggetto allocato dinamicamente che trova da
+sola: due chiamate alla stessa funzione interferiranno.  Una funzione può non
+essere rientrante se usa e modifica un oggetto che le viene fornito dal
+chiamante: due chiamate possono interferire se viene passato lo stesso
+oggetto. 
+
+Le glibc mettono a disposizione due macro di compilatore \macro{_REENTRANT} e
+\macro{_THREAD_SAFE} per assicurare che siano usate delle versioni rientranti
+delle funzioni di libreria.