X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=blobdiff_plain;f=prochand.tex;h=23dd77cf8d769b1368609cb2eab6f557b4ce6da3;hb=3d317acde1491cb7fb6f77ef415e010b1c0e6d56;hp=a85b31f81a48d61e2424579c2cb6cd4d95b9fd72;hpb=fa2959bc0d6de2bf0f171f76591d437fe7b5595d;p=gapil.git diff --git a/prochand.tex b/prochand.tex index a85b31f..23dd77c 100644 --- a/prochand.tex +++ b/prochand.tex @@ -78,9 +78,10 @@ affrontate in dettaglio in \secref{sec:proc_fork}). Se si vuole che il processo padre si fermi fino alla conclusione del processo figlio questo deve essere specificato subito dopo la \func{fork} chiamando la -funzione \func{wait} o la funzione \func{waitpid}; queste funzioni -restituiscono anche una informazione abbastanza limitata (il codice di uscita) -sulle cause della terminazione del processo. +funzione \func{wait} o la funzione \func{waitpid} (si veda +\secref{sec:proc_wait}); queste funzioni restituiscono anche una informazione +abbastanza limitata (lo stato di terminazione) sulle cause della terminazione +del processo. Quando un processo ha concluso il suo compito o ha incontrato un errore non risolvibile esso può essere terminato con la funzione \func{exit} (si veda @@ -116,7 +117,7 @@ non ritorna mai (in quanto con essa viene eseguito un altro programma). In questa sezione tratteremo le funzioni per la gestione dei processi, a partire dalle funzioni elementari che permettono di leggerne gli identificatori, alle varie funzioni di manipolazione dei processi, che -riguardano la lore creazione, terminazione, e la messa in esecuzione di altri +riguardano la loro creazione, terminazione, e la messa in esecuzione di altri programmi. @@ -146,12 +147,13 @@ usando le funzioni: \funcdecl{pid\_t getpid(void)} restituisce il pid del processo corrente. \funcdecl{pid\_t getppid(void)} restituisce il pid del padre del processo corrente. - Entrambe le funzioni non riportano condizioni di errore. \end{functions} +esempi dell'uso di queste funzioni sono riportati in +\figref{fig:proc_fork_code}, nel programma di esempio \file{ForkTest.c}. Il fatto che il \acr{pid} sia un numero univoco per il sistema lo rende il -candidato ideale per generare ultieriori indicatori associati al processo di +candidato ideale per generare ulteriori indicatori associati al processo di cui diventa possibile garantire l'unicità: ad esempio la funzione \func{tmpname} (si veda \secref{sec:file_temp_file}) usa il \acr{pid} per generare un pathname univoco, che non potrà essere replicato da un'altro @@ -168,17 +170,16 @@ identificativi associati ad un processo relativi al controllo di sessione. \subsection{La funzione \func{fork}} \label{sec:proc_fork} -La funzione \func{fork} è la funzione fondamentale della gestione dei processi -in unix; come si è detto l'unico modo di creare un nuovo processo è attraverso -l'uso di questa funzione, che è quindi la base per il multitasking; il protipo -della funzione è: +La funzione \func{fork} è la funzione fondamentale della gestione dei +processi: come si è detto l'unico modo di creare un nuovo processo è +attraverso l'uso di questa funzione, essa quindi riveste un ruolo centrale +tutte le volte che si devono scrivere programmi che usano il multitasking. Il +prototipo della funzione è: \begin{functions} \headdecl{sys/types.h} \headdecl{unistd.h} - \funcdecl{pid\_t fork(void)} - Restituisce zero al padre e il \acr{pid} al figlio in caso di successo, ritorna -1 al padre (senza creare il figlio) in caso di errore; \texttt{errno} può assumere i valori: @@ -191,22 +192,33 @@ della funzione \end{errlist} \end{functions} -Dopo l'esecuzione di una \func{fork} sia il processo padre che il processo -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. +Dopo il successo dell'esecuzione di una \func{fork} sia il processo padre che +il processo 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. +dal padre o dal figlio. Si noti come la funzione \func{fork} ritorni +\textbf{due} volte: una nel padre e una nel figlio. La sola differenza che si +ha nei due processi è il valore di ritorno restituito dalla funzione, che nel +padre è il \acr{pid} del figlio mentre nel figlio è zero; in questo modo il +programma può identificare se viene eseguito dal padre o dal figlio. + +La scelta di questi valori non è casuale, un processo infatti può avere più +figli, ed il valore di ritorno di \func{fork} è l'unico modo che permette di +identificare quello appena creato; al contrario un figlio ha sempre un solo +padre (il cui \acr{pid} può sempre essere ottenuto con \func{getppid}, vedi +\secref{sec:proc_pid}) e si usa il valore nullo, che non può essere il +\acr{pid} di nessun processo. \begin{figure}[!htb] \footnotesize @@ -227,26 +239,23 @@ int main(int argc, char *argv[]) */ int nchild, i; pid_t pid; - int wait_child=0; - int wait_parent=0; - + int wait_child = 0; + int wait_parent = 0; + int wait_end = 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] + 570 pts/0 Z 0:00 [forktest ] + 571 pts/0 Z 0:00 [forktest ] + 572 pts/0 R 0:00 ps T +\end{verbatim} %$ +e come si vede, dato che non si è fatto nulla per riceverne lo stato di +terminazione, i tre processi figli sono ancora presenti pur essendosi +conclusi, con lo stato di zombie e l'indicazione che sono stati terminati. + +La possibilità di avere degli zombie deve essere tenuta sempre presente quando +si scrive un programma che deve essere mantenuto in esecuzione a lungo e +creare molti figli. In questo caso si deve sempre avere cura di far leggere +l'eventuale stato di uscita di tutti i figli (in genere questo si fa +attraverso un apposito \textit{signal handler}, che chiama la funzione +\func{wait}, vedi \secref{sec:sig_xxx} e \secref{sec:proc_wait}). Questa +operazione è necessaria perché anche se gli \textit{zombie} non consumano +risorse di memoria o processore, occupano comunque una voce nella tabella dei +processi, che a lungo andare potrebbe esaurirsi. + +Si noti che quando un processo adottato da \cmd{init} termina, esso non +diviene uno \textit{zombie}; questo perché una delle funzioni di \cmd{init} è +appunto quella di chiamare la funzione \func{wait} per i processi cui fa da +padre, completandone la terminazione. Questo è quanto avviene anche quando, +come nel caso del precedente esempio con \cmd{forktest}, il padre termina con +dei figli in stato di zombie: alla sua terminazione infatti tutti i suoi figli +vengono ereditati (compresi gli zombie) verranno adottati da \cmd{init}, il +quale provvederà a completarne la terminazione. + +Si tenga presente infine che siccome gli zombie sono processi già terminati, +non c'è modo di eliminarli con il comando \cmd{kill}; l'unica possibilità è +quella di terminare il processo che li ha generati, in modo che \cmd{init} +possa adottarli e provvedere a concludere la terminazione. + + \subsection{Le funzioni \texttt{wait} e \texttt{waitpid}} \label{sec:proc_wait} +Abbiamo già visto in precedenza come uno degli usi possibili delle capacità +multitasking di un sistema unix-like consiste nella creazione di programmi di +tipo server, in cui un processo principale attende le richieste che vengono +poi soddisfatte creando una serie di processi figli. Si è gia sottolineato +come in questo caso diventi necessario gestire esplicitamente la conclusione +dei vari processi figli; le funzioni deputate a questo sono sostanzialmente +due, \func{wait} e \func{waitpid}. La prima, il cui prototipo è: +\begin{functions} +\headdecl{sys/types.h} +\headdecl{sys/wait.h} +\funcdecl{pid\_t wait(int * status)} + +Sospende il processo corrente finché un figlio non è uscito, o finché un +segnale termina il processo o chiama una funzione di gestione. Se un figlio è +già uscito la funzione ritorna immediatamente. Al ritorno lo stato di +termininazione del processo viene salvato nella variabile puntata da +\var{status} e tutte le informazioni relative al processo (vedi +\secref{sec:proc_termination}) vengono rilasciate. + +La funzione restituisce il \acr{pid} del figlio in caso di successo e -1 in +caso di errore; \var{errno} può assumere i valori: + \begin{errlist} + \item \macro{EINTR} la funzione è stata interrotta da un segnale. + \end{errlist} +\end{functions} +è presente fin dalle prime versioni di unix; la funzione ritorna alla +conclusione del primo figlio (o immediatamente se un figlio è già uscito), nel +caso un processo abbia più figli il valore di ritorno permette di identificare +qual'è quello che è uscito. + +Questa funzione però ha il difetto di essere poco flessibile, in quanto +ritorna all'uscita di un figlio qualunque, per cui se si vuole attendere la +conclusione di un processo specifico occorre predisporre un meccanismo che +tenga conto dei processi già terminati, e ripeta la chiamata alla funzione nel +caso il processo cercato sia ancora attivo. + +Per questo motivo lo standard Posix.1 ha introdotto \func{waitpid} che +effettua lo stesso servizio, ma dispone di una serie di funzionalità più +ampie; il suo prototipo è: +\begin{functions} +\headdecl{sys/types.h} +\headdecl{sys/wait.h} +\funcdecl{pid\_t waitpid(pid\_t pid, int * status, int options)} + +La funzione restituisce il \acr{pid} del figlio che è uscito, 0 se è stata +specificata l'opzione \macro{WNOHANG} e il figlio non è uscito e -1 per un +errore, nel qual caso \var{errno} assumerà i valori: + \begin{errlist} + \item \macro{EINTR} non è stata specificata l'opzione \macro{WNOHANG} e la + funzione è stata interrotta da un segnale. + \item \macro{ECHILD} il processo specificato da \var{pid} non esiste o non è + figlio del processo chiamante. + \end{errlist} +\end{functions} + +Le differenze principali fra le due funzioni sono che \func{wait} si blocca +sempre fino a che un figlio non termina, mentre \func{waitpid} ha la +possibilità si specificare un'opzione, \macro{WNOHANG} che ne previene il +blocco, inoltre \func{waitpid} può specificare quale figlio attendere sulla +base del valore soecificato tramite la variabile \var{pid} secondo lo +specchietto riportato in \ntab: +\begin{table}[!htb] + \centering + \begin{tabular}[c]{|c|p{10cm}|} + \hline + \textbf{Valore} & \textbf{Significato}\\ + \hline + \hline + -1 & attende per un figlio qualsiasi, equivalente a \func{wait}\\ + > 0 & \\ + 0 & \\ + < -1& \\ + \hline + \end{tabular} + \caption{Significato del parametro \var{pid} della funzione \func{waitpid}.} + \label{tab:proc_waidpid_pid} +\end{table} + + + +Come abbiamo appena visto una delle azioni prese dal kernel alla terminazione +di un processo è quella di salvarne lo stato e mandare un segnale di +\macro{SIGCHLD} al padre (torneremo su questa parte in \secref{sec:sig_xxx}). + \subsection{Le funzioni \texttt{exec}} \label{sec:proc_exec} @@ -464,7 +798,7 @@ funzioni per la loro manipolazione diretta. Abbiamo già accennato in \secref{sec:intro_multiuser} ad ogni utente ed gruppo sono associati due identificatori univoci, lo \acr{uid} e il \acr{gid} che li -contraddistinguono nei confonti del kernel. Questi identificatori stanno alla +contraddistinguono nei confronti del kernel. Questi identificatori stanno alla base del sistema di permessi e protezioni di un sistema unix, e vengono usati anche nella gestione dei privilegi di accesso dei processi.