X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=baced8dc2e33c57301ab144ce0a2bfe9b92bc94d;hp=173c0bed7e7d585913bacce51ee78a8742d1f6ff;hb=7090500d79c488db306ed0c065b90bb0c0505430;hpb=30cb49b067deded2d7f8c8331d466c3a937c4288 diff --git a/prochand.tex b/prochand.tex index 173c0be..baced8d 100644 --- a/prochand.tex +++ b/prochand.tex @@ -14,13 +14,12 @@ finale introdurremo alcune problematiche generiche della programmazione in ambiente multitasking. - \section{Introduzione} \label{sec:proc_gen} -Inizieremo con una introduzione generale ai concetti che stanno alla base -della gestione dei processi in un sistema unix-like. Introdurremo in questa -sezione l'architettura della gestione dei processi e le sue principali +Inizieremo con un'introduzione generale ai concetti che stanno alla base della +gestione dei processi in un sistema unix-like. Introdurremo in questa sezione +l'architettura della gestione dei processi e le sue principali caratteristiche, dando una panoramica sull'uso delle principali funzioni di gestione. @@ -37,9 +36,9 @@ numero unico, il cosiddetto \textit{process identifier} o, pi \acr{pid}. Una seconda caratteristica di un sistema Unix è che la generazione di un -processo è una operazione separata rispetto al lancio di un programma. In +processo è un'operazione separata rispetto al lancio di un programma. In genere la sequenza è sempre quella di creare un nuovo processo, il quale -eseguirà, in un passo successivo, il programma voluto: questo è ad esempio +eseguirà, in un passo successivo, il programma desiderato: questo è ad esempio quello che fa la shell quando mette in esecuzione il programma che gli indichiamo nella linea di comando. @@ -104,15 +103,15 @@ init-+-keventd Dato che tutti i processi attivi nel sistema sono comunque generati da \cmd{init} o da uno dei suoi figli\footnote{in realtà questo non è del tutto - vero, in Linux ci sono alcuni processi che pur comparendo come figli di - init, o con \acr{pid} successivi, sono in realtà generati direttamente dal - 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_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. + vero, in Linux ci sono alcuni processi speciali che pur comparendo come + figli di \cmd{init}, o con \acr{pid} successivi, sono in realtà generati + direttamente dal kernel, (come \cmd{keventd}, \cmd{kswapd}, etc.).} si +possono classificare i processi con la relazione padre/figlio in +un'organizzazione gerarchica ad albero, in maniera analoga a come i file sono +organizzati in un albero di directory (si veda +\secref{sec:file_organization}); in \curfig\ si è mostrato il risultato del +comando \cmd{pstree} che permette di visualizzare 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 @@ -162,7 +161,7 @@ figlio sono 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} (si veda -\secref{sec:proc_wait}); queste funzioni restituiscono anche una informazione +\secref{sec:proc_wait}); queste funzioni restituiscono anche un'informazione abbastanza limitata sulle cause della terminazione del processo figlio. Quando un processo ha concluso il suo compito o ha incontrato un errore non @@ -182,8 +181,8 @@ coi processi che Il programma che un processo sta eseguendo si chiama immagine del processo (o \textit{process image}), le funzioni della famiglia \func{exec} permettono di caricare un'altro programma da disco sostituendo quest'ultimo all'immagine -corrente; questo fa si che l'immagine precedente venga completamente -cancellata. Questo significa che quando il nuovo programma esce anche il +corrente; questo fa sì che l'immagine precedente venga completamente +cancellata. Questo significa che quando il nuovo programma esce, anche il processo termina, e non si può tornare alla precedente immagine. Per questo motivo la \func{fork} e la \func{exec} sono funzioni molto @@ -207,17 +206,22 @@ programmi. \subsection{Gli identificatori dei processi} \label{sec:proc_pid} -Come accennato nell'introduzione ogni processo viene identificato dal sistema +Come accennato nell'introduzione, ogni processo viene identificato dal sistema da un numero identificativo unico, il \textit{process id} o \acr{pid}; quest'ultimo è un tipo di dato standard, il \type{pid\_t} che in genere è un -intero con segno (nel caso di Linux e delle \acr{glibc} il tipo usato è \type{int}). +intero con segno (nel caso di Linux e delle \acr{glibc} il tipo usato è +\type{int}). 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\footnote{FIXME: verificare, non sono - sicuro}. Per questo motivo processo il processo di avvio (\cmd{init}) ha -sempre il \acr{pid} uguale a uno. +processo viene creato, fino ad un limite che, essendo il \acr{pid} un numero +positivo memorizzato in un intero a 16 bit, arriva ad un massimo di 32767. +Oltre questo valore l'assegnazione riparte dal numero più basso disponibile a +partire da un minimo di 300,\footnote{questi valori sono definiti dalla macro + \macro{PID\_MAX} in \file{threads.h} e direttamente in \file{fork.c} nei + sorgenti del kernel.} che serve a riservare i \acr{pid} più bassi ai processi +eseguiti dal direttamente dal kernel. Per questo motivo, come visto in +\secref{sec:proc_hierarchy}, 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 @@ -273,9 +277,9 @@ prototipo della funzione \funcdecl{pid\_t fork(void)} Crea un nuovo processo. - \bodydesc{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; - \var{errno} può assumere i valori: + \bodydesc{In caso di successo restituisce il \acr{pid} del figlio al padre e + zero al figlio; ritorna -1 al padre (senza creare il figlio) in caso di + errore; \var{errno} può assumere i valori: \begin{errlist} \item[\macro{EAGAIN}] non ci sono risorse sufficienti per creare un'altro processo (per allocare la tabella delle pagine e le strutture del task) o @@ -286,16 +290,23 @@ prototipo della funzione \end{functions} Dopo il successo dell'esecuzione di una \func{fork} sia il processo padre che -il processo figlio continuano ad essere eseguiti normalmente alla istruzione +il processo figlio continuano ad essere eseguiti normalmente all'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 della creazione di - un nuovo processo.}, pertanto padre e figlio vedono variabili diverse. +padre. Si tenga presente però che la memoria è copiata, non condivisa, +pertanto padre e figlio vedono variabili diverse. + +Per quanto riguarda la gestione della memoria in generale il segmento di +testo, che è identico, è condiviso e tenuto in read-only per il padre e per i +figli. Per gli altri segmenti Linux utilizza la tecnica del \textit{copy on + write}\index{copy on write}; questa tecnica comporta che una pagina di +memoria viene effettivamente copiata per il nuovo processo solo quando ci +viene effettuata sopra una scrittura (e si ha quindi una reale differenza fra +padre e figlio). In questo modo si rende molto più efficiente il meccanismo +della creazione di un nuovo processo, non essendo più necessaria la copia di +tutto lo spazio degli indirizzi virtuali del padre, ma solo delle pagine di +memoria che sono state modificate, e solo al momento della modifica stessa. 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 @@ -385,7 +396,7 @@ operazione che viene chiamata \textit{spawn}. Nei sistemi unix-like scelto di mantenere questa separazione, dato che, come per la prima modalità d'uso, esistono numerosi scenari in cui si può usare una \func{fork} senza aver bisogno di eseguire una \func{exec}. Inoltre, anche nel caso della -seconda modalità di uso, avere le due funzioni separate permette al figlio di +seconda modalità d'uso, avere le due funzioni separate permette al figlio di cambiare gli attributi del processo (maschera dei segnali, redirezione dell'output, \textit{user id}) prima della \func{exec}, rendendo così relativamente facile intervenire sulle le modalità di esecuzione del nuovo @@ -394,11 +405,14 @@ programma. In \curfig\ si è riportato il corpo del codice del programma di esempio \cmd{forktest}, che ci permette di illustrare molte caratteristiche dell'uso della funzione \func{fork}. Il programma permette di creare un numero di figli -specificato a linea di comando, e prende anche alcune opzioni per indicare +specificato da linea di comando, e prende anche alcune opzioni per indicare degli eventuali tempi di attesa in secondi (eseguiti tramite la funzione \func{sleep}) per il padre ed il figlio (con \cmd{forktest -h} si ottiene la descrizione delle opzioni); il codice completo, compresa la parte che gestisce -le opzioni a riga di comando, è disponibile nel file \file{ForkTest.c}. +le opzioni a riga di comando, è disponibile nel file \file{ForkTest.c}, +distribuito insieme agli altri sorgenti degli esempi su +\href{http://firenze.linux.it/~piccardi/gapil_source.tgz} +{\texttt{http://firenze.linux.it/\~~\hspace{-2.0mm}piccardi/gapil\_source.tgz}}. Decifrato il numero di figli da creare, il ciclo principale del programma (\texttt{\small 24--40}) esegue in successione la creazione dei processi figli @@ -434,12 +448,12 @@ Go to next child \end{verbatim} %$ \normalsize -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 +Esaminiamo questo risultato: una prima conclusione che si può trarre è che non +si può dire quale processo fra il padre ed il figlio venga eseguito per 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 + 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 @@ -456,7 +470,7 @@ cui il processo padre ha eseguito pi figli venisse messo in esecuzione. 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 +istruzioni del codice fra padre e figli, né sull'ordine in cui questi potranno 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 @@ -465,9 +479,9 @@ rischio di incorrere nelle cosiddette \textit{race condition} \index{race 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 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). +a loro (ogni processo vede solo la propria copia della memoria), 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). Un secondo aspetto molto importante nella creazione dei processi figli è quello dell'interazione dei vari processi con i file; per illustrarlo meglio @@ -591,8 +605,8 @@ comune dopo l'esecuzione di una \func{fork} \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}). -\item la maschera dei segnali bloccati e le azioni installate (vedi -\secref{sec:sig_xxx}). +\item la maschera dei segnali bloccati (vedi \secref{sec:sig_sigpending}) e le + azioni installate (vedi \secref{sec:sig_gen_beha}). \item i segmenti di memoria condivisa agganciati al processo (vedi \secref{sec:ipc_xxx}). \item i limiti sulle risorse (vedi \secref{sec:sys_xxx}). @@ -608,7 +622,8 @@ le differenze fra padre e figlio dopo la \func{fork} invece sono: nel figlio sono posti a zero. \item i \textit{file lock} (vedi \secref{sec:file_locking}), che non vengono ereditati dal figlio. -\item gli allarmi ed i segnali pendenti (vedi \secref{sec:sig_xxx}), che per il figlio vengono cancellati. +\item gli allarmi ed i segnali pendenti (vedi \secref{sec:sig_gen_beha}), che + per il figlio vengono cancellati. \end{itemize*} @@ -667,7 +682,7 @@ eseguite alla chiusura di un processo \item ad ogni processo figlio viene assegnato un nuovo padre (in genere \cmd{init}). \item viene inviato il segnale \macro{SIGCHLD} al processo padre (vedi - \secref{sec:sig_xxx}). + \secref{sec:sig_sigchld}). \item se il processo è un leader di sessione viene mandato un segnale di \macro{SIGHUP} a tutti i processi in background e il terminale di controllo viene disconnesso (vedi \secref{sec:sess_xxx}). @@ -679,7 +694,7 @@ eseguite alla chiusura di un processo 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 +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. @@ -707,7 +722,7 @@ terminato (si potrebbe avere cio 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 +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 possiamo parlare di un padre \textsl{adottivo}) @@ -784,7 +799,7 @@ 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 +\func{wait}, vedi \secref{sec:sig_sigchld} 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. @@ -835,7 +850,7 @@ segnale termina il processo o chiama una funzione di gestione. processo figlio termina. Se un figlio è già terminato la funzione ritorna immediatamente. -Al ritorno lo stato di termininazione del processo viene salvato nella +Al ritorno lo stato di terminazione del processo viene salvato nella variabile puntata da \var{status} e tutte le informazioni relative al processo (vedi \secref{sec:proc_termination}) vengono rilasciate. Nel caso un processo abbia più figli il valore di ritorno permette di @@ -924,7 +939,7 @@ 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}). In questo -caso infatti, dato che il segnale è generato dalla terminazione un figlio, +caso infatti, dato che il segnale è generato dalla terminazione di un figlio, avremo la certezza che la chiamata a \func{wait} non si bloccherà. \begin{table}[!htb] @@ -949,9 +964,9 @@ avremo la certezza che la chiamata a \func{wait} non si bloccher \macro{WIFSIGNALED} ha restituito un valore non nullo.\\ \macro{WCOREDUMP(s)} & Vera se il processo terminato ha generato un file si \textit{core dump}. Può essere valutata solo se - \macro{WIFSIGNALED} ha restituito un valore non nullo\footnote{questa + \macro{WIFSIGNALED} ha restituito un valore non nullo.\footnote{questa macro non è definita dallo standard POSIX.1, ma è presente come estensione - sia in Linux che in altri unix}.\\ + sia in Linux che in altri Unix.}\\ \macro{WIFSTOPPED(s)} & Vera se il processo che ha causato il ritorno di \func{waitpid} è bloccato. L'uso è possibile solo avendo specificato l'opzione \macro{WUNTRACED}. \\ @@ -978,12 +993,12 @@ anomala), uno per indicare se 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{} ed elencate in \curtab\ (si tenga presente che queste -macro prendono come parametro la variabile di tipo \type{int} puntata da -\var{status}). +\file{} ed elencate in \tabref{tab:proc_status_macro} (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 +\macro{WTERMSIG} può essere confrontato con le costanti definite in \file{signal.h} ed elencate in \tabref{tab:sig_signal_list}, e stampato usando le apposite funzioni trattate in \secref{sec:sig_strsignal}. @@ -991,13 +1006,13 @@ le apposite funzioni trattate in \secref{sec:sig_strsignal}. \subsection{Le funzioni \func{wait3} e \func{wait4}} \label{sec:proc_wait4} -Linux, seguendo una estensione di BSD, supporta altre due funzioni, -\func{wait} e \func{waitpd}, per la lettura dello stato di terminazione di un -processo, 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: +Linux, seguendo un'estensione di BSD, supporta altre due funzioni per la +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} @@ -1052,8 +1067,9 @@ famiglia di funzioni) che possono essere usate per questo compito, in realt \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 @@ -1155,11 +1171,12 @@ specificare il comando da eseguire; quando il parametro \var{file} non contiene una \file{/} esso viene considerato come un nome di programma, e viene eseguita automaticamente una ricerca fra i file presenti nella lista di directory specificate dalla variabile di ambiente \var{PATH}. Il file che -viene posto in esecuzione è il primo che viene trovato. Se si ha un errore di -permessi negati (cioè l'esecuzione della sottostante \func{execve} ritorna un -\macro{EACCESS}), la ricerca viene proseguita nelle eventuali ulteriori -directory indicate nel \var{PATH}, solo se non viene trovato nessun altro file -viene finalmente restituito \macro{EACCESS}. +viene posto in esecuzione è il primo che viene trovato. Se si ha un errore +relativo a permessi di accesso insufficienti (cioè l'esecuzione della +sottostante \func{execve} ritorna un \macro{EACCESS}), la ricerca viene +proseguita nelle eventuali ulteriori directory indicate in \var{PATH}; solo se +non viene trovato nessun altro file viene finalmente restituito +\macro{EACCESS}. Le altre quattro funzioni si limitano invece a cercare di eseguire il file indicato dal parametro \var{path}, che viene interpretato come il @@ -1168,7 +1185,7 @@ indicato dal parametro \var{path}, che viene interpretato come il \begin{figure}[htb] \centering \includegraphics[width=13cm]{img/exec_rel} - \caption{La interrelazione fra le sei funzioni della famiglia \func{exec}} + \caption{La interrelazione fra le sei funzioni della famiglia \func{exec}.} \label{fig:proc_exec_relat} \end{figure} @@ -1191,34 +1208,33 @@ la lista completa \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}). -\item il tempo restante ad un allarme (vedi \secref{sec:sig_xxx}). +\item il tempo restante ad un allarme (vedi \secref{sec:sig_alarm_abort}). \item la directory radice e la directory di lavoro corrente (vedi \secref{sec:file_work_dir}). \item la maschera di creazione dei file (\var{umask}, vedi \secref{sec:file_umask}) ed i \textit{lock} sui file (vedi \secref{sec:file_locking}). \item i segnali sospesi (\textit{pending}) e la maschera dei segnali (si veda - \secref{sec:sig_xxx}). + \secref{sec:sig_sigpending}). \item i limiti sulle risorse (vedi \secref{sec:sys_limits}). \item i valori delle variabili \var{tms\_utime}, \var{tms\_stime}, \var{tms\_cutime}, \var{tms\_ustime} (vedi \secref{sec:xxx_xxx}). \end{itemize*} -Oltre a questo i segnali che sono stati settati per essere ignorati nel -processo chiamante mantengono lo stesso settaggio pure nel nuovo programma, -tutti gli altri segnali vengono settati alla loro azione di default. Un caso -speciale è il segnale \macro{SIGCHLD} che, quando settato a \macro{SIG\_IGN}, -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. - -Per le directory lo standard POSIX.1 richiede che esse vengano chiuse +Inoltre i segnali che sono stati settati per essere ignorati nel processo +chiamante mantengono lo stesso settaggio pure nel nuovo programma, tutti gli +altri segnali vengono settati alla loro azione di default. Un caso speciale è +il segnale \macro{SIGCHLD} che, quando settato a \macro{SIG\_IGN}, può anche +non essere resettato a \macro{SIG\_DFL} (si veda \secref{sec:sig_gen_beha}). + +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 \func{opendir} (vedi \secref{sec:file_dir_read}) che effettua da sola il settaggio del flag di \textit{close-on-exec} sulle directory che apre, in @@ -1226,9 +1242,9 @@ maniera trasparente all'utente. 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}). @@ -1260,8 +1276,8 @@ parametri connessi ai processi. 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{Gli identificatori del controllo di accesso} @@ -1270,7 +1286,7 @@ le problematiche connesse ad una gestione accorta dei privilegi. Come accennato in \secref{sec:intro_multiuser} il modello base\footnote{in realtà già esistono estensioni di questo modello base, che lo rendono più flessibile e controllabile, come le \textit{capabilities}, le ACL per i file - o il \textit{Mandatory Access Control} di SELinux} di sicurezza di un + o il \textit{Mandatory Access Control} di SELinux.} di sicurezza di un sistema unix-like è fondato sui concetti di utente e gruppo, e sulla separazione fra l'amministratore (\textsl{root}, detto spesso anche \textit{superuser}) che non è sottoposto a restrizioni, ed il resto degli @@ -1294,12 +1310,12 @@ evidente che per poter implementare un controllo sulle operazioni occorre 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 @@ -1341,23 +1357,23 @@ cui si accede al sistema (e relativo gruppo di default). Servono per 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 l'\textit{effective group id} (a cui si aggiungono gli eventuali -\textit{supplementary group id} dei gruppi dei quale l'utente fa parte). +\textit{supplementary group id} dei gruppi dei quali l'utente fa parte). Questi sono invece gli identificatori usati nella verifiche dei permessi del processo e per il controllo di accesso ai file (argomento affrontato in dettaglio in \secref{sec:file_perm_overview}). Questi identificatori normalmente sono identici ai corrispondenti del gruppo -\textsl{reale} tranne nel caso in cui, come accennato in +\textit{real} tranne nel caso in cui, come accennato in \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). @@ -1392,10 +1408,10 @@ servano di nuovo. Questo in Linux viene fatto usando altri due gruppi di identificatori, il \textit{saved} ed il \textit{filesystem}, analoghi ai precedenti. Il primo gruppo è lo stesso usato in SVr4, e previsto dallo standard POSIX quando è -definita la costante \macro{\_POSIX\_SAVED\_IDS}\footnote{in caso si abbia a +definita la costante \macro{\_POSIX\_SAVED\_IDS},\footnote{in caso si abbia a cuore la portabilità del programma su altri Unix è buona norma controllare sempre la disponibilità di queste funzioni controllando se questa costante è - definita}, il secondo gruppo è specifico di Linux e viene usato per + definita.} il secondo gruppo è specifico di Linux e viene usato per migliorare la sicurezza con NFS. Il \textit{saved user id} e il \textit{saved group id} sono copie @@ -1447,8 +1463,7 @@ corrente. 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 @@ -1465,18 +1480,18 @@ riportare l'\textit{effective user id} a quello dell'utente che ha lanciato il 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 l'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 +Quando uno di questi programmi (ad esempio \cmd{xterm}) viene lanciato, la situazione degli identificatori è la seguente: \begin{eqnarray*} \label{eq:1} @@ -1485,7 +1500,7 @@ situazione degli identificatori \textit{saved group id} &=& \textrm{\acr{utmp}} \end{eqnarray*} in questo modo, dato che l'\textit{effective group id} è quello giusto, il -programma può accedere a \file{/var/log/utmp} in scrittura ed aggiornarlo, a +programma può accedere a \file{/var/log/utmp} in scrittura ed aggiornarlo. A questo punto il programma può eseguire una \code{setgid(getgid())} per settare l'\textit{effective group id} a quello dell'utente (e dato che il \textit{real group id} corrisponde la funzione avrà successo), in questo modo non sarà @@ -1501,9 +1516,9 @@ e ogni processo lanciato dal terminale avrebbe comunque \acr{gid} come \textit{effective group id}. All'uscita dal terminale, per poter di nuovo aggiornare lo stato di \file{/var/log/utmp} il programma eseguirà una \code{setgid(utmp)} (dove \var{utmp} è il valore numerico associato al gruppo -\acr{utmp}, ottenuto ad esempio con una \func{getegid}), dato che in questo -caso il valore richiesto corrisponde al \textit{saved group id} la funzione -avrà successo e riporterà la situazione a: +\acr{utmp}, ottenuto ad esempio con una precedente \func{getegid}), dato che +in questo caso il valore richiesto corrisponde al \textit{saved group id} la +funzione avrà successo e riporterà la situazione a: \begin{eqnarray*} \label{eq:3} \textit{real group id} &=& \textrm{\acr{gid} (invariato)} \\ @@ -1526,7 +1541,7 @@ ricorrere ad altre funzioni (si veda ad esempio \secref{sec:proc_seteuid}). \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} @@ -1572,7 +1587,7 @@ Lo stesso problema di propagazione dei privilegi ad eventuali processi figli si porrebbe per i \textit{saved id}: queste funzioni derivano da un'implementazione che non ne prevede la presenza, e quindi non è possibile usarle per correggere la situazione come nel caso precedente. Per questo -motivo in Linux tutte le volte che vengono usata per modificare uno degli +motivo in Linux tutte le volte che vengono usate per modificare uno degli identificatori ad un valore diverso dal \textit{real id} precedente, il \textit{saved id} viene sempre settato al valore dell'\textit{effective id}. @@ -1608,7 +1623,7 @@ il settaggio di tutti gli identificatori. \subsection{Le funzioni \func{setresuid} e \func{setresgid}} \label{sec:proc_setresuid} -Queste due funzioni sono una estensione introdotta in Linux dal kernel 2.1.44, +Queste due funzioni sono un'estensione introdotta in Linux dal kernel 2.1.44, e permettono un completo controllo su tutti gli identificatori (\textit{real}, \textit{effective} e \textit{saved}), i prototipi sono: \begin{functions} @@ -1655,7 +1670,7 @@ prototipi sono: variabili di ritorno non sono validi.} \end{functions} -Anche queste funzioni sono una estensione specifica di Linux, e non richiedono +Anche queste funzioni sono un'estensione specifica di Linux, e non richiedono nessun privilegio. I valori sono restituiti negli argomenti, che vanno specificati come puntatori (è un'altro esempio di \textit{value result argument}). Si noti che queste funzioni sono le uniche in grado di leggere i @@ -1707,11 +1722,10 @@ coincide con uno dei \textit{real}, \textit{effective} o \textit{saved id}. \subsection{Le funzioni \func{setgroups} e \func{getgroups}} \label{sec:proc_setgroups} -Le ultime funzioni che esamineremo sono quelle sono quelle che permettono di -operare sui gruppi supplementari. Ogni processo può avere fino a -\macro{NGROUPS\_MAX} gruppi supplementari in aggiunta al gruppo primario, -questi vengono ereditati dal processo padre e possono essere cambiati con -queste funzioni. +Le ultime funzioni che esamineremo sono quelle che permettono di operare sui +gruppi supplementari. Ogni processo può avere fino a \macro{NGROUPS\_MAX} +gruppi supplementari in aggiunta al gruppo primario, questi vengono ereditati +dal processo padre e possono essere cambiati con queste funzioni. La funzione che permette di leggere i gruppi supplementari è \func{getgroups}; questa funzione è definita nello standard POSIX ed il suo prototipo è: @@ -1750,10 +1764,10 @@ ottenere tutti i gruppi a cui appartiene un utente; il suo prototipo 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 -perché qualora il valore specificato sia troppo piccolo la funzione ritorna -1 -e passando indietro il numero dei gruppi trovati. +\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, passando indietro il numero dei gruppi trovati. Per settare i gruppi supplementari di un processo ci sono due funzioni, che possono essere usate solo se si hanno i privilegi di amministratore. La prima @@ -1776,7 +1790,7 @@ delle due \end{functions} Se invece si vogliono settare i gruppi supplementari del processo a quelli di -un utente specifico si può usare \func{initgroups} il cui prototipo è: +un utente specifico, si può usare \func{initgroups} il cui prototipo è: \begin{functions} \headdecl{sys/types.h} \headdecl{grp.h} @@ -1792,9 +1806,9 @@ un utente specifico si pu \end{functions} La funzione esegue la scansione del database dei gruppi (usualmente -\file{/etc/groups}) cercando i gruppi di cui è membro \param{user} costruendo -una lista di gruppi supplementari a cui aggiunge \param{group}, che poi setta -usando \func{setgroups}. +\file{/etc/groups}) cercando i gruppi di cui è membro \param{user} e +costruendo una lista di gruppi supplementari a cui aggiunge \param{group}, che +poi setta usando \func{setgroups}. Si tenga presente che sia \func{setgroups} che \func{initgroups} non sono definite nello standard POSIX.1 e che pertanto non è possibile utilizzarle @@ -1806,9 +1820,116 @@ quando si definisce \macro{\_POSIX\_SOURCE} o si compila con il flag \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 prenderemo in esame i vari meccanismi +con cui viene gestita l'assegnazione 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 le architetture +multi-processore si introduce anche la problematica 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 determinare 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, anche +quando si hanno più processori (e dei processi che sono eseguiti davvero in +contemporanea), si può pensare alle politiche di scheduling come concernenti +la risorsa \textsl{tempo di esecuzione}, la cui assegnazione sarà governata +dagli stessi meccanismi di scelta di priorità, solo 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. + +Il meccanismo tradizionale di scheduling di Unix (che tratteremo in +\secref{sec:proc_sched_stand}) è sempre stato basato su delle \textsl{priorità + dinamiche}, in modo da assicurare che tutti i processi, anche i meno +importanti, possano ricevere un po' di tempo di CPU. In sostanza quando un +processo ottiene la CPU la sua priorità viene diminuita. In questo modo alla +fine, anche un processo con priorità iniziale molto bassa, finisce per avere +una priorità sufficiente per essere eseguito. + +Lo standard POSIX.1b però ha introdotto il concetto di \textsl{priorità + assoluta}, (chiamata anche \textsl{priorità statica}, in contrapposizione +alla normale priorità dinamica), 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 che +devono essere eseguiti in un determinato momento non debbano aspettare la +conclusione di altri che non hanno questa necessità. + +Il concetto di priorità assoluta dice che quando due processi si contendono +l'esecuzione, vince sempre quello con la priorità assoluta più alta, anche +quando l'altro è in esecuzione (grazie al \textit{prehemptive scheduling}). +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}. La priorità assoluta viene in +genere indicata con un numero intero, ed un valore più alto comporta una +priorità maggiore, su questa politica di scheduling torneremo in +\secref{sec:proc_real_time}. + +In generale quello che succede in tutti gli Unix moderni è che ai processi +normali viene sempre data una priorità assoluta pari a zero, e la decisione di +assegnazione della CPU è fatta solo in base ad una priorità dinamica che è +calcolata indipendentemente. È tuttavia possibile assegnare anche una priorità +assoluta nel qual caso un processo avrà la precedenza su tutti gli altri di +priorità inferiore che saranno eseguiti solo quando quest'ultimo non avrà +bisogno della CPU. + + +\subsection{Il meccanismo di \textit{scheduling} standard} +\label{sec:proc_sched_stand} + +A meno che non si abbiano specifiche esigenze, l'unico meccanismo di +scheduling con il quale si avrà a che fare è quello tradizionale che prevede +solo priorità dinamiche, ed è di questo che di norma ci si dovrà preoccupare +nella programmazione. + +Come accennato in Linux tutti i processi ordinari hanno la stessa priorità +assoluta. Quello che determina quale, fra tutti i processi in attesa di +esecuzione, sarà eseguito per primo, è la priorità dinamica, che è chiamata +così proprio perché viene varia nel corso dell'esecuzione di un processo. + + + + +\subsection{Il meccanismo di \textit{scheduling real-time}} +\label{sec:proc_real_time} + +Per settare le + + +\footnote{a meno che non si siano installate le patch di RTLinux o RTAI, con i + quali è possibile ottenere un sistema effettivamente hard real-time.} + +in realtà non si tratta di un vero hard real-time, in quanto + la presenza di eventuali interrupt o di page fault può sempre interrompere + l'esecuzione di un processo, a meno di non installare le estensioni di + RTLinux o RTAI, il normale kernel non è real-time. @@ -1823,18 +1944,17 @@ occorre tenere conto di una serie di problematiche che normalmente non 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} \label{sec:proc_atom_oper} La nozione di \textsl{operazione atomica} deriva dal significato greco della -parola atomo, cioè indivisibile; si dice infatti che una operazione è atomica +parola atomo, cioè indivisibile; si dice infatti che un'operazione è atomica quando si ha la certezza che, qualora essa venga effettuata, tutti i passaggi che devono essere compiuti per realizzarla verranno eseguiti senza possibilità di interruzione in una fase intermedia. @@ -1849,7 +1969,7 @@ cui non erano ancora state completate. 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 @@ -1859,19 +1979,19 @@ processi. Nel caso dei segnali invece la situazione è molto più delicata, in quanto lo stesso processo, e pure alcune system call, possono essere interrotti in qualunque momento, e le operazioni di un eventuale \textit{signal handler} -sono compiute nello stesso spazio di indirizzi del processo. Per questo anche +sono compiute nello stesso spazio di indirizzi del processo. Per questo, anche il solo accesso o l'assegnazione di una variabile possono non essere più operazioni atomiche (torneremo su questi aspetti in \secref{sec:sign_xxx}). 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}} @@ -1880,7 +2000,7 @@ problemi con le ottimizzazioni del codice. 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 un'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. @@ -1900,8 +2020,8 @@ gli adeguati provvedimenti per far si che non si verifichino. Casi tipici di 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}). @@ -1919,8 +2039,7 @@ quest'ultima diventer 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} @@ -1928,26 +2047,26 @@ adeguati meccanismi le \textsl{sezioni critiche} del programma. Si dice \textsl{rientrante} una funzione che può essere interrotta in qualunque punto della sua esecuzione ed essere chiamata una seconda volta da -un altro thread di esecuzione senza che questo comporti nessun problema nella -esecuzione della stessa. La problematica è comune nella programmazione +un altro thread di esecuzione senza che questo comporti nessun problema +nell'esecuzione della stessa. La problematica è comune nella programmazione multi-thread, ma si hanno gli stessi problemi quando si vogliono chiamare delle funzioni all'interno dei manipolatori dei segnali. Fintanto che una funzione opera soltanto con le variabili locali è rientrante; -queste infatti vengono tutte le volte allocate nello stack, e un'altra -invocazione non fa altro che allocarne un'altra copia. Una funzione può non -essere rientrante quando opera su memoria che non è nello stack. Ad esempio -una funzione non è mai rientrante se usa una variabile globale o statica. - -Nel caso invece la funzione operi su un oggetto allocato dinamicamente la cosa -viene a dipendere da come avvengono le operazioni; se l'oggetto è creato ogni -volta e ritornato indietro la funzione può essere rientrante, se invece esso -viene individuato dalla funzione stessa due chiamate alla stessa funzione -potranno interferire quando entrambe faranno riferimento allo stesso oggetto. -Allo stesso modo una funzione può non essere rientrante se usa e modifica un -oggetto che le viene fornito dal chiamante: due chiamate possono interferire -se viene passato lo stesso oggetto; in tutti questi casi occorre molta cura da -parte del programmatore. +queste infatti vengono allocate nello stack, e un'altra invocazione non fa +altro che allocarne un'altra copia. Una funzione può non essere rientrante +quando opera su memoria che non è nello stack. Ad esempio una funzione non è +mai rientrante se usa una variabile globale o statica. + +Nel caso invece la funzione operi su un oggetto allocato dinamicamente, la +cosa viene a dipendere da come avvengono le operazioni: se l'oggetto è creato +ogni volta e ritornato indietro la funzione può essere rientrante, se invece +esso viene individuato dalla funzione stessa, due chiamate alla stessa +funzione potranno interferire quando entrambe faranno riferimento allo stesso +oggetto. Allo stesso modo una funzione può non essere rientrante se usa e +modifica un oggetto che le viene fornito dal chiamante: due chiamate possono +interferire se viene passato lo stesso oggetto; in tutti questi casi occorre +molta cura da parte del programmatore. In genere le funzioni di libreria non sono rientranti, molte di esse ad esempio utilizzano variabili statiche, le \acr{glibc} però mettono a