\chapter{La gestione dei processi}
\label{cha:process_handling}
-Come accennato nell'introduzione in un sistema Unix ogni attività del sistema
-viene svolta tramite i processi. In sostanza i processi costituiscono l'unità
-base per l'allocazione e l'uso delle risorse del sistema.
+Come accennato nell'introduzione in un sistema Unix tutte le operazioni
+vengono svolte tramite opportuni processi. In sostanza questi ultimi vengono
+a costituire l'unità base per l'allocazione e l'uso delle risorse del sistema.
Nel precedente capitolo abbiamo esaminato il funzionamento di un processo come
unità a se stante, in questo esamineremo il funzionamento dei processi
all'interno del sistema. Saranno cioè affrontati i dettagli della creazione e
-della distruzione dei processi, della gestione dei loro attributi e privilegi,
-e di tutte le funzioni a questo connesse. Infine nella sezione finale
-affronteremo alcune problematiche generiche della programmazione in ambiente
-multitasking.
+della terminazione dei processi, della gestione dei loro attributi e
+privilegi, e di tutte le funzioni a questo connesse. Infine nella sezione
+finale introdurremo alcune problematiche generiche della programmazione in
+ambiente multitasking.
kernel, (come \cmd{keventd}, \cmd{kswapd}, etc.)} si possono classificare i
processi con la relazione padre/figlio in una organizzazione gerarchica ad
albero, in maniera analoga a come i file sono organizzati in un albero di
-directory (si veda \secref{sec:file_file_struct}); in \curfig\ si è mostrato il
-risultato del comando \cmd{pstree} che permette di mostrare questa struttura,
-alla cui base c'è \cmd{init} che è progenitore di tutti gli altri processi.
+directory (si veda \secref{sec:file_organization}); in \curfig\ si è mostrato
+il risultato del comando \cmd{pstree} che permette di mostrare questa
+struttura, alla cui base c'è \cmd{init} che è progenitore di tutti gli altri
+processi.
Il kernel mantiene una tabella dei processi attivi, la cosiddetta
figlio questo deve essere specificato subito dopo la \func{fork} chiamando la
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 figlio.
+abbastanza limitata sulle cause della terminazione del processo figlio.
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
Il \acr{pid} viene assegnato in forma progressiva ogni volta che un nuovo
processo viene creato, fino ad un limite massimo (in genere essendo detto
numero memorizzato in un intero a 16 bit si arriva a 32767) oltre il quale si
-riparte dal numero più basso disponibile (FIXME: verificare, non sono sicuro).
-Per questo motivo processo il processo di avvio (\cmd{init}) ha sempre il
-\acr{pid} uguale a uno.
+riparte dal numero più basso disponibile\footnote{FIXME: verificare, non sono
+ sicuro}. Per questo motivo processo il processo di avvio (\cmd{init}) ha
+sempre il \acr{pid} uguale a uno.
Tutti i processi inoltre memorizzano anche il \acr{pid} del genitore da cui
sono stati creati, questo viene chiamato in genere \acr{ppid} (da
\noindent 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
-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
-processo che usi la stessa funzione.
+Il fatto che il \acr{pid} sia un numero univoco per il sistema lo rende un
+candidato per generare ulteriori indicatori associati al processo di cui
+diventa possibile garantire l'unicità: ad esempio in alcune implementazioni 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
+processo che usi la stessa funzione.
Tutti i processi figli dello stesso processo padre sono detti
\textit{sibling}, questa è una delle relazioni usate nel \textsl{controllo di
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
+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 della creazione di
- un nuovo processo}, pertanto padre e figlio vedono variabili diverse.
+ un nuovo processo.} 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 \func{fork} è il \acr{pid} del processo figlio, mentre
sul numero totale di processi permessi all'utente (vedi \secref{sec:sys_xxx}).
L'uso di \func{fork} avviene secondo due modalità principali; la prima è
-quella in cui all'interno di un programma si creano processi figli per
-affidargli l'esecuzione di una certa sezione di codice, mentre il processo
-padre ne esegue un'altra. È il caso tipico dei server di rete in cui il padre
-riceve ed accetta le richieste da parte dei client, per ciascuna delle quali
-pone in esecuzione un figlio che è incaricato di fornire il servizio.
+quella in cui all'interno di un programma si creano processi figli cui viene
+affidata l'esecuzione di una certa sezione di codice, mentre il processo padre
+ne esegue un'altra. È il caso tipico dei server di rete in cui il padre riceve
+ed accetta le richieste da parte dei client, per ciascuna delle quali pone in
+esecuzione un figlio che è incaricato di fornire il servizio.
La seconda modalità è quella in cui il processo vuole eseguire un altro
programma; questo è ad esempio il caso della shell. In questo caso il processo
le opzioni a riga di comando, è disponibile nel file \file{ForkTest.c}.
Decifrato il numero di figli da creare, il ciclo principale del programma
-(\texttt{\small 28--40}) esegue in successione la creazione dei processi figli
+(\texttt{\small 24--40}) esegue in successione la creazione dei processi figli
controllando il successo della chiamata a \func{fork} (\texttt{\small
- 29--31}); ciascun figlio (\texttt{\small 29--31}) si limita a stampare il
+ 25--29}); ciascun figlio (\texttt{\small 31--34}) si limita a stampare il
suo numero di successione, eventualmente attendere il numero di secondi
specificato e scrivere un messaggio prima di uscire. Il processo padre invece
-(\texttt{\small 29--31}) stampa un messaggio di creazione, eventualmente
+(\texttt{\small 36--38}) stampa un messaggio di creazione, eventualmente
attende il numero di secondi specificato, e procede nell'esecuzione del ciclo;
alla conclusione del ciclo, prima di uscire, può essere specificato un altro
periodo di attesa.
Esaminiamo questo risultato: una prima conclusione che si può trarre è non si
può dire quale processo fra il padre ed il figlio venga eseguito per
-primo\footnote{anche se nel kernel 2.4.x era stato introdotto un meccanismo
- che metteva in esecuzione sempre il xxx per primo (TODO recuperare le
- informazioni esatte)} dopo la chiamata a \func{fork}; dall'esempio si può
-notare infatti come nei primi due cicli sia stato eseguito per primo il padre
-(con la stampa del \acr{pid} del nuovo processo) per poi passare
-all'esecuzione del figlio (completata con i due avvisi di esecuzione ed
-uscita), e tornare all'esecuzione del padre (con la stampa del passaggio al
-ciclo successivo), mentre la terza volta è stato prima eseguito il figlio
-(fino alla conclusione) e poi il padre.
+primo\footnote{a partire dal kernel 2.5.2-pre10 è stato introdotto il nuovo
+ scheduler di Ingo Molnar che esegue sempre per primo il figlio; per
+ mantenere la portabilità è opportuno non fare comunque affidamento su questo
+ comportamento} dopo la chiamata a \func{fork}; dall'esempio si può notare
+infatti come nei primi due cicli sia stato eseguito per primo il padre (con la
+stampa del \acr{pid} del nuovo processo) per poi passare all'esecuzione del
+figlio (completata con i due avvisi di esecuzione ed uscita), e tornare
+all'esecuzione del padre (con la stampa del passaggio al ciclo successivo),
+mentre la terza volta è stato prima eseguito il figlio (fino alla conclusione)
+e poi il padre.
In generale l'ordine di esecuzione dipenderà, oltre che dall'algoritmo di
scheduling usato dal kernel, dalla particolare situazione in si trova la
Pertanto non si può fare nessuna assunzione sulla sequenza di esecuzione delle
istruzioni del codice fra padre e figli, nè sull'ordine in cui questi potranno
-essere messi in esecuzione, e se è necessaria una qualche forma di precedenza
+essere messi in esecuzione. Se è necessaria una qualche forma di precedenza
occorrerà provvedere ad espliciti meccanismi di sincronizzazione, pena il
rischio di incorrere nelle cosiddette \textit{race condition} \index{race
condition} (vedi \secref{sec:proc_race_cond}.
Si noti inoltre che essendo i segmenti di memoria utilizzati dai singoli
processi completamente separati, le modifiche delle variabili nei processi
-figli (come l'incremento di \var{i} in \texttt{\small 33}) sono visibili solo
+figli (come l'incremento di \var{i} in \texttt{\small 31}) sono visibili solo
a loro, e non hanno alcun effetto sul valore che le stesse variabili hanno nel
processo padre (ed in eventuali altri processi figli che eseguano lo stesso
codice).
in gran dettaglio in \capref{cha:file_unix_interface} e in
\secref{cha:files_std_interface}. Qui basta accennare che si sono usate le
funzioni standard della libreria del C che prevedono l'output bufferizzato; e
-questa bufferizzazione (di veda \secref{sec:file_buffering}) 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 carattere di a capo).
+questa bufferizzazione (trattata in dettaglio in \secref{sec:file_buffering})
+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 carattere di 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 subito dopo
l'esecuzione della \func{printf}. Ma con la redirezione su file la scrittura
-non avviene più alla fine di ogni riga e l'output resta nel buffer. Per questo
-motivo, 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 del figlio il buffer
-viene scritto su disco, troveremo nel file anche tutto quello che il processo
-padre aveva scritto prima della sua creazione. E solo alla fine del file,
-dato che in questo caso il padre esce per ultimo, troveremo 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.
+non avviene più alla fine di ogni riga e l'output resta nel buffer. 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 il buffer viene scritto su disco all'uscita del
+figlio, troveremo nel file anche tutto quello che il processo padre aveva
+scritto prima della sua creazione. E alla fine del file (dato che in questo
+caso il padre esce per ultimo) troveremo anche l'output completo del padre.
+
+L'esempio ci mostra un'altro aspetto fondamentale dell'interazione con i file,
+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 è 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
\item gli identificatori per il controllo di accesso: il \textit{real user
id}, il \textit{real group id}, l'\textit{effective user id},
l'\textit{effective group id} ed i \textit{supplementary group id} (vedi
- \secref{sec:proc_user_group}).
+ \secref{sec:proc_access_id}).
\item gli identificatori per il controllo di sessione: il \textit{process
group id} e il \textit{session id} ed il terminale di controllo (vedi
\secref{sec:sess_xxx} e \secref{sec:sess_xxx}).
-\item gli identificatori per il controllo di accesso (vedi
- \secref{sec:proc_user_group}).
\item la directory di lavoro e la directory radice (vedi
\secref{sec:file_work_dir} e \secref{sec:file_chroot}).
\item la maschera dei permessi di creazione (vedi \secref{sec:file_umask}).
Questa funzione è un rimasuglio dei vecchi tempi in cui eseguire una
\func{fork} comportava anche la copia completa del segmento dati del processo
padre, che costituiva un inutile appesantimento in tutti quei casi in cui la
-\func{fork} veniva fatto solo per poi eseguire una \func{exec}. La funzione
+\func{fork} veniva fatta solo per poi eseguire una \func{exec}. La funzione
venne introdotta in BSD per migliorare le prestazioni.
Dato che Linux supporta il \textit{copy on write} la perdita di prestazioni è
\label{sec:proc_termination}
In \secref{sec:proc_conclusion} abbiamo già affrontato le modalità con cui
-chiudere un programma, ma dal punto di vista del programma stesso; avendo a
-che fare con un sistema multitasking occorre adesso affrontare l'argomento dal
-punto di vista generale di come il sistema gestisce la conclusione dei
-processi.
+chiudere un programma, ma dall'interno del programma stesso; avendo a che fare
+con un sistema multitasking resta da affrontare l'argomento dal punto di vista
+di come il sistema gestisce la conclusione dei processi.
-Abbiamo già visto in \secref{sec:proc_conclusion} le tre modalità con cui un
+Abbiamo visto in \secref{sec:proc_conclusion} le tre modalità con cui un
programma viene terminato in maniera normale: la chiamata di \func{exit} (che
esegue le funzioni registrate per l'uscita e chiude gli stream), il ritorno
dalla funzione \func{main} (equivalente alla chiamata di \func{exit}), e la
chiamata ad \func{\_exit} (che passa direttamente alle operazioni di
terminazione del processo da parte del kernel).
-Ma oltre alla conclusione normale abbiamo accennato che esistono anche delle
+Ma abbiamo accennato che oltre alla conclusione normale esistono anche delle
modalità di conclusione anomala; queste sono in sostanza due: il programma può
chiamare la funzione \func{abort} per invocare una chiusura anomala, o essere
terminato da un segnale. In realtà anche la prima modalità si riconduce alla
inviati in successione i segnali \macro{SIGHUP} e \macro{SIGCONT}
(vedi \secref{sec:sess_xxx}).
\end{itemize*}
-ma al di la di queste operazioni è necessario poter disporre di un meccanismo
-ulteriore che consenta di sapere come questa terminazione è avvenuta; dato che
-in un sistema unix-like tutto viene gestito attraverso i processi il
-meccanismo scelto consiste nel riportare lo stato di terminazione (il
-cosiddetto \textit{termination status}) al processo padre.
+
+Oltre queste operazioni è però necessario poter disporre di un meccanismo
+ulteriore che consenta di sapere come la terminazione è avvenuta: dato che in
+un sistema unix-like tutto viene gestito attraverso i processi il meccanismo
+scelto consiste nel riportare lo stato di terminazione (il cosiddetto
+\textit{termination status}) al processo padre.
Nel caso di conclusione normale, abbiamo visto in \secref{sec:proc_conclusion}
che lo stato di uscita del processo viene caratterizzato tramite il valore del
terminato (si potrebbe avere cioè quello che si chiama un processo
\textsl{orfano}).
-Questa complicazione viene superata facendo in modo che il processo figlio
-venga \textsl{adottato} da \cmd{init}: come già accennato quando un processo
+Questa complicazione viene superata facendo in modo che il processo orfano
+venga \textsl{adottato} da \cmd{init}. Come già accennato quando un processo
termina il kernel controlla se è il padre di altri processi in esecuzione: in
caso positivo allora il \acr{ppid} di tutti questi processi viene sostituito
con il \acr{pid} di \cmd{init} (e cioè con 1); in questo modo ogni processo
-avrà sempre un padre (nel caso \textsl{adottivo}) cui riportare il suo stato
-di terminazione. Come verifica di questo comportamento possiamo eseguire il
-comando \cmd{forktest} imponendo a ciascun processo figlio due
-secondi di attesa prima di uscire, il risultato è:
+avrà sempre un padre (nel caso possiamo parlare di un padre \textsl{adottivo})
+cui riportare il suo stato di terminazione. Come verifica di questo
+comportamento possiamo eseguire il nostro programma \cmd{forktest} imponendo a
+ciascun processo figlio due secondi di attesa prima di uscire, il risultato è:
\footnotesize
\begin{verbatim}
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.
+(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à usciti, 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.
+c'è modo di eliminarli con il comando \cmd{kill}; l'unica possibilità di
+cancellarli dalla tabella dei processi è quella di terminare il processo che
+li ha generati, in modo che \cmd{init} possa adottarli e provvedere a
+concluderne la terminazione.
\subsection{Le funzioni \func{wait} e \func{waitpid}}
\label{sec:proc_wait}
-Abbiamo già accennato come uno degli usi possibili delle capacità multitasking
-di un sistema unix-like consista 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 è già sottolineato al paragrafo
-precedente come in questo caso diventi necessario gestire esplicitamente la
-conclusione dei vari processi figli onde evitare di riempire di
-\textit{zombie} la tabella dei processi; le funzioni deputate a questo compito
-sono sostanzialmente due, \func{wait} e \func{waitpid}. La prima, il cui
-prototipo è:
+Uno degli usi più comuni 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 da una serie di
+processi figli. Si è già sottolineato al paragrafo precedente come in questo
+caso diventi necessario gestire esplicitamente la conclusione dei figli onde
+evitare di riempire di \textit{zombie} la tabella dei processi; le funzioni
+deputate a questo compito sono sostanzialmente due, \func{wait} e
+\func{waitpid}. La prima, il cui prototipo è:
\begin{functions}
\headdecl{sys/types.h}
\headdecl{sys/wait.h}
\end{errlist}}
\end{functions}
\noindent
-è presente fin dalle prime versioni di unix; la funzione ritorna non appena un
+è presente fin dalle prime versioni di Unix; la funzione ritorna non appena un
processo figlio termina. Se un figlio è già terminato la funzione ritorna
immediatamente.
\begin{errlist}
\item[\macro{EINTR}] se 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.
+ \item[\macro{ECHILD}] il processo specificato da \param{pid} non esiste o
+ non è figlio del processo chiamante.
\end{errlist}}
\end{functions}
sempre fino a che un processo 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 processo attendere sulla
-base del valore specificato tramite la variabile \var{pid}, secondo lo
+base del valore fornito dall'argomento \param{pid}, secondo lo
specchietto riportato in \ntab:
\begin{table}[!htb]
\centering
\label{tab:proc_waidpid_pid}
\end{table}
-Il comportamento di \func{waitpid} può essere modificato passando delle
-opportune opzioni tramite la variabile \var{option}. I valori possibili sono
-il già citato \macro{WNOHANG}, che previene il blocco della funzione quando il
-processo figlio non è terminato, e \macro{WUNTRACED} (usata per il controllo
-di sessione, trattato in \capref{cha:session}) che fa ritornare la funzione
-anche per i processi figli che sono bloccati ed il cui stato non è stato
-ancora riportato al padre. Il valore dell'opzione deve essere specificato come
-maschera binaria ottenuta con l'OR delle suddette costanti con zero.
+Il comportamento di \func{waitpid} può inoltre essere modificato passando
+delle opportune opzioni tramite l'argomento \param{option}. I valori possibili
+sono il già citato \macro{WNOHANG}, che previene il blocco della funzione
+quando il processo figlio non è terminato, e \macro{WUNTRACED} (usata per il
+controllo di sessione, trattato in \capref{cha:session}) che fa ritornare la
+funzione anche per i processi figli che sono bloccati ed il cui stato non è
+stato ancora riportato al padre. Il valore dell'opzione deve essere
+specificato come maschera binaria ottenuta con l'OR delle suddette costanti
+con zero.
La terminazione di un processo figlio è chiaramente un evento asincrono
rispetto all'esecuzione di un programma e può avvenire in un qualunque
-momento, per questo motivo, come si è visto nella sezione precedente, una
-delle azioni prese dal kernel alla conclusione di un processo è quella di
-mandare un segnale di \macro{SIGCHLD} al padre. Questo segnale viene ignorato
-di default, ma costituisce il meccanismo di comunicazione asincrona con cui il
-kernel avverte un processo padre che uno dei suoi figli è terminato.
+momento. Per questo motivo, come accennato nella sezione precedente, una delle
+azioni prese dal kernel alla conclusione di un processo è quella di mandare un
+segnale di \macro{SIGCHLD} al padre. L'azione di default (si veda
+\secref{sec:sig_base}) per questo segnale è di essere ignorato, ma la sua
+generazione costituisce il meccanismo di comunicazione asincrona con cui il
+kernel avverte il processo padre che uno dei suoi figli è terminato.
In genere in un programma non si vuole essere forzati ad attendere la
conclusione di un processo per proseguire, specie se tutto questo serve solo
per leggerne lo stato di chiusura (ed evitare la presenza di \textit{zombie}),
per questo la modalità più usata per chiamare queste funzioni è quella di
utilizzarle all'interno di un \textit{signal handler} (torneremo sui segnali e
-su come gestire \macro{SIGCHLD} in \secref{sec:sig_sigwait_xxx}) nel qual
-caso, dato che il segnale è generato dalla terminazione un figlio, avremo la
-certezza che la chiamata a \func{wait} non si bloccherà.
+su come gestire \macro{SIGCHLD} in \secref{sec:sig_sigwait_xxx}). In questo
+caso infatti, dato che il segnale è generato dalla terminazione un figlio,
+avremo la certezza che la chiamata a \func{wait} non si bloccherà.
\begin{table}[!htb]
\centering
\end{table}
Entrambe le funzioni di attesa restituiscono lo stato di terminazione del
-processo tramite il puntatore \var{status} (se non interessa memorizzare lo
+processo tramite il puntatore \param{status} (se non interessa memorizzare lo
stato si può passare un puntatore nullo). Il valore restituito da entrambe le
-funzioni dipende dall'implementazione, e tradizionalmente alcuni bit sono
-riservati per memorizzare lo stato di uscita (in genere 8) altri per indicare
-il segnale che ha causato la terminazione (in caso di conclusione anomala),
-uno per indicare se è stato generato un core file, ecc\footnote{le
- definizioni esatte si possono trovare in \file{<bits/waitstatus.h} ma questo
- file non deve mai essere usato direttamente, esso viene incluso attraverso
- \file{<sys/wait.h>}}. Lo standard POSIX.1 definisce una serie di macro di
-preprocessore da usare per analizzare lo stato di uscita; esse sono definite
-sempre in \file{<sys/wait.h>} ed elencate in \curtab\ (si tenga presente che
-queste macro prendono come parametro la variabile di tipo \type{int} puntata
-da \var{status}).
+funzioni dipende dall'implementazione, e tradizionalmente alcuni bit (in
+genere 8) sono riservati per memorizzare lo stato di uscita, e altri per
+indicare il segnale che ha causato la terminazione (in caso di conclusione
+anomala), uno per indicare se è stato generato un core file, ecc.\footnote{le
+ definizioni esatte si possono trovare in \file{<bits/waitstatus.h>} ma
+ questo file non deve mai essere usato direttamente, esso viene incluso
+ attraverso \file{<sys/wait.h>}.}
+
+Lo standard POSIX.1 definisce una serie di macro di preprocessore da usare per
+analizzare lo stato di uscita. Esse sono definite sempre in
+\file{<sys/wait.h>} ed elencate in \curtab\ (si tenga presente che queste
+macro prendono come parametro la variabile di tipo \type{int} puntata da
+\var{status}).
Si tenga conto che nel caso di conclusione anomala il valore restituito da
\macro{WTERMSIG} può essere controllato contro le costanti definite in
\label{sec:proc_wait4}
Linux, seguendo una estensione di BSD, supporta altre due funzioni per la
-lettura dello stato di terminazione di un processo, analoghe a \func{wait} e
-\func{waitpid}, ma che prevedono un ulteriore parametro attraverso il quale il
-kernel può restituire al padre informazioni sulle risorse usate dal processo
-terminato e dai vari figli. Queste funzioni, che diventano accessibili
-definendo la costante \macro{\_USE\_BSD}, sono:
+lettura dello stato di terminazione di un processo \func{wait3} e
+\func{wait4}, analoghe alle precedenti ma che prevedono un ulteriore
+parametro attraverso il quale il kernel può restituire al padre informazioni
+sulle risorse usate dal processo terminato e dai vari figli. I prototipi di
+queste funzioni, che diventano accessibili definendo la costante
+\macro{\_USE\_BSD}, sono:
\begin{functions}
\headdecl{sys/times.h}
\headdecl{sys/types.h}
\noindent
la struttura \type{rusage} è definita in \file{sys/resource.h}, e viene
utilizzata anche dalla funzione \func{getrusage} (vedi \secref{sec:sys_xxx})
-per ottenere le risorse di sistema usate dal processo; la sua definizione è
+per ottenere le risorse di sistema usate da un processo; la sua definizione è
riportata in \figref{fig:sys_rusage_struct}.
In genere includere esplicitamente \file{<sys/time.h>} non è più
\begin{errlist}
\item[\macro{EACCES}] il file non è eseguibile, oppure il filesystem è
montato in \cmd{noexec}, oppure non è un file normale o un interprete.
- \item[\macro{EPERM}] il file ha i bit \acr{suid} o \acr{sgid} ma l'utente non
- è root o il filesystem è montato con \cmd{nosuid}, oppure
+ \item[\macro{EPERM}] il file ha i bit \acr{suid} o \acr{sgid}, l'utente non
+ è root, e o il processo viene tracciato, o il filesystem è montato con
+ l'opzione \cmd{nosuid}.
\item[\macro{ENOEXEC}] il file è in un formato non eseguibile o non
riconosciuto come tale, o compilato per un'altra architettura.
\item[\macro{ENOENT}] il file o una delle librerie dinamiche o l'interprete
\item il \textit{process id} (\acr{pid}) ed il \textit{parent process id}
(\acr{ppid}).
\item il \textit{real user id} ed il \textit{real group id} (vedi
- \secref{sec:proc_user_group}).
-\item i \textit{supplementary group id} (vedi \secref{sec:proc_user_group}).
+ \secref{sec:proc_access_id}).
+\item i \textit{supplementary group id} (vedi \secref{sec:proc_access_id}).
\item il \textit{session id} ed il \textit{process group id} (vedi
\secref{sec:sess_xxx}).
\item il terminale di controllo (vedi \secref{sec:sess_xxx}).
può anche non essere resettato a \macro{SIG\_DFL} (si veda
\secref{sec:sig_xxx}).
-La gestione dei file aperti dipende dal valore del flag di
-\textit{close-on-exec} per ciascun file descriptor (si veda
-\secref{sec:file_fcntl}); i file per cui è settato vengono chiusi, tutti gli
-altri file restano aperti. Questo significa che il comportamento di default è
-che i file restano aperti attraverso una \func{exec}, a meno di una chiamata
-esplicita a \func{fcntl} che setti il suddetto flag.
+La gestione dei file aperti dipende dal valore che ha il flag di
+\textit{close-on-exec} (trattato in \secref{sec:file_fcntl}) per ciascun file
+descriptor. I file per cui è settato vengono chiusi, tutti gli altri file
+restano aperti. Questo significa che il comportamento di default è che i file
+restano aperti attraverso una \func{exec}, a meno di una chiamata esplicita a
+\func{fcntl} che setti il suddetto flag.
Per le directory lo standard POSIX.1 richiede che esse vengano chiuse
attraverso una \func{exec}, in genere questo è fatto dalla funzione
Abbiamo detto che il \textit{real user id} ed il \textit{real group id}
restano gli stessi all'esecuzione di \func{exec}; lo stesso vale per
-l'\textit{effective user id} ed l'\textit{effective group id}, tranne il caso
-in cui il file che si va ad eseguire ha o il \acr{suid} bit o lo \acr{sgid}
-bit settato, nel qual caso \textit{effective user id} e \textit{effective
+l'\textit{effective user id} ed l'\textit{effective group id}, tranne quando
+il file che si va ad eseguire abbia o il \acr{suid} bit o lo \acr{sgid} bit
+settato, in questo caso l'\textit{effective user id} e l'\textit{effective
group id} vengono settati rispettivamente all'utente o al gruppo cui il file
appartiene (per i dettagli vedi \secref{sec:proc_perms}).
In questa sezione esamineremo le problematiche relative al controllo di
accesso dal punto di vista del processi; vedremo quali sono gli identificatori
usati, come questi possono essere modificati nella creazione e nel lancio di
-nuovi processi, e le varie funzioni per la loro manipolazione diretta e tutte
-le problematiche connesse ad una gestione accorta dei privilegi.
+nuovi processi, le varie funzioni per la loro manipolazione diretta e tutte le
+problematiche connesse ad una gestione accorta dei privilegi.
-\subsection{Utente e gruppo di un processo}
-\label{sec:proc_user_group}
+\subsection{Gli identificatori del controllo di accesso}
+\label{sec:proc_access_id}
Come accennato in \secref{sec:intro_multiuser} il modello base\footnote{in
realtà già esistono estensioni di questo modello base, che lo rendono più
anche poter identificare chi è che ha lanciato un certo programma, e pertanto
anche a ciascun processo è associato un utente e a un gruppo.
-Un semplice controllo di una corrispondenza fra identificativi però non
-garantisce però sufficiente flessibilità per tutti quei casi in cui è
-necessario poter disporre di privilegi diversi, o dover impersonare un altro
-utente per un limitato insieme di operazioni. Per questo motivo in generale
-tutti gli Unix prevedono che i processi abbiano almeno due gruppi di
-identificatori, chiamati rispettivamente \textit{real} ed \textit{effective}.
+Un semplice controllo di una corrispondenza fra identificativi non garantisce
+però sufficiente flessibilità per tutti quei casi in cui è necessario poter
+disporre di privilegi diversi, o dover impersonare un altro utente per un
+limitato insieme di operazioni. Per questo motivo in generale tutti gli Unix
+prevedono che i processi abbiano almeno due gruppi di identificatori, chiamati
+rispettivamente \textit{real} ed \textit{effective}.
\begin{table}[htb]
\footnotesize
l'identificazione dell'utente e normalmente non vengono mai cambiati. In
realtà vedremo (in \secref{sec:proc_setuid}) che è possibile modificarli, ma
solo ad un processo che abbia i privilegi di amministratore; questa
-possibilità è usata ad esempio da \cmd{login} che una volta completata la
-procedura di autenticazione lancia una shell per la quale setta questi
+possibilità è usata ad esempio da \cmd{login} che, una volta completata la
+procedura di autenticazione, lancia una shell per la quale setta questi
identificatori ai valori corrispondenti all'utente che entra nel sistema.
Al secondo gruppo appartengono l'\textit{effective user id} e
\secref{sec:proc_exec}, il programma che si è posto in esecuzione abbia i bit
\acr{suid} o \acr{sgid} settati (il significato di questi bit è affrontato in
dettaglio in \secref{sec:file_suid_sgid}). In questo caso essi saranno settati
-all'utente e al gruppo proprietari del file; questo consente, per programmi in
+all'utente e al gruppo proprietari del file. Questo consente, per programmi in
cui ci sia necessità, di dare a qualunque utente normale privilegi o permessi
di un'altro (o dell'amministratore).
Le due funzioni che vengono usate per cambiare identità (cioè utente e gruppo
di appartenenza) ad un processo sono rispettivamente \func{setuid} e
-\func{setgid}; come accennato in \secref{sec:proc_user_group} in Linux esse
+\func{setgid}; come accennato in \secref{sec:proc_access_id} in Linux esse
seguono la semantica POSIX che prevede l'esistenza del \textit{saved user id}
e del \textit{saved group id}; i loro prototipi sono:
\begin{functions}
Il funzionamento di queste due funzioni è analogo, per cui considereremo solo
la prima; la seconda si comporta esattamente allo stesso modo facendo
riferimento al \textit{group id} invece che all'\textit{user id}. Gli
-eventuali \textit{supplementary group id} non vengono modificati da nessuna
-delle funzioni che tratteremo in questa sezione.
+eventuali \textit{supplementary group id} non vengono modificati.
L'effetto della chiamata è diverso a seconda dei privilegi del processo; se
programma, effettuare il lavoro che non necessita di privilegi aggiuntivi, ed
eventualmente tornare indietro.
-Come esempio per chiarire dell'uso di queste funzioni prediamo quello con cui
+Come esempio per chiarire dell'uso di queste funzioni prendiamo quello con cui
viene gestito l'accesso al file \file{/var/log/utmp}. In questo file viene
registrato chi sta usando il sistema al momento corrente; chiaramente non può
essere lasciato aperto in scrittura a qualunque utente, che potrebbe
falsificare la registrazione. Per questo motivo questo file (e l'analogo
\file{/var/log/wtmp} su cui vengono registrati login e logout) appartengono ad
un gruppo dedicato (\acr{utmp}) ed i programmi che devono accedervi (ad
-esempio tutti i programmi di terminale in X, o il programma \cmd{screen}
-che crea terminali multipli su una console) appartengono a questo gruppo ed
-hanno il bit \acr{sgid} settato.
+esempio tutti i programmi di terminale in X, o il programma \cmd{screen} che
+crea terminali multipli su una console) appartengono a questo gruppo ed hanno
+il bit \acr{sgid} settato.
Quando uno di questi programmi (ad esempio \cmd{xterm}) viene lanciato la
situazione degli identificatori è la seguente:
\label{sec:proc_setreuid}
Queste due funzioni derivano da BSD che, non supportando\footnote{almeno fino
- alla versione 4.3+BSD TODO, verificare e aggiornare la nota.} i
+ alla versione 4.3+BSD TODO, FIXME verificare e aggiornare la nota.} i
\textit{saved id}, le usava per poter scambiare fra di loro \textit{effective}
e \textit{real id}. I loro prototipi sono:
\begin{functions}
Queste funzioni sono usate per settare gli identificatori usati da Linux per
il controllo dell'accesso ai file. Come già accennato in
-\secref{sec:proc_user_group} in Linux è definito questo ulteriore gruppo di
+\secref{sec:proc_access_id} in Linux è definito questo ulteriore gruppo di
identificatori, che di norma sono assolutamente equivalenti agli
\textit{effective id}, dato che ogni cambiamento di questi ultimi viene
immediatamente riportato sui \textit{filesystem id}.
restituisce 0 in caso di successo e -1 in caso di fallimento.}
\end{functions}
\noindent la funzione esegue una scansione del database dei gruppi (si veda
-\secref{sec:sys_xxx}) e ritorna in \param{groups} la lista di quelli a cui
-l'utente appartiene. Si noti che \param{ngroups} è passato come puntatore
+\secref{sec:sys_user_group}) e ritorna in \param{groups} la lista di quelli a
+cui l'utente appartiene. Si noti che \param{ngroups} è passato come puntatore
perché qualora il valore specificato sia troppo piccolo la funzione ritorna -1
e passando indietro il numero dei gruppi trovati.
\label{sec:proc_priority}
In questa sezione tratteremo più approfonditamente i meccanismi con il quale
-lo \textit{scheduler} assegna la CPU ai vari processi attivi, illustrando le
-varie funzioni che permettono di leggere e modificare le priorità di
-esecuzione dei programmi.
-
+lo \textit{scheduler}\footnote{che è la parte del kernel che si occupa di
+ stabilire quale processo dovrà essere posto in esecuzione.} assegna la CPU
+ai vari processi attivi. In particolare prendremo in esame i vari meccanismi
+con cui viene gestita l'assgnazione del tempo di CPU, ed illustreremo le varie
+funzioni di gestione.
+
+
+\subsection{I meccanismi di \textit{scheduling}}
+\label{sec:proc_sched}
+
+La scelta di un meccanismo che sia in grado di distribuire in maniera efficace
+il tempo di CPU per l'esecuzione dei processi è sempre una questione delicata,
+ed oggetto di numerose ricerche; in ogni caso essa dipende in maniera
+essenziale anche dal tipo di utilizzo che deve essere fatto del sistema.
+
+La cosa è resa ancora più complicata dal fatto che con sistemi
+multi-processore si introduce anche la complessità dovuta alla scelta di quale
+sia la CPU più opportuna da utilizzare.\footnote{nei processori moderni la
+ presenza di ampie cache può rendere poco efficiente trasferire l'esecuzione
+ di un processo da una CPU ad un'altra, per cui occorrono meccanismi per
+ determininare quale è la migliore scelta fra le diverse CPU.} Tutto questo
+comunque appartiene alle sottigliezze dell'implementazione del kernel, e dal
+punto di vista dei programmi che girano in user space di può pensare sempre
+alla risorsa tempo di esecuzione, governata dagli stessi mecca, che nel caso
+di più processori sarà a disposizione di più di un processo alla volta.
+
+Si tenga presente inoltre che l'utilizzo della CPU è soltanto una delle
+risorse (insieme alla memoria e all'accesso alle periferiche) che sono
+necessarie per l'esecuzione di un programma, e spesso non è neanche la più
+importante. Per questo non è affatto detto che dare ad un programma la massima
+priorità di esecuzione abbia risultati significativi in termini di
+prestazioni.
+
+La politica tradizionale di scheduling di Unix (che tratteremo in
+\secref{sec:proc_sched_stand}) è sempre stata basata su delle priorità
+dinamiche, che assicurassaro che tutti i processi, anche i meno importanti,
+potessero ricevere un po' di tempo di CPU.
+
+Lo standard POSIX però per tenere conto dei sistemi real-time,\footnote{per
+ sistema real-time si intende un sistema in grado di eseguire operazioni in
+ tempo reale; in genere si tende a distinguere fra l'\textit{hard real-time}
+ in cui è necessario che i tempi di esecuzione di un programma siano
+ determinabili con certezza assoluta, come nel caso di meccanismi di
+ controllo di macchine, dove uno sforamento dei tempi avrebbe conseguenze
+ disastrose, e \textit{soft-real-time} in cui un occasionale sforamento è
+ ritenuto accettabile.} in cui è vitale che i processi in che devono essere
+eseguiti in un determinato momento non debbano aspettare la conclusione di un
+altri processi che non hanno questa necessità, ha introdotto il concetto di
+\textsl{priorità assoluta}, chimata anche \textsl{priorità statica}, in
+contrapposizione con la normale priorità dinamica.
+
+Il concetto di prorità assoluta dice che quando due processi si contendono
+l'esecuzione, vince sempre quello con la priorità assoluta più alta, anche,
+grazie al \textit{prehemptive scheduling}, se l'altro è in esecuzione.
+Ovviamente questo avviene solo per i processi che sono pronti per essere
+eseguiti (cioè nello stato \textit{runnable}\footnote{lo stato di un processo
+ è riportato nel campo \texttt{STAT} dell'output del comando \cmd{ps},
+ abbiamo già visto che lo stato di \textit{zombie} è indicato con \texttt{Z},
+ gli stati \textit{runnable}, \textit{sleep} e di I/O (\textit{uninteruttible
+ sleep}) sono invece indicati con \texttt{R}, \texttt{S} e \texttt{D}.}),
+la priorità assoluta viene invece ignorata per quelli che sono bloccati su una
+richiesta di I/O o in stato di \textit{sleep}.
+
+Questa viene in genere indicata con un numero
+
+
+
+
+\subsection{Il meccanismo di \textit{scheduling} standard}
+\label{sec:proc_sched_stand}
+
+In Linux tutti i processi hanno sostanzialmente la stessa priorità; benché sia
+possibile specificare una priorità assoluta secondo lo standard POSIX
+(argomento che tratteremo più avanti) l'uso comune segue quello che è il
+meccanismo tradizionale con cui i sistemi
esistono quando si ha a che fare con un sistema in cui viene eseguito un solo
programma alla volta.
-Pur essendo questo argomento di carattere generale, in questa sezione
-conclusiva del capitolo in cui abbiamo affrontato la gestione dei processi ci
-è parso opportuno introdurre sinteticamente queste problematiche, che
-ritroveremo a più riprese in capitoli successivi, dando una breve descrizione
-delle loro caratteristiche principali e della terminologia relativa.
+Pur essendo questo argomento di carattere generale, ci è parso opportuno
+introdurre sinteticamente queste problematiche, che ritroveremo a più riprese
+in capitoli successivi, in questa sezione conclusiva del capitolo in cui
+abbiamo affrontato la gestione dei processi.
\subsection{Le operazioni atomiche}
Nel caso dell'interazione fra processi la situazione è molto più semplice, ed
occorre preoccuparsi della atomicità delle operazioni solo quando si ha a che
fare con meccanismi di intercomunicazione (che esamineremo in dettaglio in
-\capref{cha:IPC}) o nella operazioni con i file (vedremo alcuni esempi in
+\capref{cha:IPC}) o nelle operazioni con i file (vedremo alcuni esempi in
\secref{sec:file_atomic}). In questi casi in genere l'uso delle appropriate
funzioni di libreria per compiere le operazioni necessarie è garanzia
sufficiente di atomicità in quanto le system call con cui esse sono realizzate
In questo caso il sistema provvede un tipo di dato, il \type{sig\_atomic\_t},
il cui accesso è assicurato essere atomico. In pratica comunque si può
-assumere che in ogni piattaforma su cui è implementato Linux il tipo
-\type{int} (e gli altri interi di dimensione inferiore) ed i puntatori sono
+assumere che, in ogni piattaforma su cui è implementato Linux, il tipo
+\type{int}, gli altri interi di dimensione inferiore ed i puntatori sono
atomici. Non è affatto detto che lo stesso valga per interi di dimensioni
maggiori (in cui l'accesso può comportare più istruzioni in assembler) o per
-le strutture. In questi casi è anche opportuno marcare come \type{volatile} le
-variabili che possono essere interessate ad accesso condiviso, onde evitare
-problemi con le ottimizzazioni del codice.
+le strutture. In tutti questi casi è anche opportuno marcare come
+\type{volatile} le variabili che possono essere interessate ad accesso
+condiviso, onde evitare problemi con le ottimizzazioni del codice.
\subsection{Le \textit{race condition} e i \textit{deadlock}}
Si definiscono \textit{race condition} tutte quelle situazioni in cui processi
diversi operano su una risorsa comune, ed in cui il risultato viene a
dipendere dall'ordine in cui essi effettuano le loro operazioni. Il caso
-tipico è quella di una operazione che viene eseguita da un processo in più
+tipico è quello di una operazione che viene eseguita da un processo in più
passi, e può essere compromessa dall'intervento di un altro processo che
accede alla stessa risorsa quando ancora non tutti i passi sono stati
completati.
file, o nell'accesso a meccanismi di intercomunicazione come la memoria
condivisa. In questi casi, se non si dispone della possibilità di eseguire
atomicamente le operazioni necessarie, occorre che quelle parti di codice in
-cui si compiono le operazioni critiche sulle risorse condivise, le cosiddette
-\textsl{sezioni critiche} del programma, siano opportunamente protette da
+cui si compiono le operazioni sulle risorse condivise (le cosiddette
+\textsl{sezioni critiche}) del programma, siano opportunamente protette da
meccanismi di sincronizzazione (torneremo su queste problematiche di questo
tipo in \secref{sec:ipc_semaph}).
In tutti questi casi è di fondamentale importanza il concetto di atomicità
visto in \secref{sec:proc_atom_oper}; questi problemi infatti possono essere
risolti soltanto assicurandosi, quando essa sia richiesta, che sia possibile
-eseguire in maniera atomica le operazioni necessarie, proteggendo con gli
-adeguati meccanismi le \textsl{sezioni critiche} del programma.
+eseguire in maniera atomica le operazioni necessarie.
\subsection{Le funzioni rientranti}