From 35a74444a2fac699a43daefd96369aa984f91870 Mon Sep 17 00:00:00 2001 From: Simone Piccardi Date: Wed, 19 Sep 2001 17:10:49 +0000 Subject: [PATCH] Aggiunti zombie, orfani, e lupi mannari. --- gapil.tex | 2 +- prochand.tex | 274 ++++++++++++++++++++++++++++++++------------- sources/ForkTest.c | 25 +++-- 3 files changed, 216 insertions(+), 85 deletions(-) diff --git a/gapil.tex b/gapil.tex index 4ea34f6..3109915 100644 --- a/gapil.tex +++ b/gapil.tex @@ -1,4 +1,4 @@ -%% +%% %% GaPiL : Guida alla Programmazione in Linux %% %% S. Piccardi Feb. 2001 diff --git a/prochand.tex b/prochand.tex index 69bdb16..b74cacd 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 @@ -146,9 +147,10 @@ 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 ulteriori indicatori associati al processo di @@ -168,10 +170,11 @@ 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 prototipo -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} @@ -189,17 +192,17 @@ 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 @@ -214,8 +217,8 @@ 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}, vista -in \secref{sec:proc_pid}) e si usa il valore nullo, che non può essere il +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] @@ -237,26 +240,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 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}, in quanto una delle funzioni di \cmd{init} è appunto +quella di chiamare \func{wait} per i processi di cui fa da padre. Questo è +quanto avviene ad esempio nel caso dell'ultimo esempio: scaduti i dieci +secondi \cmd{forktest} esce, siccome i suoi figli vengono ereditati da +\cmd{init} il quale provvederà. \subsection{Le funzioni \texttt{wait} e \texttt{waitpid}} \label{sec:proc_wait} - \subsection{Le funzioni \texttt{exec}} \label{sec:proc_exec} diff --git a/sources/ForkTest.c b/sources/ForkTest.c index d57ef7a..5401e31 100644 --- a/sources/ForkTest.c +++ b/sources/ForkTest.c @@ -26,7 +26,7 @@ * * Usage: forktest -h give all info's * - * $Id: ForkTest.c,v 1.4 2001/09/14 22:16:41 piccardi Exp $ + * $Id: ForkTest.c,v 1.5 2001/09/19 17:10:49 piccardi Exp $ * ****************************************************************/ /* @@ -48,14 +48,15 @@ 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; /* * Input section: decode command line parameters * Use getopt function */ opterr = 0; /* don't want writing to stderr */ - while ( (i = getopt(argc, argv, "hp:c:")) != -1) { + while ( (i = getopt(argc, argv, "hp:c:e:")) != -1) { switch (i) { /* * Handling options @@ -66,10 +67,13 @@ int main(int argc, char *argv[]) return -1; break; case 'c': /* take wait time for childen */ - wait_child=strtol(optarg, NULL, 10); /* convert input */ + wait_child = strtol(optarg, NULL, 10); /* convert input */ break; case 'p': /* take wait time for childen */ - wait_parent=strtol(optarg, NULL, 10); /* convert input */ + wait_parent = strtol(optarg, NULL, 10); /* convert input */ + break; + case 'e': /* take wait before parent exit */ + wait_end = strtol(optarg, NULL, 10); /* convert input */ break; case '?': /* unrecognized options */ printf("Unrecognized options -%c\n",optopt); @@ -101,7 +105,7 @@ int main(int argc, char *argv[]) if (pid == 0) { /* child */ printf("Child %d successfully executing\n", ++i); if (wait_child) sleep(wait_child); - printf("Child %d exiting\n", i); + printf("Child %d, parent %d, exiting\n", i, getppid()); exit(0); } else { /* parent */ printf("Spawned %d child, pid %d \n", i+1, pid); @@ -110,6 +114,7 @@ int main(int argc, char *argv[]) } } /* normal exit */ + if (wait_end) sleep(wait_end); return 0; } /* @@ -118,7 +123,11 @@ int main(int argc, char *argv[]) void usage(void) { printf("Program forktest: fork a given number of child \n"); printf("Usage:\n"); - printf(" forktest [-h] child to fork \n"); + printf(" forktest [-h] [-p sec] [-c sec] [-e sec] child to fork \n"); printf(" -h print this help\n"); + printf(" -p sec wait sec seconds before next fork\n"); + printf(" -c sec wait sec seconds before child termination\n"); + printf(" -e sec wait sec seconds before parent return\n"); + exit(1); } -- 2.30.2