X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=fileunix.tex;h=5508c50c9c882f4ced4136c647ca53d63404a1f9;hp=772e13ec2746de1f0e063219f8c7bf427f858611;hb=25de957ddf731370bec1eb74b13cf35aa7886d1b;hpb=33a26fb169d5a3e459b816cb79eb7956e8434ffb diff --git a/fileunix.tex b/fileunix.tex index 772e13e..5508c50 100644 --- a/fileunix.tex +++ b/fileunix.tex @@ -1,97 +1,1212 @@ -\chapter{L'interfaccia unix di I/O con i file} +%% fileunix.tex +%% +%% Copyright (C) 2000-2002 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 "Prefazione", +%% 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{I file: l'interfaccia standard Unix} \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, ma è su questa che è costruita -anche l'interfaccia standard dei file definita dallo standard ANSI C. +per i file, quella dei \textit{file descriptor}\index{file!descriptor}, +nativa di Unix. Questa è l'interfaccia di basso livello provvista direttamente +dalle system call, che non prevede funzionalità evolute come la +bufferizzazione o funzioni di lettura o scrittura formattata, e sulla quale è +costruita anche l'interfaccia definita dallo standard ANSI C che affronteremo +al \capref{cha:files_std_interface}. \section{L'architettura di base} \label{sec:file_base_arch} -Iniziamo la trattazione con una panoramica sull'architettura base della -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. +In questa sezione faremo una breve introduzione sull'architettura su cui è +basata dell'interfaccia dei \textit{file descriptor}, che, sia pure con +differenze nella realizzazione pratica, resta sostanzialmente la stessa in +tutte le implementazione di un sistema unix-like. -\subsection{L'architettura dei \textit{file descriptors}} +\subsection{L'architettura dei \textit{file descriptor}} \label{sec:file_fd} -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 +\index{file!descriptor|(} 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\index{inode} del file e inizializzare i puntatori che rendono +disponibili 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 descriptor}. +Quando un file viene aperto la funzione \func{open} restituisce questo numero, +tutte le ulteriori operazioni saranno compiute specificando questo stesso +valore come argomento alle varie funzioni dell'interfaccia. +Per capire come funziona il meccanismo occorre spiegare a grandi linee come è +che il kernel gestisce l'interazione fra processi e file. Il kernel mantiene +sempre un elenco dei processi attivi nella cosiddetta \textit{process table} +ed un elenco dei file aperti nella \textit{file table}. -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 +La \textit{process table} è una tabella che contiene una voce per ciascun +processo attivo nel sistema. In Linux ciascuna voce è costituita da una +struttura di tipo \var{task\_struct} nella quale sono raccolte tutte le +informazioni relative al processo; fra queste informazioni c'è anche il +puntatore ad una ulteriore struttura di tipo \var{files\_struct}, in cui sono +contenute le informazioni relative ai file che il processo ha aperto, ed in +particolare: +\begin{itemize*} +\item i flag relativi ai file descriptor. +\item il numero di file aperti. +\item una tabella che contiene un puntatore alla relativa voce nella + \textit{file table} per ogni file aperto. +\end{itemize*} +il \textit{file descriptor} in sostanza è l'intero positivo che indicizza +quest'ultima tabella. +La \textit{file table} è una tabella che contiene una voce per ciascun file +che è stato aperto nel sistema. In Linux è costituita da strutture di tipo +\var{file}; in ciascuna di esse sono tenute varie informazioni relative al +file, fra cui: +\begin{itemize*} +\item lo stato del file (nel campo \var{f\_flags}). +\item il valore della posizione corrente (l'\textit{offset}) nel file (nel + campo \var{f\_pos}). +\item un puntatore all'inode\index{inode}\footnote{nel kernel 2.4.x si è in + realtà passati ad un puntatore ad una struttura \var{dentry} che punta a + sua volta all'inode\index{inode} passando per la nuova struttura del VFS.} + del file. +%\item un puntatore alla tabella delle funzioni \footnote{la struttura +% \var{f\_op} descritta in \secref{sec:file_vfs_work}} che si possono usare +% sul file. +\end{itemize*} + +In \figref{fig:file_proc_file} si è riportato uno schema in cui è illustrata +questa architettura, ed in cui si sono evidenziate le interrelazioni fra le +varie strutture di dati sulla quale essa è basata. +\begin{figure}[htb] + \centering + \includegraphics[width=13cm]{img/procfile} + \caption{Schema della architettura dell'accesso ai file attraverso + l'interfaccia dei \textit{file descriptor}.} + \label{fig:file_proc_file} +\end{figure} +Ritorneremo su questo schema più volte, dato che esso è fondamentale per +capire i dettagli del funzionamento dell'interfaccia dei \textit{file + descriptor}. +\index{file!descriptor|)} -\subsection{La condivisione dei files} -\label{sec:file_sharing} + +\subsection{I file standard} +\label{sec:file_std_descr} + +Come accennato i \textit{file descriptor} non sono altro che un indice nella +tabella dei file aperti di ciascun processo; per questo motivo essi vengono +assegnati in successione tutte le volte che si apre un nuovo file (se non ne è +stato chiuso nessuno in precedenza). + +In tutti i sistemi unix-like esiste una convenzione generale per cui ogni +processo viene lanciato con almeno tre file aperti. Questi, per quanto appena +detto, avranno come \textit{file descriptor}\index{file!descriptor} i valori +0, 1 e 2. Benché questa sia soltanto una convenzione, essa è seguita dalla +gran parte delle applicazioni, e non aderirvi potrebbe portare a gravi +problemi di interoperabilità. + +Il primo file è sempre associato a quello che viene chiamato \textit{standard + input}. È cioè il file da cui il processo si aspetta di ricevere i dati in +ingresso (nel caso della shell, è associato all'ingresso dal terminale, e +quindi alla lettura della tastiera). Il secondo file è il cosiddetto +\textit{standard output}, cioè il file su cui ci si aspetta debbano essere +inviati i dati in uscita (sempre nel caso della shell, è associato all'uscita +del terminale, e quindi alla scrittura sullo schermo). Il terzo è lo +\textit{standard error}, su cui viene inviato l'output relativo agli errori, +ed è anch'esso associato all'uscita del termininale. Lo standard POSIX.1 +provvede tre costanti simboliche, definite nell'header \file{unistd.h}, al +posto di questi valori numerici: +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Costante} & \textbf{Significato} \\ + \hline + \hline + \const{STDIN\_FILENO} & \textit{file descriptor} dello \textit{standard + input} \\ + \const{STDOUT\_FILENO} & \textit{file descriptor} dello \textit{standard + output} \\ + \const{STDERR\_FILENO} & \textit{file descriptor} dello \textit{standard + error}\\ + \hline + \end{tabular} + \caption{Costanti definite in \file{unistd.h} per i file standard aperti + alla creazione di ogni processo.} + \label{tab:file_std_files} +\end{table} + +In \figref{tab:file_std_files} si è utilizzata questa situazione come esempio, +facendo riferimento ad un programma in cui lo \textit{standard input} è +associato ad un file mentre lo \textit{standard output} e lo \textit{standard + error} sono entrambi associati ad un altro file (e quindi utilizzano lo +stesso inode\index{inode}). + +Nelle vecchie versioni di Unix (ed anche in Linux fino al kernel 2.0.x) il +numero di file aperti era anche soggetto ad un limite massimo dato dalle +dimensioni del vettore di puntatori con cui era realizzata la tabella dei file +descriptor dentro \var{file\_struct}; questo limite intrinseco nei kernel più +recenti non sussiste più, dato che si è passati da un vettore ad una lista, ma +restano i limiti imposti dall'amministratore (vedi \secref{sec:sys_limits}). \section{Le funzioni base} \label{sec:file_base_func} -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}; +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}, usate rispettivamente per aprire, leggere, scrivere, spostarsi e +chiudere un file. + +La gran parte delle operazioni sui file si effettua attraverso queste cinque +funzioni, esse vengono chiamate anche funzioni di I/O non bufferizzato dato +che effettuano le operazioni di lettura e scrittura usando direttamente le +system call del kernel. -\subsection{La funzione \texttt{open}} +\subsection{La funzione \func{open}} \label{sec:file_open} -\subsection{La funzione \texttt{creat}} -\label{sec:file_creat} +La funzione \func{open} è la funzione fondamentale per accedere ai file, ed è +quella che crea l'associazione fra un pathname ed un file descriptor, il suo +prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{sys/stat.h} + \headdecl{fcntl.h} + \funcdecl{int open(const char *pathname, int flags)} + \funcdecl{int open(const char *pathname, int flags, mode\_t mode)} + Apre il file indicato da \var{pathname} nella modalità indicata da + \var{flags}, e, nel caso il file sia creato, con gli eventuali permessi + specificati da \var{mode}. + + \bodydesc{La funzione ritorna il file descriptor in caso di successo e -1 in + caso di errore. In questo caso la variabile \var{errno} assumerà uno dei + valori: + \begin{errlist} + \item[\errcode{EEXIST}] \var{pathname} esiste e si è specificato + \const{O\_CREAT} e \const{O\_EXCL}. + \item[\errcode{EISDIR}] \var{pathname} indica una directory e si è tentato + l'accesso in scrittura. + \item[\errcode{ENOTDIR}] si è specificato \const{O\_DIRECTORY} e + \var{pathname} non è una directory. + \item[\errcode{ENXIO}] si sono impostati \const{O\_NOBLOCK} o + \const{O\_WRONLY} ed il file è una fifo che non viene letta da nessun + processo o \var{pathname} è un file di dispositivo ma il dispositivo è + assente. + \item[\errcode{ENODEV}] \var{pathname} si riferisce a un file di dispositivo + che non esiste. + \item[\errcode{ETXTBSY}] si è cercato di accedere in scrittura all'immagine + di un programma in esecuzione. + \item[\errcode{ELOOP}] si sono incontrati troppi link simbolici nel risolvere + pathname o si è indicato \const{O\_NOFOLLOW} e \var{pathname} è un link + simbolico. + \end{errlist} + ed inoltre \errval{EACCES}, \errval{ENAMETOOLONG}, \errval{ENOENT}, + \errval{EROFS}, \errval{EFAULT}, \errval{ENOSPC}, \errval{ENOMEM}, + \errval{EMFILE} e \errval{ENFILE}.} +\end{functions} + +La funzione apre il file, usando il primo file descriptor libero, e crea +l'opportuna voce (cioè la struttura \var{file}) nella file table. Viene usato +sempre il file descriptor con il valore più basso. + +\begin{table}[!htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|p{12cm}|} + \hline + \textbf{Flag} & \textbf{Descrizione} \\ + \hline + \hline % modalità di accesso al file + \const{O\_RDONLY} & apre il file in sola lettura. \\ + \const{O\_WRONLY} & apre il file in sola scrittura. \\ + \const{O\_RDWR} & apre il file in lettura/scrittura. \\ + \hline % modalità di apertura del file + \hline + \const{O\_CREAT} & se il file non esiste verrà creato, con le regole di + titolarità del file viste in \secref{sec:file_ownership}. Il parametro + \var{mode} deve essere specificato. \\ + \const{O\_EXCL} & usato in congiunzione con \const{O\_CREAT} fa sì che + l'esistenza del file diventi un errore\protect\footnotemark\ che fa fallire + \func{open} con \errcode{EEXIST}. \\ + \const{O\_NONBLOCK} & apre il file in modalità non bloccante. Questo + valore specifica anche una modalità di operazione (vedi sotto), e + comporta che \func{open} ritorni immediatamente (l'opzione ha senso + solo per le fifo, torneremo questo in \secref{sec:ipc_named_pipe}). \\ + \const{O\_NOCTTY} & se \var{pathname} si riferisce ad un device di + terminale, questo non diventerà il terminale di controllo, anche se il + processo non ne ha ancora uno (si veda \secref{sec:sess_ctrl_term}). \\ + \const{O\_SHLOCK} & opzione di BSD, acquisisce uno shared lock (vedi + \secref{sec:file_locking}) sul file. Non è disponibile in Linux. \\ + \const{O\_EXLOCK} & opzione di BSD, acquisisce uno lock esclusivo (vedi + \secref{sec:file_locking}) sul file. Non è disponibile in Linux. \\ + \const{O\_TRUNC} & se il file esiste ed è un file di dati e la modalità di + apertura consente la scrittura, allora la sua lunghezza verrà troncata a + zero. Se il file è un terminale o una fifo il flag verrà ignorato, negli + altri casi il comportamento non è specificato. \\ + \const{O\_NOFOLLOW} & se \var{pathname} è un link simbolico la chiamata + fallisce. Questa è un'estensione BSD aggiunta in Linux dal kernel 2.1.126. + Nelle versioni precedenti i link simbolici sono sempre seguiti, e questa + opzione è ignorata. \\ + \const{O\_DIRECTORY} & se \var{pathname} non è una directory la chiamata + fallisce. Questo flag è specifico di Linux ed è stato introdotto con il + kernel 2.1.126 per evitare dei + \textit{DoS}\index{DoS}\protect\footnotemark\ quando + \func{opendir} viene chiamata su una + fifo o su un device di unità a nastri, non deve essere utilizzato al di + fuori dell'implementazione di \func{opendir}. \\ + \const{O\_LARGEFILE} & nel caso di sistemi a 32 bit che supportano file di + grandi dimensioni consente di aprire file le cui dimensioni non possono + essere rappresentate da numeri a 31 bit. \\ + \hline + \hline % modalità di operazione col file + \const{O\_APPEND} & il file viene aperto in append mode. Prima di ciascuna + scrittura la posizione corrente viene sempre impostata alla fine del + file. Può causare corruzione del file con NFS se più di un processo scrive + allo stesso tempo.\footnotemark\\ + \const{O\_NONBLOCK} & il file viene aperto in modalità non bloccante per + le operazioni di I/O (che tratteremo in \secref{sec:file_noblocking}): + questo significa il fallimento di \func{read} in assenza di dati da + leggere e quello di \func{write} in caso di impossibilità di scrivere + immediatamente. Questa modalità ha senso solo per le fifo e per alcuni + file di dispositivo. \\ + \const{O\_NDELAY} & in Linux\footnotemark\ è sinonimo di + \const{O\_NONBLOCK}.\\ + \const{O\_ASYNC} & apre il file per l'I/O in modalità + asincrona (vedi \secref{sec:file_asyncronous_io}). Quando è impostato viene + generato il segnale \const{SIGIO} tutte le volte che sono disponibili + dati in input sul file. \\ + \const{O\_SYNC} & apre il file per l'input/output sincrono, ogni + \func{write} bloccherà fino al completamento della scrittura di tutti dati + sul sull'hardware sottostante.\\ + \const{O\_FSYNC} & sinonimo di \const{O\_SYNC}. \\ + \const{O\_NOATIME} & blocca l'aggiornamento dei tempi dei di accesso dei + file (vedi \secref{sec:file_file_times}). In Linux questa opzione non è + disponibile per il singolo file ma come opzione per il filesystem in fase + di montaggio.\\ + \hline + \end{tabular} + \caption{Valori e significato dei vari bit del \textit{file status flag}.} + \label{tab:file_open_flags} +\end{table} + +\footnotetext[2]{la pagina di manuale di \func{open} segnala che questa + opzione è difettosa su NFS, e che i programmi che la usano per stabilire un + \textsl{file di lock}\index{file!di lock} possono incorrere in una race + condition\index{race condition}. Si consiglia come alternativa di usare un + file con un nome univoco e la funzione \func{link} per verificarne + l'esistenza (vedi \secref{sec:ipc_file_lock}).} + +\footnotetext[3]{\textit{Denial of Service}, si chiamano così attacchi miranti + ad impedire un servizio causando una qualche forma di carico eccessivo per + il sistema, che resta bloccato nelle risposte all'attacco.} + +\footnotetext[4]{il problema è che NFS non supporta la scrittura in append, ed + il kernel deve simularla, ma questo comporta la possibilità di una race + condition, vedi \secref{sec:file_atomic}.} + +\footnotetext[5]{l'opzione origina da SVr4, dove però causava il ritorno da + una \func{read} con un valore nullo e non con un errore, questo introduce + un'ambiguità, dato che come vedremo in \secref{sec:file_read} il ritorno di + zero da parte di \func{read} ha il significato di una end-of-file.} + +Questa caratteristica permette di prevedere qual'è il valore del file +descriptor che si otterrà al ritorno di \func{open}, e viene talvolta usata da +alcune applicazioni per sostituire i file corrispondenti ai file standard +visti in \secref{sec:file_std_descr}: se ad esempio si chiude lo standard +input e si apre subito dopo un nuovo file questo diventerà il nuovo standard +input (avrà cioè il file descriptor 0). + +Il nuovo file descriptor non è condiviso con nessun altro processo (torneremo +sulla condivisione dei file, in genere accessibile dopo una \func{fork}, in +\secref{sec:file_sharing}). Il nuovo file descriptor è impostato per restare +aperto attraverso una \func{exec} (come accennato in \secref{sec:proc_exec}) e +l'offset è impostato all'inizio del file. + +L'argomento \param{mode} specifica i permessi con cui il file viene +eventualmente creato; i valori possibili sono gli stessi già visti in +\secref{sec:file_perm_overview} e possono essere specificati come OR binario +delle costanti descritte in \tabref{tab:file_bit_perm}. Questi permessi sono +filtrati dal valore di \var{umask} (vedi \secref{sec:file_umask}) per il +processo. + +La funzione prevede diverse opzioni, che vengono specificate usando vari bit +dell'argomento \param{flags}. Alcuni di questi bit vanno anche a costituire +il flag di stato del file (o \textit{file status flag}), che è mantenuto nel +campo \var{f\_flags} della struttura \var{file} (al solito si veda lo schema +di \figref{fig:file_proc_file}). Essi sono divisi in tre categorie +principali: +\begin{itemize} +\item \textsl{i bit delle modalità di accesso}: specificano con quale modalità + si accederà al file: i valori possibili sono lettura, scrittura o + lettura/scrittura. Uno di questi bit deve essere sempre specificato quando + si apre un file. Vengono impostati alla chiamata da \func{open}, e possono + essere riletti con una \func{fcntl} (fanno parte del \textit{file status + flag}), ma non possono essere modificati. +\item \textsl{i bit delle modalità di apertura}: permettono di specificare + alcune delle caratteristiche del comportamento di \func{open} quando viene + eseguita. Hanno effetto solo al momento della chiamata della funzione e non + sono memorizzati né possono essere riletti. +\item \textsl{i bit delle modalità di operazione}: permettono di specificare + alcune caratteristiche del comportamento delle future operazioni sul file + (come la \func{read} o la \func{write}). Anch'essi fanno parte del + \textit{file status flag}. Il loro valore è impostato alla chiamata di + \func{open}, ma possono essere riletti e modificati (insieme alle + caratteristiche operative che controllano) con una \func{fcntl}. +\end{itemize} + +In \tabref{tab:file_open_flags} si sono riportate, ordinate e divise fra loro +secondo le tre modalità appena elencate, le costanti mnemoniche associate a +ciascuno di questi bit. Dette costanti possono essere combinate fra di loro +con un OR aritmetico per costruire il valore (in forma di maschera binaria) +dell'argomento \param{flags} da passare alla \func{open} per specificarne il +comportamento. I due flag \const{O\_NOFOLLOW} e \const{O\_DIRECTORY} sono +estensioni specifiche di Linux, e deve essere definita la macro +\macro{\_GNU\_SOURCE} per poterli usare. + +Nelle prime versioni di Unix i valori di \param{flag} specificabili per +\func{open} erano solo quelli relativi alle modalità di accesso del file. Per +questo motivo per creare un nuovo file c'era una system call apposita, +\func{creat}, il cui prototipo è: +\begin{prototype}{fcntl.h} + {int creat(const char *pathname, mode\_t mode)} + Crea un nuovo file vuoto, con i permessi specificati da \var{mode}. È del + tutto equivalente a \code{open(filedes, O\_CREAT|O\_WRONLY|O\_TRUNC, mode)}. +\end{prototype} +\noindent adesso questa funzione resta solo per compatibilità con i vecchi +programmi. -\subsection{La funzione \texttt{close}} + +\subsection{La funzione \func{close}} \label{sec:file_close} -\subsection{La funzione \texttt{lseek}} +La funzione \func{close} permette di chiudere un file, in questo modo il file +descriptor ritorna disponibile; il suo prototipo è: +\begin{prototype}{unistd.h}{int close(int fd)} + Chiude il descrittore \var{fd}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + ed in questo caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EBADF}] \var{fd} non è un descrittore valido. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. + \end{errlist} + ed inoltre \errval{EIO}.} +\end{prototype} + +La chiusura di un file rilascia ogni blocco (il \textit{file + locking}\index{file!locking} è trattato in \secref{sec:file_locking}) che il +processo poteva avere acquisito su di esso; se \var{fd} è l'ultimo riferimento +(di eventuali copie) ad un file aperto, tutte le risorse nella file table +vengono rilasciate. Infine se il file descriptor era l'ultimo riferimento ad +un file su disco quest'ultimo viene cancellato. + +Si ricordi che quando un processo termina anche tutti i suoi file descriptor +vengono chiusi, molti programmi sfruttano questa caratteristica e non usano +esplicitamente \func{close}. In genere comunque chiudere un file senza +controllarne lo stato di uscita è errore; infatti molti filesystem +implementano la tecnica del \textit{write-behind}, per cui una \func{write} +può avere successo anche se i dati non sono stati scritti, un eventuale errore +di I/O allora può sfuggire, ma verrà riportato alla chiusura del file: per +questo motivo non effettuare il controllo può portare ad una perdita di dati +inavvertita.\footnote{in Linux questo comportamento è stato osservato con NFS + e le quote su disco.} + +In ogni caso una \func{close} andata a buon fine non garantisce che i dati +siano stati effettivamente scritti su disco, perché il kernel può decidere di +ottimizzare l'accesso a disco ritardandone la scrittura. L'uso della funzione +\func{sync} (vedi \secref{sec:file_sync}) effettua esplicitamente il +\emph{flush} dei dati, ma anche in questo caso resta l'incertezza dovuta al +comportamento dell'hardware (che a sua volta può introdurre ottimizzazioni +dell'accesso al disco che ritardano la scrittura dei dati, da cui l'abitudine +di ripetere tre volte il comando prima di eseguire lo shutdown). + + +\subsection{La funzione \func{lseek}} \label{sec:file_lseek} -\subsection{La funzione \texttt{read}} +Come già accennato in \secref{sec:file_fd} a ciascun file aperto è associata +una \textsl{posizione corrente nel file} (il cosiddetto \textit{file offset}, +mantenuto nel campo \var{f\_pos} di \var{file}) espressa da un numero intero +positivo come numero di byte dall'inizio del file. Tutte le operazioni di +lettura e scrittura avvengono a partire da questa posizione che viene +automaticamente spostata in avanti del numero di byte letti o scritti. + +In genere (a meno di non avere richiesto la modalità \const{O\_APPEND}) questa +posizione viene impostata a zero all'apertura del file. È possibile impostarla +ad un valore qualsiasi con la funzione \func{lseek}, il cui prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{unistd.h} + \funcdecl{off\_t lseek(int fd, off\_t offset, int whence)} + Imposta la posizione attuale nel file. + + \bodydesc{La funzione ritorna valore della posizione corrente in caso di + successo e -1 in caso di errore nel qual caso \var{errno} assumerà uno dei + valori: + \begin{errlist} + \item[\errcode{ESPIPE}] \param{fd} è una pipe, un socket\index{socket} o una + fifo. + \item[\errcode{EINVAL}] \param{whence} non è un valore valido. + \end{errlist} + ed inoltre \errval{EBADF}.} +\end{functions} + +La nuova posizione è impostata usando il valore specificato da \param{offset}, +sommato al riferimento dato da \param{whence}; quest'ultimo può assumere i +seguenti valori\footnote{per compatibilità con alcune vecchie notazioni + questi valori possono essere rimpiazzati rispettivamente con 0, 1 e 2 o con + \const{L\_SET}, \const{L\_INCR} e \const{L\_XTND}.}: +\begin{basedescript}{\desclabelwidth{2.0cm}} +\item[\const{SEEK\_SET}] si fa riferimento all'inizio del file: il valore + (sempre positivo) di \param{offset} indica direttamente la nuova posizione + corrente. +\item[\const{SEEK\_CUR}] si fa riferimento alla posizione corrente del file: + ad essa viene sommato \param{offset} (che può essere negativo e positivo) + per ottenere la nuova posizione corrente. +\item[\const{SEEK\_END}] si fa riferimento alla fine del file: alle dimensioni + del file viene sommato \param{offset} (che può essere negativo e positivo) + per ottenere la nuova posizione corrente. +\end{basedescript} + +Come accennato in \secref{sec:file_file_size} con \func{lseek} è possibile +impostare la posizione corrente anche oltre la fine del file, e alla +successiva scrittura il file sarà esteso. La chiamata non causa nessuna +attività di input/output, si limita a modificare la posizione corrente nel +kernel (cioè \var{f\_pos} in \var{file}, vedi \figref{fig:file_proc_file}). + +Dato che la funzione ritorna la nuova posizione, usando il valore zero per +\param{offset} si può riottenere la posizione corrente nel file chiamando la +funzione con \code{lseek(fd, 0, SEEK\_CUR)}. + +Si tenga presente inoltre che usare \const{SEEK\_END} non assicura affatto che +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 \textit{race condition} +\index{race condition}, vedi \secref{sec:file_atomic}). + +Non tutti i file supportano la capacità di eseguire una \func{lseek}, in +questo caso la funzione ritorna l'errore \errcode{EPIPE}. Questo, oltre che per +i tre casi citati nel prototipo, vale anche per tutti quei dispositivi che non +supportano questa funzione, come ad esempio per i file di +terminale.\footnote{altri sistemi, usando \const{SEEK\_SET}, in questo caso + ritornano il numero di caratteri che vi sono stati scritti.} Lo standard +POSIX però non specifica niente in proposito. Infine alcuni file speciali, ad +esempio \file{/dev/null}, non causano un errore ma restituiscono un valore +indefinito. + + +\subsection{La funzione \func{read}} \label{sec:file_read} -\subsection{La funzione \texttt{write}} + +Una volta che un file è stato aperto (con il permesso in lettura) su possono +leggere i dati che contiene utilizzando la funzione \func{read}, il cui +prototipo è: +\begin{prototype}{unistd.h}{ssize\_t read(int fd, void * buf, size\_t count)} + + Cerca di leggere \var{count} byte dal file \var{fd} al buffer \var{buf}. + + \bodydesc{La funzione ritorna il numero di byte letti in caso di successo e + -1 in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima di + aver potuto leggere qualsiasi dato. + \item[\errcode{EAGAIN}] la funzione non aveva nessun dato da restituire e si + era aperto il file in modalità \const{O\_NONBLOCK}. + \end{errlist} + ed inoltre \errval{EBADF}, \errval{EIO}, \errval{EISDIR}, \errval{EBADF}, + \errval{EINVAL} e \errval{EFAULT} ed eventuali altri errori dipendenti dalla + natura dell'oggetto connesso a \var{fd}.} +\end{prototype} + +La funzione tenta di leggere \var{count} byte a partire dalla posizione +corrente nel file. Dopo la lettura la posizione sul file è spostata +automaticamente in avanti del numero di byte letti. Se \var{count} è zero la +funzione restituisce zero senza nessun altro risultato. + +Si deve sempre tener presente che non è detto che la funzione \func{read} +restituisca sempre il numero di byte richiesto, ci sono infatti varie ragioni +per cui la funzione può restituire un numero di byte inferiore; questo è un +comportamento normale, e non un errore, che bisogna sempre tenere presente. + +La prima e più ovvia di queste ragioni è che si è chiesto di leggere più byte +di quanto il file ne contenga. In questo caso il file viene letto fino alla +sua fine, e la funzione ritorna regolarmente il numero di byte letti +effettivamente. + +Raggiunta la fine del file, alla ripetizione di un'operazione di lettura, +otterremmo il ritorno immediato di \func{read} con uno zero. La condizione di +raggiungimento della fine del file non è un errore, e viene segnalata appunto +da un valore di ritorno di \func{read} nullo. Ripetere ulteriormente la +lettura non avrebbe nessun effetto se non quello di continuare a ricevere zero +come valore di ritorno. + +Con i \textsl{file regolari} questa è l'unica situazione in cui si può avere +un numero di byte letti inferiore a quello richiesto, ma questo non è vero +quando si legge da un terminale, da una fifo o da una pipe. In tal caso +infatti, se non ci sono dati in ingresso, la \func{read} si blocca (a meno di +non aver selezionato la modalità non bloccante, vedi +\secref{sec:file_noblocking}) e ritorna solo quando ne arrivano; se il numero +di byte richiesti eccede quelli disponibili la funzione ritorna comunque, ma +con un numero di byte inferiore a quelli richiesti. + +Lo stesso comportamento avviene caso di lettura dalla rete (cioè su un +socket\index{socket}, come vedremo in \secref{sec:sock_io_behav}), o per la +lettura da certi file di dispositivo, come le unità a nastro, che +restituiscono sempre i dati ad un singolo blocco alla volta. + +In realtà anche le due condizioni segnalate dagli errori \errcode{EINTR} e +\errcode{EAGAIN} non sono errori. La prima si verifica quando la \func{read} è +bloccata in attesa di dati in ingresso e viene interrotta da un segnale; in +tal caso l'azione da intraprendere è quella di rieseguire la funzione. +Torneremo in dettaglio sull'argomento in \secref{sec:sig_gen_beha}. + +La seconda si verifica quando il file è in modalità non bloccante (vedi +\secref{sec:file_noblocking}) e non ci sono dati in ingresso: la funzione +allora ritorna immediatamente con un errore \errcode{EAGAIN}\footnote{sotto + BSD per questo errore viene usata la costante \errcode{EWOULDBLOCK}, in + Linux, con le glibc, questa è sinonima di \errcode{EAGAIN}.} che indica +soltanto che occorrerà provare a ripetere la lettura. + +La funzione \func{read} è una delle system call fondamentali, esistenti fin +dagli albori di Unix, ma nella seconda versione delle \textit{Single Unix + Specification}\footnote{questa funzione, e l'analoga \func{pwrite} sono + state aggiunte nel kernel 2.1.60, il supporto nelle \acr{glibc}, compresa + l'emulazione per i vecchi kernel che non hanno la system call, è stato + aggiunto con la versione 2.1, in versioni precedenti sia del kernel che + delle librerie la funzione non è disponibile.} (quello che viene chiamato +normalmente Unix98, vedi \secref{sec:intro_opengroup}) è stata introdotta la +definizione di un'altra funzione di lettura, \func{pread}, il cui prototipo è: +\begin{prototype}{unistd.h} +{ssize\_t pread(int fd, void * buf, size\_t count, off\_t offset)} + +Cerca di leggere \var{count} byte dal file \var{fd}, a partire dalla posizione +\var{offset}, nel buffer \var{buf}. + +\bodydesc{La funzione ritorna il numero di byte letti in caso di successo e -1 + in caso di errore, nel qual caso \var{errno} assumerà i valori già visti per + \func{read} e \func{lseek}.} +\end{prototype} +\noindent che però diventa accessibile solo con la definizione della macro: +\begin{verbatim} + #define _XOPEN_SOURCE 500 +\end{verbatim} + +Questa funzione serve quando si vogliono leggere dati dal file senza +modificare la posizione corrente. È 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 \secref{sec:file_sharing}). Il valore di +\var{offset} fa sempre riferimento all'inizio del file. + + +\subsection{La funzione \func{write}} \label{sec:file_write} -\subsection{Operazioni atomiche coi file} -\label{sec:file_atomic} +Una volta che un file è stato aperto (con il permesso in scrittura) su può +scrivere su di esso utilizzando la funzione \func{write}, il cui prototipo è: +\begin{prototype}{unistd.h}{ssize\_t write(int fd, void * buf, size\_t count)} + + Scrive \var{count} byte dal buffer \var{buf} sul file \var{fd}. + + \bodydesc{La funzione ritorna il numero di byte scritti in caso di successo + e -1 in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: + \begin{errlist} + \item[\errcode{EINVAL}] \var{fd} è connesso ad un oggetto che non consente la + scrittura. + \item[\errcode{EFBIG}] si è cercato di scrivere oltre la dimensione massima + consentita dal filesystem o il limite per le dimensioni dei file del + processo o su una posizione oltre il massimo consentito. + \item[\errcode{EPIPE}] \var{fd} è connesso ad una pipe il cui altro capo è + chiuso in lettura; in questo caso viene anche generato il segnale + \const{SIGPIPE}, se questo viene gestito (o bloccato o ignorato) la + funzione ritorna questo errore. + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale prima di + aver potuto scrivere qualsiasi dato. + \item[\errcode{EAGAIN}] la funzione non aveva nessun dato da restituire e si + era aperto il file in modalità \const{O\_NONBLOCK}. + \end{errlist} + ed inoltre \errval{EBADF}, \errval{EIO}, \errval{EISDIR}, \errval{EBADF}, + \errval{ENOSPC}, \errval{EINVAL} e \errval{EFAULT} ed eventuali altri errori + dipendenti dalla natura dell'oggetto connesso a \var{fd}.} +\end{prototype} -\section{Funzioni avanzate} +Come nel caso di \func{read} la funzione tenta di scrivere \var{count} byte a +partire dalla posizione corrente nel file e sposta automaticamente la +posizione in avanti del numero di byte scritti. Se il file è aperto in +modalità \const{O\_APPEND} i dati vengono sempre scritti alla fine del file. +Lo standard POSIX richiede che i dati scritti siano immediatamente disponibili +ad una \func{read} chiamata dopo che la \func{write} che li ha scritti è +ritornata; ma dati i meccanismi di caching non è detto che tutti i filesystem +supportino questa capacità. + +Se \var{count} è zero la funzione restituisce zero senza fare nient'altro. Per +i file ordinari il numero di byte scritti è sempre uguale a quello indicato +da \var{count}, a meno di un errore. Negli altri casi si ha lo stesso +comportamento di \func{read}. + +Anche per \func{write} lo standard Unix98 definisce un'analoga \func{pwrite} +per scrivere alla posizione indicata senza modificare la posizione corrente +nel file, il suo prototipo è: +\begin{prototype}{unistd.h} +{ssize\_t pwrite(int fd, void * buf, size\_t count, off\_t offset)} + +Cerca di scrivere sul file \var{fd}, a partire dalla posizione \var{offset}, +\var{count} byte dal buffer \var{buf}. + +\bodydesc{La funzione ritorna il numero di byte letti in caso di successo e -1 + in caso di errore, nel qual caso \var{errno} assumerà i valori già visti per + \func{write} e \func{lseek}.} +\end{prototype} +\noindent e per essa valgono le stesse considerazioni fatte per \func{pread}. + + +\section{Caratteristiche avanzate} \label{sec:file_adv_func} -\subsection{La funzioni \texttt{dup} e \texttt{dup2}} +In questa sezione approfondiremo alcune delle caratteristiche più sottili +della gestione file in un sistema unix-like, esaminando in dettaglio il +comportamento delle funzioni base, inoltre tratteremo le funzioni che +permettono di eseguire alcune operazioni avanzate con i file (il grosso +dell'argomento sarà comunque affrontato in \capref{cha:file_advanced}). + + +\subsection{La condivisione dei files} +\label{sec:file_sharing} + +In \secref{sec:file_fd} abbiamo descritto brevemente l'architettura +dell'interfaccia con i file da parte di un processo, mostrando in +\figref{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. + +\begin{figure}[htb] + \centering + \includegraphics[width=15cm]{img/filemultacc} + \caption{Schema dell'accesso allo stesso file da parte di due processi + diversi} + \label{fig:file_mult_acc} +\end{figure} + +Il primo caso è quello in cui due processi diversi che aprono lo stesso file +su disco; sulla base di quanto visto in \secref{sec:file_fd} avremo una +situazione come quella illustrata in \figref{fig:file_mult_acc}: ciascun +processo avrà una sua voce nella \textit{file table} referenziata da un +diverso file descriptor nella sua \var{file\_struct}. Entrambe le voci nella +\textit{file table} faranno però riferimento allo stesso inode\index{inode} su +disco. + +Questo significa che ciascun processo avrà la sua posizione corrente sul file, +la sua modalità di accesso e versioni proprie di tutte le proprietà che +vengono mantenute nella sua voce della \textit{file table}. Questo ha +conseguenze specifiche sugli effetti della possibile 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} + nell'inode\index{inode}. +\item se un file è in modalità \const{O\_APPEND} tutte le volte che viene + effettuata una scrittura la posizione corrente viene prima impostata alla + dimensione corrente del file letta dall'inode\index{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 \var{file} della \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 + dall'inode\index{inode}. +\end{itemize} + +\begin{figure}[htb] + \centering + \includegraphics[width=15cm]{img/fileshar} + \caption{Schema dell'accesso ai file da parte di un processo figlio} + \label{fig:file_acc_child} +\end{figure} + +Il secondo caso è quello in cui due file descriptor di due processi diversi +puntino alla stessa voce nella \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 \secref{sec:proc_fork}). La +situazione è illustrata in \figref{fig:file_acc_child}; dato che il processo +figlio riceve una copia dello spazio di indirizzi del padre, riceverà anche +una copia di \var{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 +\secref{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 flag di stato del file (quelli impostati +dall'argomento \param{flag} di \func{open}) essendo tenuti nella voce della +\textit{file table}\footnote{per la precisione nel campo \var{f\_flags} di + \var{file}.}, vengono in questo caso condivisi. Ai file però sono associati +anche altri flag, dei quali l'unico usato al momento è \const{FD\_CLOEXEC}, +detti \textit{file descriptor flags}. Questi ultimi sono tenuti invece in +\var{file\_struct}, e perciò sono specifici di 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 \textit{file + locking}\index{file!locking}, che esamineremo in \secref{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 \secref{sec:file_lseek} impostare la posizione alla +fine del file e poi scrivere può condurre ad una \textit{race + condition}\index{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 system call in successione non è un'operazione +atomica; il problema è stato risolto introducendo la modalità +\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 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 race condition\index{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 system call (per i dettagli sull'uso di questa caratteristica +si veda \secref{sec:ipc_file_lock}). + + +\subsection{La funzioni \func{sync} e \func{fsync}} +\label{sec:file_sync} + +Come accennato in \secref{sec:file_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 è \func{sync} il cui +prototipo è: +\begin{prototype}{unistd.h}{int sync(void)} + + Sincronizza il buffer della cache dei file col disco. + + \bodydesc{La funzione ritorna sempre zero.} +\end{prototype} +\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 \file{/proc/sys/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 \func{fsync} e \func{fdatasync}, i cui prototipi sono: +\begin{functions} + \headdecl{unistd.h} + \funcdecl{int fsync(int fd)} + Sincronizza dati e metadati del file \param{fd} + \funcdecl{int fdatasync(int fd)} + Sincronizza i dati del file \param{fd}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} assume i valori: + \begin{errlist} + \item[\errcode{EINVAL}] \param{fd} è un file speciale che non supporta la + sincronizzazione. + \end{errlist} + ed inoltre \errval{EBADF}, \errval{EROFS} e \errval{EIO}.} +\end{functions} + +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 metadati del file (che +riguardano sia le modifiche alle tabelle di allocazione dei settori, che gli +altri dati contenuti nell'inode\index{inode} che si leggono con \var{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{La funzioni \func{dup} e \func{dup2}} \label{sec:file_dup} -\subsection{La funzione \texttt{fcntl}} +Abbiamo già visto in \secref{sec:file_sharing} 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 \func{dup} il cui +prototipo è: +\begin{prototype}{unistd.h}{int dup(int oldfd)} + Crea una copia del file descriptor \param{oldfd}. + + \bodydesc{La funzione ritorna il nuovo file descriptor in caso di successo e + -1 in caso di errore, nel qual caso \var{errno} assumerà uno dei + valori: + \begin{errlist} + \item[\errcode{EBADF}] \param{oldfd} non è un file aperto. + \item[\errcode{EMFILE}] si è raggiunto il numero massimo consentito di file + descriptor aperti. + \end{errlist}} +\end{prototype} + +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 \figref{fig:file_dup}: l'effetto della funzione è +semplicemente quello di copiare il valore nella struttura \var{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. + +\begin{figure}[htb] + \centering \includegraphics[width=15cm]{img/filedup} + \caption{Schema dell'accesso ai file duplicati} + \label{fig:file_dup} +\end{figure} + +Si noti che per quanto illustrato in\figref{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}\index{close-on-exec} (vedi +\secref{sec:proc_exec} e \secref{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 \secref{sec:ipc_pipe_use}, +quando tratteremo le pipe). Per fare questo in genere occorre prima chiudere +il file che si vuole sostituire, cossicché 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, \func{dup2}, che permette di specificare esplicitamente qual'è +il valore di file descriptor che si vuole avere come duplicato; il suo +prototipo è: +\begin{prototype}{unistd.h}{int dup2(int oldfd, int newfd)} + + Rende \param{newfd} una copia del file descriptor \param{oldfd}. + + \bodydesc{La funzione ritorna il nuovo file descriptor in caso di successo e + -1 in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: + \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{EMFILE}] si è raggiunto il numero massimo consentito di file + descriptor aperti. + \end{errlist}} +\end{prototype} +\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 duplicazione dei file descriptor può essere effettuata anche usando la +funzione di controllo dei file \func{fnctl} (che esamineremo in +\secref{sec:file_fcntl}) con il parametro \const{F\_DUPFD}. L'operazione ha +la sintassi \code{fnctl(oldfd, F\_DUPFD, newfd)} e se si usa 0 come valore per +\param{newfd} diventa equivalente a \func{dup}. + +La sola differenza fra le due funzioni\footnote{a parte la sistassi 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). + + +\subsection{La funzione \func{fcntl}} \label{sec:file_fcntl} -\subsection{La funzione \texttt{ioctl}} +Oltre alle operazioni base esaminate in \secref{sec:file_base_func} esistono +tutta una serie di operazioni ausiliarie che è possibile eseguire su un file +descriptor, che non riguardano la normale lettura e scrittura di dati, ma la +gestione sia delle loro proprietà, che di tutta una serie di ulteriori +funzionalità che il kernel può mettere a disposizione.\footnote{ad esempio si + gesticono con questa funzione l'I/O asincrono (vedi + \secref{sec:file_asyncronous_io}) e il file locking\index{file!locking} + (vedi \secref{sec:file_locking}).} + +Per queste operazioni di manipolazione e di controllo su proprietà e +caratteristiche un file descriptor, viene usata la funzione \func{fcntl}, il +cui prototipo è: +\begin{functions} + \headdecl{unistd.h} + \headdecl{fcntl.h} + \funcdecl{int fcntl(int fd, int cmd)} + \funcdecl{int fcntl(int fd, int cmd, long arg)} + \funcdecl{int fcntl(int fd, int cmd, struct flock * lock)} + Esegue una delle possibili operazioni specificate da \param{cmd} + sul file \param{fd}. + + \bodydesc{La funzione ha valori di ritorno diversi a seconda + dell'operazione. In caso di errore il valore di ritorno è sempre -1 ed il + codice dell'errore è restituito nella variabile \var{errno}; i codici + possibili dipendono dal tipo di operazione, l'unico valido in generale è: + \begin{errlist} + \item[\errcode{EBADF}] \param{fd} non è un file aperto. + \end{errlist}} +\end{functions} + +Il comportamento di questa funzione è determinato dal valore del comando +\param{cmd} che le viene fornito; in \secref{sec:file_dup} abbiamo incontrato +un esempio per la duplicazione dei file descriptor, una lista dei possibili +valori è riportata di seguito: +\begin{basedescript}{\desclabelwidth{2.0cm}} +\item[\const{F\_DUPFD}] trova il primo file descriptor disponibile di valore + maggiore o uguale ad \param{arg} e ne fa una copia di \var{fd}. In caso di + successo ritorna il nuovo file descriptor. Gli errori possibili sono + \errcode{EINVAL} se \param{arg} è negativo o maggiore del massimo consentito + o \errcode{EMFILE} se il processo ha già raggiunto il massimo numero di + descrittori consentito. +\item[\const{F\_SETFD}] imposta il valore del \textit{file descriptor flag} al + valore specificato con \param{arg}. Al momento l'unico bit usato è quello di + \textit{close-on-exec}\index{close-on-exec}, identificato dalla costante + \const{FD\_CLOEXEC}, che serve a richiedere che il file venga chiuso nella + esecuzione di una \func{exec} (vedi \secref{sec:proc_exec}). +\item[\const{F\_GETFD}] ritorna il valore del \textit{file descriptor flag} di + \var{fd}, se \const{FD\_CLOEXEC} è impostato i file descriptor aperti + vengono chiusi attraverso una \func{exec} altrimenti (il comportamento + predefinito) restano aperti. +\item[\const{F\_GETFL}] ritorna il valore del \textit{file status flag}, + 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 \tabref{tab:file_open_flags}). +\item[\const{F\_SETFL}] imposta il \textit{file status flag} al valore + specificato da \param{arg}, possono essere impostati solo i bit riportati + nella terza sezione di \tabref{tab:file_open_flags}.\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 + (questa funzionalità è trattata in dettaglio in + \secref{sec:file_posix_lock}). +\item[\const{F\_SETLK}] richiede o rilascia un file lock a seconda di quanto + specificato nella struttura puntata da \param{lock}. Se il lock è tenuto da + qualcun'altro ritorna immediatamente restituendo -1 e imposta \var{errno} a + \errcode{EACCES} o \errcode{EAGAIN} (questa funzionalità è trattata in + dettaglio in \secref{sec:file_posix_lock}). +\item[\const{F\_SETLKW}] identica a \const{F\_SETLK} eccetto per il fatto che + la funzione non ritorna subito ma attende che il blocco sia rilasciato. Se + l'attesa viene interrotta da un segnale la funzione restituisce -1 e imposta + \var{errno} a \errcode{EINTR} (questa funzionalità è trattata in dettaglio in + \secref{sec:file_posix_lock}). +\item[\const{F\_GETOWN}] restituisce il \acr{pid} del processo o + l'identificatore del process group\footnote{i \texttt{process group} sono + (vedi \secref{sec:sess_proc_group}) sono raggruppamenti di processi usati + nel controllo di sessione; a ciascuno di essi è associato un + identificatore (un numero positivo analogo al \acr{pid}).} che è preposto + alla ricezione dei segnali \const{SIGIO} e \const{SIGURG} per gli eventi + associati al file descriptor \var{fd}. Nel caso di un process group viene + restituito un valore negativo il cui valore assoluto corrisponde + all'identificatore del process group. +\item[\const{F\_SETOWN}] imposta, con il valore dell'argomento \param{arg}, + l'identificatore del processo o del \textit{process group} che riceverà i + segnali \const{SIGIO} e \const{SIGURG} per gli eventi associati al file + descriptor \var{fd}. Come per \const{F\_GETOWN}, per impostare un process + group si deve usare per \param{arg} un valore negativo, il cui valore + assoluto corrisponde all'identificatore del process group. +\item[\const{F\_GETSIG}] restituisce il valore del segnale inviato quando ci + sono dati disponibili in ingresso su un file descriptor aperto ed impostato + per l'I/O asincrono (si veda \secref{sec:file_asyncronous_io}). Il valore 0 + indica il valore predefinito (che è \const{SIGIO}), un valore diverso da + zero indica il segnale richiesto, (che può essere anche lo stesso + \const{SIGIO}). +\item[\const{F\_SETSIG}] imposta il segnale da inviare quando diventa + possibile effettuare I/O sul file descriptor in caso di I/O asincrono. Il + valore zero indica di usare il segnale predefinito, \const{SIGIO}. Un altro + valore (compreso lo stesso \const{SIGIO}) specifica il segnale voluto; l'uso + di un valore diverso da zero permette inoltre, se si è installato il + manipolatore del segnale come \var{sa\_sigaction} usando + \const{SA\_SIGINFO}, (vedi \secref{sec:sig_sigaction}), di rendere + disponibili al manipolatore informazioni ulteriori informazioni riguardo il + file che ha generato il segnale attraverso i valori restituiti in + \type{siginfo\_t} (come vedremo in + \secref{sec:file_asyncronous_io}).\footnote{i due comandi \const{F\_SETSIG} + e \const{F\_GETSIG} sono una estensione specifica di Linux.} +\end{basedescript} + +La maggior parte delle funzionalità di \func{fcntl} sono troppo avanzate per +poter essere affrontate in dettaglio a questo punto; saranno riprese più +avanti quando affronteremo le problematiche ad esse relative (in particolare +le tematiche relative all'I/O asincrono sono trattate in maniera esaustiva in +\secref{sec:file_asyncronous_io} mentre quelle relative al \textit{file + locking}\index{file!locking} saranno esaminate in +\secref{sec:file_locking}). + +Si tenga presente infine che quando si usa la funzione per determinare le +modalità di accesso con cui è stato aperto il file (attraverso l'uso del +comando \const{F\_GETFL}) è necessario estrarre i bit corripondenti nel +\textit{file status flag} che si è ottenuto. Infatti la definizione corrente +di quest'ultimo non assegna bit separati alle tre diverse modalità +\const{O\_RDONLY}, \const{O\_WRONLY} e \const{O\_RDWR}.\footnote{in Linux + queste costanti sono poste rispettivamente ai valori 0, 1 e 2.} Per questo +motivo il valore della modalità di accesso corrente si ottiene eseguendo un +AND binario del valore di ritorno di \func{fcntl} con la maschera +\const{O\_ACCMODE} (anch'essa definita in \file{fcntl.h}), che estrae i bit di +accesso dal \textit{file status flag}. + + + +\subsection{La funzione \func{ioctl}} \label{sec:file_ioctl} +Benché il concetto di \textit{everything is a file} si sia dimostratato molto +valido anche per l'interazione con i dispositivi più vari, fornendo una +interfaccia che permette di interagire con essi tramite le stesse funzioni +usate per i normali file di dati, esisteranno sempre caratteristiche +peculiari, specifiche dell'hardware e della funzionalità che ciascun +dispositivo può provvedere, che non possono venire comprese in questa +interfaccia astratta (un caso tipico è l'impostazione della velocità di una +porta seriale, o le dimensioni di un framebuffer). + +Per questo motivo nell'architettura del sistema è stata prevista l'esistenza +di una funzione apposita, \func{ioctl}, con cui poter compiere le operazioni +specifiche di ogni dispositivo particolare, usando come riferimento il solito +file descriptor. Il prototipo di questa funzione è: +\begin{prototype}{sys/ioctl.h}{int ioctl(int fd, int request, ...)} + Manipola il dispositivo sottostante, usando il parametro \param{request} per + specificare l'operazione richiesta ed il terzo parametro (usualmente di tipo + \param{char * argp} o \param{int argp}) per il trasferimento + dell'informazione necessaria. + + \bodydesc{La funzione nella maggior parte dei casi ritorna 0, alcune + operazioni usano però il valore di ritorno per restituire informazioni. In + caso di errore viene sempre restituito -1 ed \var{errno} assumerà uno dei + valori: + \begin{errlist} + \item[\errcode{ENOTTY}] il file \param{fd} non è associato con un device, o + la richiesta non è applicabile all'oggetto a cui fa riferimento + \param{fd}. + \item[\errcode{EINVAL}] gli argomenti \param{request} o \param{argp} non sono + validi. + \end{errlist} + ed inoltre \errval{EBADF} e \errval{EFAULT}.} +\end{prototype} + +La funzione serve in sostanza per fare tutte quelle operazioni che non si +adattano al design dell'architettura dei file e che non è possibile effettuare +con le funzioni esaminate finora. Esse vengono selezionate attraverso il +valore di \param{request} e gli eventuali risultati possono essere restituiti +sia attraverso il valore di ritorno che attraverso il terzo argomento +\param{argp}. Sono esempi delle operazioni gestite con una \func{ioctl}: +\begin{itemize*} +\item il cambiamento dei font di un terminale. +\item l'esecuzione di una traccia audio di un CDROM. +\item i comandi di avanti veloce e riavvolgimento di un nastro. +\item il comando di espulsione di un dispositivo rimovibile. +\item l'impostazione della velocità trasmissione di una linea seriale. +\item l'impostazione della frequenza e della durata dei suoni emessi dallo + speaker. +\end{itemize*} + +In generale ogni dispositivo ha un suo insieme di possibili diverse operazioni +effettuabili attraverso \func{ioctl}, che sono definite nell'header file +\file{sys/ioctl.h}, e devono essere usate solo sui dispositivi cui fanno +riferimento. Infatti anche se in genere i valori di \param{request} sono +opportunamente differenziati a seconda del dispositivo\footnote{il kernel usa + un apposito \textit{magic number} per distinguere ciascun dispositivo nella + definizione delle macro da usare per \param{request}, in modo da essere + sicuri che essi siano sempre diversi, ed il loro uso per dispositivi diversi + causi al più un errore. Si veda il capitolo quinto di \cite{LinDevDri} per + una trattazione dettagliata dell'argomento.} così che la richiesta di +operazioni relative ad altri dispositivi usualmente provoca il ritorno della +funzione con una condizione di errore, in alcuni casi, relativi a valori +assegnati prima che questa differenziazione diventasse pratica corrente, si +potrebbero usare valori validi anche per il dispositivo corrente, con effetti +imprevedibili o indesiderati. +Data la assoluta specificità della funzione, il cui comportamento varia da +dispositivo a dispositivo, non è possibile fare altro che dare una descrizione +sommaria delle sue caratteristiche; torneremo ad esaminare in seguito quelle +relative ad alcuni casi specifici (ad esempio la gestione dei terminali è +effettuata attraverso \func{ioctl} in quasi tutte le implementazioni di Unix), +qui riportiamo solo i valori di alcuni comandi che sono definiti per ogni +file: +\begin{basedescript}{\desclabelwidth{2.0cm}} +\item[\const{FIOCLEX}] Imposta il bit di \textit{close on exec}. +\item[\const{FIONCLEX}] Cancella il bit di \textit{close on exec}. +\item[\const{FIOASYNC}] Abilita l'I/O asincrono. +\item[\const{FIONBIO}] Abilita l'I/O in modalità non bloccante. +\end{basedescript} +relativi ad operazioni comunque eseguibili anche attraverso \func{fcntl}. +%%% Local Variables: +%%% mode: latex +%%% TeX-master: "gapil" +%%% End: