From: Simone Piccardi Date: Wed, 31 Oct 2001 00:43:26 +0000 (+0000) Subject: Iniziato l'I/O sui file X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=commitdiff_plain;h=33a26fb169d5a3e459b816cb79eb7956e8434ffb;p=gapil.git Iniziato l'I/O sui file --- diff --git a/fileunix.tex b/fileunix.tex index 4964298..772e13e 100644 --- a/fileunix.tex +++ b/fileunix.tex @@ -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} diff --git a/prochand.tex b/prochand.tex index c2af15d..97b9c5e 100644 --- a/prochand.tex +++ b/prochand.tex @@ -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.