X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=a85b31f81a48d61e2424579c2cb6cd4d95b9fd72;hp=3ddfa4b0b23506451daaeb750afdcfe8ffd3fb64;hb=fa2959bc0d6de2bf0f171f76591d437fe7b5595d;hpb=a4b228460d4a3710752b2bc66f3c56d72c3ad203 diff --git a/prochand.tex b/prochand.tex index 3ddfa4b..a85b31f 100644 --- a/prochand.tex +++ b/prochand.tex @@ -192,17 +192,258 @@ della funzione \end{functions} Dopo l'esecuzione di una \func{fork} sia il processo padre che il processo -figlio continuano ad essere eseguiti normalmente, ed il processo figlio esegue -esattamente lo stesso codice del padre. La sola differenza è che nel processo -padre il valore di ritorno della funzione fork è il \acr{pid} del processo -figlio, mentre nel figlio è zero; in questo modo il programma può identificare -se viene eseguito dal padre o dal figlio. - - +figlio continuano ad essere eseguiti normalmente alla istruzione seguente la +\func{fork}; il processo figlio è però una copia del padre, e riceve una copia +dei segmenti di testo, stack e dati (vedi \secref{sec:proc_mem_layout}), ed +esegue esattamente lo stesso codice del padre, ma la memoria è copiata, non +condivisa\footnote{In generale il segmento di testo, che è identico, è + condiviso e tenuto in read-only, linux poi utilizza la tecnica del + \textit{copy-on-write}, per cui la memoria degli altri segmenti viene + copiata dal kernel per il nuovo processo solo in caso di scrittura, rendendo + molto più efficiente il meccanismo} pertanto padre e figlio vedono variabili +diverse. + +La differenza che si ha nei due processi è che nel processo padre il valore di +ritorno della funzione fork è il \acr{pid} del processo figlio, mentre nel +figlio è zero; in questo modo il programma può identificare se viene eseguito +dal padre o dal figlio. + +\begin{figure}[!htb] + \footnotesize + \begin{lstlisting}{} +#include /* error definitions and routines */ +#include /* C standard library */ +#include /* unix standard library */ +#include /* standard I/O library */ +#include /* string functions */ + +/* Help printing routine */ +void usage(void); + +int main(int argc, char *argv[]) +{ +/* + * Variables definition + */ + int nchild, i; + pid_t pid; + int wait_child=0; + int wait_parent=0; + + ... /* handling options */ + + /* There must be remaing parameters */ + if (optind == argc) { + usage(); + } + nchild = atoi(argv[optind]); + printf("Test for forking %d child\n", nchild); + /* loop to fork children */ + for (i=0; i output +[piccardi@selidor sources]$ cat output +Test for forking 3 child +Child 1 successfully executing +Child 1 exiting +Test for forking 3 child +Spawned 1 child, pid 836 +Go to next child +Child 2 successfully executing +Child 2 exiting +Test for forking 3 child +Spawned 1 child, pid 836 +Go to next child +Spawned 2 child, pid 837 +Go to next child +Child 3 successfully executing +Child 3 exiting +Test for forking 3 child +Spawned 1 child, pid 836 +Go to next child +Spawned 2 child, pid 837 +Go to next child +Spawned 3 child, pid 838 +Go to next child +\end{verbatim} +che come si vede è completamente diverso da quanto ottenevamo sul terminale. + +Analizzeremo in gran dettaglio in \capref{cha:file_unix_interface} e in +\secref{cha:files_std_interface} il comportamento delle varie funzioni di +interfaccia con i file. Qui basta ricordare che si sono usate le funzioni +standard della libreria del C che prevedono l'output bufferizzato; e questa +bufferizzazione varia a seconda che si tratti di un file su disco (in cui il +buffer viene scaricato su disco solo quando necessario) o di un terminale (nel +qual caso il buffer viene scaricato ad ogni a capo). + +Nel primo esempio allora avevamo che ad ogni chiamata a \func{printf} il +buffer veniva scaricato, e le singole righe erano stampate a video volta a +volta. Quando con la redirezione andiamo a scrivere su un file, questo non +avviene più, e dato che ogni figlio riceve una copia della memoria del padre, +esso riceverà anche quanto c'è nel buffer delle funzioni di I/O, comprese le +linee scritte dal padre fino allora. Così quando all'uscita di un figlio il +buffer viene scritto su disco, troveremo nel file anche tutto quello che il +processo padre aveva scritto prima della sua creazione. Alla fine, dato che +in questo caso il padre esce per ultimo, troviamo anche l'output del padre. + +Ma l'esempio ci mostra un'altro aspetto fondamentale dell'interazione con i +file, che era valido anche per l'esempio precedente, ma meno evidente; il +fatto cioè che non solo processi diversi possono scrivere in contemporanea +sullo stesso file (l'argomento della condivisione dei file in unix è trattato +in dettaglio in \secref{sec:file_sharing}), ma anche che, a differenza di +quanto avviene per le variabili, la posizione corrente sul file è condivisa +fra il padre e tutti i processi figli. + +Quello che succede è che quando lo standard output del padre viene rediretto, +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. + +In questo modo se un processo scrive sul file aggiornerà l'offset sulla file +table, e tutti gli altri vedranno il nuovo valore; in questo modo si evita, in +casi come quello appena mostrato, in cui diversi processi scrivono sullo +stesso file, che l'output successivo di un processo vada a sovrascrivere +quello dei precedenti (l'output potrà risultare mescolato, ma non ci saranno +parti perdute per via di una sovrapposizione). + +Questo tipo di comportamento è essenziale in tutti quei casi in cui il padre +crea un figlio ed attende la sua conclusione per proseguire, ed entrambi +scrivono sullo stesso file (ad esempio lo standard output). Se l'output viene +rediretto con questo comportamento avremo che il padre potrà continuare a +scrivere automaticamente in coda a quanto scritto dal figlio; se così non +fosse ottenere questo comportamento sarebbe estremamente complesso +necessitando di una qualche forma di comunicazione fra i due processi. + +In generale comunque non è buona norma far scrivere più processi sullo stesso +file senza una qualche forma di sincronizzazione in quanto, come visto con il +nostro esempio, le varie scritture risulteranno mescolate fra loro in una +sequenza impredicibile. Le modalità generali con cui si usano i file dopo una +\func{fork} sono sostanzialmente due: +\begin{itemize} +\item Il processo padre aspetta la conclusione del figlio. In questo caso non + è necessaria nessuna azione riguardo ai file, in quanto la sincronizzazione + degli offset dopo eventuali operazioni di lettura e scrittura effettuate dal + figlio è automatica. +\item L'esecuzione di padre e figlio procede indipendentemente. In questo caso + entrambi devono chiudere i file che non servono, per evitare ogni forma +\end{itemize} \subsection{Le funzioni \texttt{wait} e \texttt{waitpid}} \label{sec:proc_wait} + \subsection{Le funzioni \texttt{exec}} \label{sec:proc_exec}