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
-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
-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}
 
-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}
 
-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}}
@@ -51,11 +77,7 @@ L'interfaccia standard unix per l'input/output sui file 
 \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}
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
-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
@@ -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*}
-\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
@@ -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 maschera dei segnali.
+\item la maschera dei segnali bloccati e le azioni installate.
 \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*}
-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}. 
@@ -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 gli allarmi pendenti, che per il figlio vengono cancellati.
+\item gli allarmi ed i segnali pendenti, che per il figlio vengono cancellati.
 \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
-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}
 
+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
-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.