X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=651e14536584e9e1e5289714ea92da1ef8cdc928;hp=5b9368bfaac7b3597c5fb029665b1ed198eaf108;hb=9a6d19e384fe9b1afbe4d9124ac34eaf7aa57562;hpb=8fecd34eeb7db69bfa3cf2ca4b60ace29cad0356 diff --git a/prochand.tex b/prochand.tex index 5b9368b..651e145 100644 --- a/prochand.tex +++ b/prochand.tex @@ -1,9 +1,9 @@ %% prochand.tex %% -%% Copyright (C) 2000-2002 Simone Piccardi. Permission is granted to +%% Copyright (C) 2000-2005 Simone Piccardi. Permission is granted to %% copy, distribute and/or modify this document under the terms of the GNU Free %% Documentation License, Version 1.1 or any later version published by the -%% Free Software Foundation; with the Invariant Sections being "Prefazione", +%% Free Software Foundation; with the Invariant Sections being "Un preambolo", %% with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the %% license is included in the section entitled "GNU Free Documentation %% License". @@ -43,7 +43,7 @@ caratteristiche di Unix (che esamineremo in dettaglio pi qualunque processo può a sua volta generarne altri, detti processi figli (\textit{child process}). Ogni processo è identificato presso il sistema da un numero univoco, il cosiddetto \textit{process identifier} o, più brevemente, -\acr{pid}, assegnato in forma progressiva (vedi \secref{sec:proc_pid}) quando +\acr{pid}, assegnato in forma progressiva (vedi sez.~\ref{sec:proc_pid}) quando il processo viene creato. Una seconda caratteristica di un sistema Unix è che la generazione di un @@ -65,7 +65,7 @@ Ovviamente \cmd{init} partire tutti gli altri processi necessari al funzionamento del sistema, inoltre \cmd{init} è essenziale per svolgere una serie di compiti amministrativi nelle operazioni ordinarie del sistema (torneremo su alcuni di -essi in \secref{sec:proc_termination}) e non può mai essere terminato. La +essi in sez.~\ref{sec:proc_termination}) e non può mai essere terminato. La struttura del sistema comunque consente di lanciare al posto di \cmd{init} qualunque altro programma, e in casi di emergenza (ad esempio se il file di \cmd{init} si fosse corrotto) è ad esempio possibile lanciare una shell al suo @@ -116,23 +116,24 @@ 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 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 + direttamente dal kernel, (come \cmd{keventd}, \cmd{kswapd}, ecc.).} 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 \figref{fig:proc_tree} si è mostrato il +sez.~\ref{sec:file_organization}); in fig.~\ref{fig:proc_tree} 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 -\textit{process table}; per ciascun processo viene mantenuta una voce nella -tabella dei processi costituita da una struttura \struct{task\_struct}, che -contiene tutte le informazioni rilevanti per quel processo. Tutte le strutture -usate a questo scopo sono dichiarate nell'header file \file{linux/sched.h}, ed -uno schema semplificato, che riporta la struttura delle principali informazioni -contenute nella \struct{task\_struct} (che in seguito incontreremo a più -riprese), è mostrato in \figref{fig:proc_task_struct}. +\textit{process table}; per ciascun processo viene mantenuta una voce, +costituita da una struttura \struct{task\_struct}, nella tabella dei processi +che contiene tutte le informazioni rilevanti per quel processo. Tutte le +strutture usate a questo scopo sono dichiarate nell'header file +\file{linux/sched.h}, ed uno schema semplificato, che riporta la struttura +delle principali informazioni contenute nella \struct{task\_struct} (che in +seguito incontreremo a più riprese), è mostrato in +fig.~\ref{fig:proc_task_struct}. \begin{figure}[htb] \centering @@ -142,10 +143,9 @@ riprese), \label{fig:proc_task_struct} \end{figure} - -Come accennato in \secref{sec:intro_unix_struct} è lo -\textit{scheduler}\index{scheduler} che decide quale processo mettere in -esecuzione; esso viene eseguito ad ogni system call ed ad ogni +Come accennato in sez.~\ref{sec:intro_unix_struct} è lo +\textit{scheduler}\index{\textit{scheduler}} che decide quale processo mettere +in esecuzione; esso viene eseguito ad ogni system call ed ad ogni interrupt,\footnote{più in una serie di altre occasioni. NDT completare questa parte.} (ma può essere anche attivato esplicitamente). Il timer di sistema provvede comunque a che esso sia invocato periodicamente, generando un @@ -154,35 +154,34 @@ interrupt periodico secondo la frequenza specificata dalla costante Hertz.\footnote{Il valore usuale di questa costante è 100, per tutte le architetture eccetto l'alpha, per la quale è 1000. Occorre fare attenzione a non confondere questo valore con quello dei clock tick (vedi - \secref{sec:sys_unix_time}).} + sez.~\ref{sec:sys_unix_time}).} %Si ha cioè un interrupt dal timer ogni centesimo di secondo. -Ogni volta che viene eseguito, lo \textit{scheduler}\index{scheduler} effettua -il calcolo delle priorità dei vari processi attivi (torneremo su questo in -\secref{sec:proc_priority}) e stabilisce quale di essi debba essere posto in -esecuzione fino alla successiva invocazione. +Ogni volta che viene eseguito, lo \textit{scheduler}\index{\textit{scheduler}} +effettua il calcolo delle priorità dei vari processi attivi (torneremo su +questo in sez.~\ref{sec:proc_priority}) e stabilisce quale di essi debba +essere posto in esecuzione fino alla successiva invocazione. \subsection{Una panoramica sulle funzioni fondamentali} \label{sec:proc_handling_intro} -I processi vengono creati dalla funzione \func{fork}; in molti unix questa è -una system call, Linux però usa un'altra nomenclatura, e la funzione -\func{fork} è basata a sua volta sulla system call \func{\_\_clone}, che viene -usata anche per generare i \textit{thread}. Il processo figlio creato dalla -\func{fork} è una copia identica del processo processo padre, ma ha un nuovo -\acr{pid} e viene eseguito in maniera indipendente (le differenze fra padre e -figlio sono affrontate in dettaglio in \secref{sec:proc_fork}). +In un sistema unix-like i processi vengono sempre creati da altri processi +tramite la funzione \func{fork}; il nuovo processo (che viene chiamato +\textsl{figlio}) creato dalla \func{fork} è una copia identica del processo +processo originale (detto \textsl{padre}), ma ha un nuovo \acr{pid} e viene +eseguito in maniera indipendente (le differenze fra padre e figlio sono +affrontate in dettaglio in sez.~\ref{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 un'informazione +sez.~\ref{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 risolvibile esso può essere terminato con la funzione \func{exit} (si veda -quanto discusso in \secref{sec:proc_conclusion}). La vita del processo però +quanto discusso in sez.~\ref{sec:proc_conclusion}). La vita del processo però termina solo quando la notifica della sua conclusione viene ricevuta dal processo padre, a quel punto tutte le risorse allocate nel sistema ad esso associate vengono rilasciate. @@ -196,7 +195,7 @@ 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 +caricare un altro programma da disco sostituendo quest'ultimo all'immagine corrente; questo fa sì che l'immagine precedente venga completamente cancellata. Questo significa che quando il nuovo programma termina, anche il processo termina, e non si può tornare alla precedente immagine. @@ -231,18 +230,18 @@ intero con segno (nel caso di Linux e delle \acr{glibc} il tipo usato Il \acr{pid} viene assegnato in forma progressiva\footnote{in genere viene assegnato il numero successivo a quello usato per l'ultimo processo creato, a meno che questo numero non sia già utilizzato per un altro \acr{pid}, - \acr{pgid} o \acr{sid} (vedi \secref{sec:sess_proc_group}).} ogni volta che -un nuovo 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 -32768. Oltre questo valore l'assegnazione riparte dal numero più basso -disponibile a partire da un minimo di 300,\footnote{questi valori, fino al - kernel 2.4.x, sono definiti dalla macro \const{PID\_MAX} in \file{threads.h} - e direttamente in \file{fork.c}, con il kernel 2.5.x e la nuova interfaccia - per i thread creata da Ingo Molnar anche il meccanismo di allocazione dei - \acr{pid} è stato modificato.} 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. + \acr{pgid} o \acr{sid} (vedi sez.~\ref{sec:sess_proc_group}).} ogni volta +che un nuovo 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 32768. Oltre questo valore l'assegnazione riparte dal numero più +basso disponibile a partire da un minimo di 300,\footnote{questi valori, fino + al kernel 2.4.x, sono definiti dalla macro \const{PID\_MAX} in + \file{threads.h} e direttamente in \file{fork.c}, con il kernel 2.5.x e la + nuova interfaccia per i thread creata da Ingo Molnar anche il meccanismo di + allocazione dei \acr{pid} è stato modificato.} che serve a riservare i +\acr{pid} più bassi ai processi eseguiti direttamente dal kernel. Per questo +motivo, come visto in sez.~\ref{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 @@ -263,30 +262,31 @@ prototipi sono: \bodydesc{Entrambe le funzioni non riportano condizioni di errore.} \end{functions} \noindent esempi dell'uso di queste funzioni sono riportati in -\figref{fig:proc_fork_code}, nel programma di esempio \file{ForkTest.c}. +fig.~\ref{fig:proc_fork_code}, nel programma \file{ForkTest.c}. 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. +funzione \func{tempnam} (si veda sez.~\ref{sec:file_temp_file}) usa il +\acr{pid} per generare un \index{\textit{pathname}}\textit{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 sessione}, in cui si raggruppano i processi creati su uno stesso terminale, o relativi allo stesso login. Torneremo su questo argomento in dettaglio in -\secref{cha:session}, dove esamineremo gli altri identificativi associati ad +cap.~\ref{cha:session}, dove esamineremo gli altri identificativi associati ad un processo e le varie relazioni fra processi utilizzate per definire una sessione. Oltre al \acr{pid} e al \acr{ppid}, (e a quelli che vedremo in -\secref{sec:sess_proc_group}, relativi al controllo di sessione), ad ogni +sez.~\ref{sec:sess_proc_group}, relativi al controllo di sessione), ad ogni processo vengono associati degli altri identificatori che vengono usati per il controllo di accesso. Questi servono per determinare se un processo può eseguire o meno le operazioni richieste, a seconda dei privilegi e dell'identità di chi lo ha posto in esecuzione; l'argomento è complesso e sarà -affrontato in dettaglio in \secref{sec:proc_perms}. +affrontato in dettaglio in sez.~\ref{sec:proc_perms}. \subsection{La funzione \func{fork}} @@ -307,7 +307,7 @@ prototipo della funzione zero al figlio; ritorna -1 al padre (senza creare il figlio) in caso di errore; \var{errno} può assumere i valori: \begin{errlist} - \item[\errcode{EAGAIN}] non ci sono risorse sufficienti per creare un'altro + \item[\errcode{EAGAIN}] non ci sono risorse sufficienti per creare un altro processo (per allocare la tabella delle pagine e le strutture del task) o si è esaurito il numero di processi disponibili. \item[\errcode{ENOMEM}] non è stato possibile allocare la memoria per le @@ -319,21 +319,21 @@ Dopo il successo dell'esecuzione di una \func{fork} sia il processo padre che il processo figlio continuano ad essere eseguiti normalmente a partire dall'istruzione successiva alla \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 +sez.~\ref{sec:proc_mem_layout}), ed esegue esattamente lo stesso codice del 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 per i due processi, è 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. +del \textit{copy on write}\index{\textit{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 @@ -345,57 +345,15 @@ La scelta di questi valori di ritorno non avere più figli, ed il valore di ritorno di \func{fork} è l'unico modo che gli permette di identificare quello appena creato; al contrario un figlio ha sempre un solo padre (il cui \acr{pid} può sempre essere ottenuto con -\func{getppid}, vedi \secref{sec:proc_pid}) per cui si usa il valore nullo, +\func{getppid}, vedi sez.~\ref{sec:proc_pid}) per cui si usa il valore nullo, che non è il \acr{pid} di nessun processo. \begin{figure}[!htb] - \footnotesize - \begin{lstlisting}{} -#include /* error definitions and routines */ -#include /* C standard library */ -#include /* unix standard library */ -#include /* standard I/O library */ -#include /* string functions */ - -/* Help printing routine */ -void usage(void); - -int main(int argc, char *argv[]) -{ -/* - * Variables definition - */ - int nchild, i; - pid_t pid; - int wait_child = 0; - int wait_parent = 0; - int wait_end = 0; - ... /* handling options */ - nchild = atoi(argv[optind]); - printf("Test for forking %d child\n", nchild); - /* loop to fork children */ - for (i=0; i} ed elencate in \tabref{tab:proc_status_macro} (si tenga +\file{} ed elencate in tab.~\ref{tab:proc_status_macro} (si tenga presente che queste macro prendono come parametro la variabile di tipo \ctyp{int} puntata da \param{status}). Si tenga conto che nel caso di conclusione anomala il valore restituito da \val{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}. +\file{signal.h} ed elencate in tab.~\ref{tab:sig_signal_list}, e stampato +usando le apposite funzioni trattate in sez.~\ref{sec:sig_strsignal}. \subsection{Le funzioni \func{wait3} e \func{wait4}} @@ -1052,7 +1025,7 @@ le apposite funzioni trattate in \secref{sec:sig_strsignal}. Linux, seguendo un'estensione di BSD, supporta altre due funzioni 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ò +ma che prevedono un ulteriore argomento attraverso il quale il kernel può restituire al padre informazioni sulle risorse usate dal processo terminato e dai vari figli. Le due funzioni sono \funcd{wait3} e \funcd{wait4}, che diventano accessibili definendo la macro \macro{\_USE\_BSD}; i loro prototipi @@ -1061,10 +1034,10 @@ sono: \headdecl{sys/times.h} \headdecl{sys/types.h} \headdecl{sys/wait.h} \headdecl{sys/resource.h} - \funcdecl{pid\_t wait4(pid\_t pid, int * status, int options, struct rusage - * rusage)} - È identica a \func{waitpid} sia per comportamento che per i valori dei - parametri, ma restituisce in \param{rusage} un sommario delle risorse usate + \funcdecl{pid\_t wait4(pid\_t pid, int *status, int options, struct rusage + *rusage)} + È identica a \func{waitpid} sia per comportamento che per i valori degli + argomenti, ma restituisce in \param{rusage} un sommario delle risorse usate dal processo. \funcdecl{pid\_t wait3(int *status, int options, struct rusage *rusage)} @@ -1074,8 +1047,8 @@ sono: \noindent la struttura \struct{rusage} è definita in \file{sys/resource.h}, e viene utilizzata anche dalla funzione \func{getrusage} (vedi -\secref{sec:sys_resource_use}) per ottenere le risorse di sistema usate da un -processo; la sua definizione è riportata in \figref{fig:sys_rusage_struct}. +sez.~\ref{sec:sys_resource_use}) per ottenere le risorse di sistema usate da un +processo; la sua definizione è riportata in fig.~\ref{fig:sys_rusage_struct}. \subsection{Le funzioni \func{exec}} @@ -1092,7 +1065,7 @@ disco. Ci sono sei diverse versioni di \func{exec} (per questo la si è chiamata famiglia di funzioni) che possono essere usate per questo compito, in realtà -(come mostrato in \figref{fig:proc_exec_relat}), sono tutte un front-end a +(come mostrato in fig.~\ref{fig:proc_exec_relat}), sono tutte un front-end a \funcd{execve}. Il prototipo di quest'ultima è: \begin{prototype}{unistd.h} {int execve(const char *filename, char *const argv[], char *const envp[])} @@ -1103,21 +1076,21 @@ famiglia di funzioni) che possono essere usate per questo compito, in realt \begin{errlist} \item[\errcode{EACCES}] il file non è eseguibile, oppure il filesystem è montato in \cmd{noexec}, oppure non è un file regolare o un interprete. - \item[\errcode{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 + \item[\errcode{EPERM}] il file ha i bit \acr{suid} o \acr{sgid}, l'utente + non è root, il processo viene tracciato, o il filesystem è montato con l'opzione \cmd{nosuid}. \item[\errcode{ENOEXEC}] il file è in un formato non eseguibile o non riconosciuto come tale, o compilato per un'altra architettura. \item[\errcode{ENOENT}] il file o una delle librerie dinamiche o l'interprete necessari per eseguirlo non esistono. - \item[\errcode{ETXTBSY}] L'eseguibile è aperto in scrittura da uno o più + \item[\errcode{ETXTBSY}] l'eseguibile è aperto in scrittura da uno o più processi. - \item[\errcode{EINVAL}] L'eseguibile ELF ha più di un segmento + \item[\errcode{EINVAL}] l'eseguibile ELF ha più di un segmento \const{PF\_INTERP}, cioè chiede di essere eseguito da più di un interprete. - \item[\errcode{ELIBBAD}] Un interprete ELF non è in un formato + \item[\errcode{ELIBBAD}] un interprete ELF non è in un formato riconoscibile. - \item[\errcode{E2BIG}] La lista degli argomenti è troppo grande. + \item[\errcode{E2BIG}] la lista degli argomenti è troppo grande. \end{errlist} ed inoltre anche \errval{EFAULT}, \errval{ENOMEM}, \errval{EIO}, \errval{ENAMETOOLONG}, \errval{ELOOP}, \errval{ENOTDIR}, \errval{ENFILE}, @@ -1132,8 +1105,8 @@ argomenti e dell'ambiente possono essere acceduti dal nuovo programma quando la sua funzione \func{main} è dichiarata nella forma \code{main(int argc, char *argv[], char *envp[])}. -Le altre funzioni della famiglia servono per fornire all'utente una serie -possibile di diverse interfacce per la creazione di un nuovo processo. I loro +Le altre funzioni della famiglia servono per fornire all'utente una serie di +possibili diverse interfacce per la creazione di un nuovo processo. I loro prototipi sono: \begin{functions} \headdecl{unistd.h} @@ -1145,7 +1118,7 @@ prototipi sono: \funcdecl{int execvp(const char *file, char *const argv[])} Sostituiscono l'immagine corrente del processo con quella indicata nel primo -argomento. I parametri successivi consentono di specificare gli argomenti a +argomento. Gli argomenti successivi consentono di specificare gli argomenti a linea di comando e l'ambiente ricevuti dal nuovo processo. \bodydesc{Queste funzioni ritornano solo in caso di errore, restituendo -1; @@ -1154,9 +1127,9 @@ linea di comando e l'ambiente ricevuti dal nuovo processo. \end{functions} Per capire meglio le differenze fra le funzioni della famiglia si può fare -riferimento allo specchietto riportato in \tabref{tab:proc_exec_scheme}. La -prima differenza riguarda le modalità di passaggio dei parametri che poi -andranno a costituire gli argomenti a linea di comando (cioè i valori di +riferimento allo specchietto riportato in tab.~\ref{tab:proc_exec_scheme}. La +prima differenza riguarda le modalità di passaggio dei valori che poi andranno +a costituire gli argomenti a linea di comando (cioè i valori di \param{argv} e \param{argc} visti dalla funzione \func{main} del programma chiamato). @@ -1168,9 +1141,7 @@ questo vettore \emph{deve} essere terminato da un puntatore nullo. Nel secondo caso le stringhe degli argomenti sono passate alla funzione come lista di puntatori, nella forma: -\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} - char *arg0, char *arg1, ..., char *argn, NULL -\end{lstlisting} +\includecodesnip{listati/char_list.c} che deve essere terminata da un puntatore nullo. In entrambi i casi vale la convenzione che il primo argomento (\var{arg0} o \var{argv[0]}) viene usato per indicare il nome del file che contiene il programma che verrà eseguito. @@ -1190,8 +1161,8 @@ per indicare il nome del file che contiene il programma che verr argomenti a lista &$\bullet$&$\bullet$&$\bullet$&&& \\ argomenti a vettore &&&&$\bullet$&$\bullet$&$\bullet$\\ \hline - filename completo &&$\bullet$&&&$\bullet$& \\ - ricerca su \var{PATH}&$\bullet$&&$\bullet$&$\bullet$&&$\bullet$ \\ + filename completo &$\bullet$&&$\bullet$&$\bullet$&&$\bullet$\\ + ricerca su \var{PATH} &&$\bullet$&&&$\bullet$& \\ \hline ambiente a vettore &&&$\bullet$&&&$\bullet$ \\ uso di \var{environ} &$\bullet$&$\bullet$&&$\bullet$&$\bullet$& \\ @@ -1205,10 +1176,10 @@ per indicare il nome del file che contiene il programma che verr La seconda differenza fra le funzioni riguarda le modalità con cui si specifica il programma che si vuole eseguire. Con lo mnemonico \code{p} si indicano le due funzioni che replicano il comportamento della shell nello -specificare il comando da eseguire; quando il parametro \param{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 +specificare il comando da eseguire; quando l'argomento \param{file} non +contiene una ``\texttt{/}'' 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 relativo a permessi di accesso insufficienti (cioè l'esecuzione della sottostante \func{execve} ritorna un \errcode{EACCES}), la ricerca viene @@ -1218,7 +1189,7 @@ non viene trovato nessun altro file viene finalmente restituito Le altre quattro funzioni si limitano invece a cercare di eseguire il file indicato dall'argomento \param{path}, che viene interpretato come il -\textit{pathname} del programma. +\index{\textit{pathname}}\textit{pathname} del programma. \begin{figure}[htb] \centering @@ -1228,34 +1199,35 @@ indicato dall'argomento \param{path}, che viene interpretato come il \end{figure} La terza differenza è come viene passata la lista delle variabili di ambiente. -Con lo mnemonico \code{e} vengono indicate quelle funzioni che necessitano di -un vettore di parametri \var{envp[]} analogo a quello usato per gli argomenti -a riga di comando (terminato quindi da un \val{NULL}), le altre usano il -valore della variabile \var{environ} (vedi \secref{sec:proc_environ}) del -processo di partenza per costruire l'ambiente. +Con lo mnemonico \texttt{e} vengono indicate quelle funzioni che necessitano +di un vettore di parametri \var{envp[]} analogo a quello usato per gli +argomenti a riga di comando (terminato quindi da un \val{NULL}), le altre +usano il valore della variabile \var{environ} (vedi +sez.~\ref{sec:proc_environ}) del processo di partenza per costruire +l'ambiente. Oltre a mantenere lo stesso \acr{pid}, il nuovo programma fatto partire da \func{exec} assume anche una serie di altre proprietà del processo chiamante; la lista completa è la seguente: \begin{itemize*} \item il \textit{process id} (\acr{pid}) ed il \textit{parent process id} - (\acr{ppid}). + (\acr{ppid}); \item l'\textsl{user-ID reale}, il \textit{group-ID reale} ed i - \textsl{group-ID supplementari} (vedi \secref{sec:proc_access_id}). + \textsl{group-ID supplementari} (vedi sez.~\ref{sec:proc_access_id}); \item il \textit{session id} (\acr{sid}) ed il \textit{process group-ID} - (\acr{pgid}), vedi \secref{sec:sess_proc_group}. -\item il terminale di controllo (vedi \secref{sec:sess_ctrl_term}). -\item il tempo restante ad un allarme (vedi \secref{sec:sig_alarm_abort}). + (\acr{pgid}), vedi sez.~\ref{sec:sess_proc_group}; +\item il terminale di controllo (vedi sez.~\ref{sec:sess_ctrl_term}); +\item il tempo restante ad un allarme (vedi sez.~\ref{sec:sig_alarm_abort}); \item la directory radice e la directory di lavoro corrente (vedi - \secref{sec:file_work_dir}). + sez.~\ref{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}). + sez.~\ref{sec:file_umask}) ed i \textit{lock} sui file (vedi + sez.~\ref{sec:file_locking}); \item i segnali sospesi (\textit{pending}) e la maschera dei segnali (si veda - \secref{sec:sig_sigmask}). -\item i limiti sulle risorse (vedi \secref{sec:sys_resource_limit}). + sez.~\ref{sec:sig_sigmask}); +\item i limiti sulle risorse (vedi sez.~\ref{sec:sys_resource_limit}); \item i valori delle variabili \var{tms\_utime}, \var{tms\_stime}, - \var{tms\_cutime}, \var{tms\_ustime} (vedi \secref{sec:sys_cpu_times}). + \var{tms\_cutime}, \var{tms\_ustime} (vedi sez.~\ref{sec:sys_cpu_times}). \end{itemize*} Inoltre i segnali che sono stati impostati per essere ignorati nel processo @@ -1263,11 +1235,11 @@ chiamante mantengono la stessa impostazione pure nel nuovo programma, tutti gli altri segnali vengono impostati alla loro azione predefinita. Un caso speciale è il segnale \const{SIGCHLD} che, quando impostato a \const{SIG\_IGN}, può anche non essere reimpostato a \const{SIG\_DFL} (si veda -\secref{sec:sig_gen_beha}). +sez.~\ref{sec:sig_gen_beha}). La gestione dei file aperti dipende dal valore che ha il flag di -\textit{close-on-exec}\index{close-on-exec} (vedi anche -\secref{sec:file_fcntl}) per ciascun file descriptor. I file per cui è +\textit{close-on-exec}\index{\textit{close-on-exec}} (vedi anche +sez.~\ref{sec:file_fcntl}) per ciascun file descriptor. I file per cui è impostato vengono chiusi, tutti gli altri file restano aperti. Questo significa che il comportamento predefinito è che i file restano aperti attraverso una \func{exec}, a meno di una chiamata esplicita a \func{fcntl} @@ -1275,31 +1247,43 @@ che imposti 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 -l'impostazione del flag di \textit{close-on-exec}\index{close-on-exec} sulle -directory che apre, in maniera trasparente all'utente. +\func{opendir} (vedi sez.~\ref{sec:file_dir_read}) che effettua da sola +l'impostazione del flag di +\textit{close-on-exec}\index{\textit{close-on-exec}} sulle directory che apre, +in maniera trasparente all'utente. Abbiamo detto che l'\textsl{user-ID reale} ed il \textsl{group-ID reale} restano gli stessi all'esecuzione di \func{exec}; lo stesso vale per l'\textsl{user-ID effettivo} ed il \textsl{group-ID effettivo} (il significato -di questi identificatori è trattato in \secref{sec:proc_access_id}), tranne +di questi identificatori è trattato in sez.~\ref{sec:proc_access_id}), tranne quando il file che si va ad eseguire abbia o il \acr{suid} bit o lo \acr{sgid} bit impostato, in questo caso l'\textsl{user-ID effettivo} ed il \textsl{group-ID effettivo} vengono impostati rispettivamente all'utente o al -gruppo cui il file appartiene (per i dettagli vedi \secref{sec:proc_perms}). +gruppo cui il file appartiene (per i dettagli vedi sez.~\ref{sec:proc_perms}). Se il file da eseguire è in formato \emph{a.out} e necessita di librerie -condivise, viene lanciato il \textit{linker} dinamico \cmd{ld.so} prima del -programma per caricare le librerie necessarie ed effettuare il link +condivise, viene lanciato il \textit{linker} dinamico \cmd{/lib/ld.so} prima +del programma per caricare le librerie necessarie ed effettuare il link dell'eseguibile. Se il programma è in formato ELF per caricare le librerie dinamiche viene usato l'interprete indicato nel segmento \const{PT\_INTERP}, in genere questo è \file{/lib/ld-linux.so.1} per programmi linkati con le \acr{libc5}, e \file{/lib/ld-linux.so.2} per programmi linkati con le -\acr{glibc}. Infine nel caso il file sia uno script esso deve iniziare con -una linea nella forma \cmd{\#!/path/to/interpreter} dove l'interprete indicato -deve esse un valido programma (binario, non un altro script) che verrà -chiamato come se si fosse eseguito il comando \cmd{interpreter [arg] - filename}. +\acr{glibc}. + +Infine nel caso il file sia uno script esso deve iniziare con una linea nella +forma \cmd{\#!/path/to/interpreter [argomenti]} dove l'interprete indicato +deve essere un programma valido (binario, non un altro script) che verrà +chiamato come se si fosse eseguito il comando \cmd{interpreter [argomenti] + filename}.\footnote{si tenga presente che con Linux quanto viene scritto + come \texttt{argomenti} viene passato all'inteprete come un unico argomento + con una unica stringa di lunghezza massima di 127 caratteri e se questa + dimensione viene ecceduta la stringa viene troncata; altri Unix hanno + dimensioni massime diverse, e diversi comportamenti, ad esempio FreeBSD + esegue la scansione della riga e la divide nei vari argomenti e se è troppo + lunga restitituisce un errore di \const{ENAMETOOLONG}, una comparazione dei + vari comportamenti si trova su + \href{http://www.in-ulm.de/~mascheck/various/shebang/} + {\texttt{http://www.in-ulm.de/\tild mascheck/various/shebang/}}.} Con la famiglia delle \func{exec} si chiude il novero delle funzioni su cui è basata la gestione dei processi in Unix: con \func{fork} si crea un nuovo @@ -1314,7 +1298,7 @@ vari parametri connessi ai processi. \label{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 +accesso dal punto di vista dei processi; vedremo quali sono gli identificatori usati, come questi possono essere modificati nella creazione e nel lancio di nuovi processi, le varie funzioni per la loro manipolazione diretta e tutte le problematiche connesse ad una gestione accorta dei privilegi. @@ -1323,7 +1307,7 @@ problematiche connesse ad una gestione accorta dei privilegi. \subsection{Gli identificatori del controllo di accesso} \label{sec:proc_access_id} -Come accennato in \secref{sec:intro_multiuser} il modello base\footnote{in +Come accennato in sez.~\ref{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; inoltre basandosi sul @@ -1344,7 +1328,7 @@ Abbiamo gi identificatori univoci, lo user-ID ed il group-ID; questi servono al kernel per identificare uno specifico utente o un gruppo di utenti, per poi poter controllare che essi siano autorizzati a compiere le operazioni richieste. Ad -esempio in \secref{sec:file_access_control} vedremo come ad ogni file vengano +esempio in sez.~\ref{sec:file_access_control} vedremo come ad ogni file vengano associati un utente ed un gruppo (i suoi \textsl{proprietari}, indicati appunto tramite un \acr{uid} ed un \acr{gid}) che vengono controllati dal kernel nella gestione dei permessi di accesso. @@ -1352,7 +1336,7 @@ kernel nella gestione dei permessi di accesso. Dato che tutte le operazioni del sistema vengono compiute dai processi, è 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 dovrà essere associato ad un utente e ad un gruppo. +anche a ciascun processo dovrà essere associato un utente e un gruppo. Un semplice controllo di una corrispondenza fra identificativi non garantisce però sufficiente flessibilità per tutti quei casi in cui è necessario poter @@ -1362,7 +1346,8 @@ prevedono che i processi abbiano almeno due gruppi di identificatori, chiamati rispettivamente \textit{real} ed \textit{effective} (cioè \textsl{reali} ed \textsl{effettivi}). Nel caso di Linux si aggiungono poi altri due gruppi, il \textit{saved} (\textsl{salvati}) ed il \textit{filesystem} (\textsl{di - filesystem}), secondo la situazione illustrata in \tabref{tab:proc_uid_gid}. + filesystem}), secondo la situazione illustrata in +tab.~\ref{tab:proc_uid_gid}. \begin{table}[htb] \footnotesize @@ -1406,7 +1391,7 @@ Al primo gruppo appartengono l'\textsl{user-ID reale} ed il \textsl{group-ID reale}: questi vengono impostati al login ai valori corrispondenti all'utente con cui si accede al sistema (e relativo gruppo principale). Servono per l'identificazione dell'utente e normalmente non vengono mai -cambiati. In realtà vedremo (in \secref{sec:proc_setuid}) che è possibile +cambiati. In realtà vedremo (in sez.~\ref{sec:proc_setuid}) che è possibile modificarli, ma solo ad un processo che abbia i privilegi di amministratore; questa possibilità è usata proprio dal programma \cmd{login} che, una volta completata la procedura di autenticazione, lancia una shell per la quale @@ -1416,18 +1401,18 @@ nel sistema. Al secondo gruppo appartengono lo \textsl{user-ID effettivo} ed il \textsl{group-ID effettivo} (a cui si aggiungono gli eventuali \textsl{group-ID supplementari} dei gruppi dei quali l'utente fa parte). Questi sono invece -gli identificatori usati nella verifiche dei permessi del processo e per il +gli identificatori usati nelle verifiche dei permessi del processo e per il controllo di accesso ai file (argomento affrontato in dettaglio in -\secref{sec:file_perm_overview}). +sez.~\ref{sec:file_perm_overview}). Questi identificatori normalmente sono identici ai corrispondenti del gruppo \textit{real} tranne nel caso in cui, come accennato in -\secref{sec:proc_exec}, il programma che si è posto in esecuzione abbia i bit +sez.~\ref{sec:proc_exec}, il programma che si è posto in esecuzione abbia i bit \acr{suid} o \acr{sgid} impostati (il significato di questi bit è affrontato -in dettaglio in \secref{sec:file_suid_sgid}). In questo caso essi saranno +in dettaglio in sez.~\ref{sec:file_suid_sgid}). In questo caso essi saranno impostati 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). +privilegi o permessi di un altro (o dell'amministratore). Come nel caso del \acr{pid} e del \acr{ppid}, anche tutti questi identificatori possono essere letti attraverso le rispettive funzioni: @@ -1458,11 +1443,11 @@ maggiori privilegi necessari, una volta che si siano effettuate le operazioni per i quali erano richiesti, e a poterli eventualmente recuperare in caso servano di nuovo. -Questo in Linux viene fatto usando altri gli altri due gruppi di -identificatori, il \textit{saved} ed il \textit{filesystem}. 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 cuore la - portabilità del programma su altri Unix è buona norma controllare sempre la +Questo in Linux viene fatto usando altri due gruppi di identificatori, il +\textit{saved} ed il \textit{filesystem}. 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 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 migliorare la sicurezza con NFS. @@ -1471,29 +1456,30 @@ L'\textsl{user-ID salvato} ed il \textsl{group-ID salvato} sono copie dell'\textsl{user-ID effettivo} e del \textsl{group-ID effettivo} del processo padre, e vengono impostati dalla funzione \func{exec} all'avvio del processo, come copie dell'\textsl{user-ID effettivo} e del \textsl{group-ID effettivo} -dopo che questo sono stati impostati tenendo conto di eventuali \acr{suid} o +dopo che questi sono stati impostati tenendo conto di eventuali \acr{suid} o \acr{sgid}. Essi quindi consentono di tenere traccia di quale fossero utente e gruppo effettivi all'inizio dell'esecuzione di un nuovo programma. L'\textsl{user-ID di filesystem} e il \textsl{group-ID di filesystem} sono un'estensione introdotta in Linux per rendere più sicuro l'uso di NFS -(torneremo sull'argomento in \secref{sec:proc_setfsuid}). Essi sono una +(torneremo sull'argomento in sez.~\ref{sec:proc_setuid}). Essi sono una replica dei corrispondenti identificatori del gruppo \textit{effective}, ai quali si sostituiscono per tutte le operazioni di verifica dei permessi -relativi ai file (trattate in \secref{sec:file_perm_overview}). Ogni +relativi ai file (trattate in sez.~\ref{sec:file_perm_overview}). Ogni cambiamento effettuato sugli identificatori effettivi viene automaticamente riportato su di essi, per cui in condizioni normali si può tranquillamente ignorarne l'esistenza, in quanto saranno del tutto equivalenti ai precedenti. -\subsection{Le funzioni \func{setuid} e \func{setgid}} +\subsection{Le funzioni di gestione degli identificatori dei processi} \label{sec:proc_setuid} -Le due funzioni che vengono usate per cambiare identità (cioè utente e gruppo -di appartenenza) ad un processo sono rispettivamente \funcd{setuid} e -\funcd{setgid}; come accennato in \secref{sec:proc_access_id} in Linux esse -seguono la semantica POSIX che prevede l'esistenza dell'\textit{user-ID - salvato} e del \textit{group-ID salvato}; i loro prototipi sono: +Le due funzioni più comuni che vengono usate per cambiare identità (cioè +utente e gruppo di appartenenza) ad un processo sono rispettivamente +\funcd{setuid} e \funcd{setgid}; come accennato in +sez.~\ref{sec:proc_access_id} in Linux esse seguono la semantica POSIX che +prevede l'esistenza dell'\textit{user-ID salvato} e del \textit{group-ID + salvato}; i loro prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} @@ -1524,8 +1510,8 @@ all'\textsl{user-ID salvato}. Negli altri casi viene segnalato un errore (con Come accennato l'uso principale di queste funzioni è quello di poter consentire ad un programma con i bit \acr{suid} o \acr{sgid} impostati (vedi -\secref{sec:file_suid_sgid}) di riportare l'\textsl{user-ID effettivo} a quello -dell'utente che ha lanciato il programma, effettuare il lavoro che non +sez.~\ref{sec:file_suid_sgid}) di riportare l'\textsl{user-ID effettivo} 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 l'uso di queste funzioni prendiamo quello con cui @@ -1576,22 +1562,19 @@ funzione avr consentendo l'accesso a \file{/var/log/utmp}. Occorre però tenere conto che tutto questo non è possibile con un processo con -i privilegi di root, in tal caso infatti l'esecuzione una \func{setuid} -comporta il cambiamento di tutti gli identificatori associati al processo, -rendendo impossibile riguadagnare i privilegi di amministratore. Questo -comportamento è corretto per l'uso che ne fa \cmd{login} una volta che crea -una nuova shell per l'utente; ma quando si vuole cambiare soltanto +i privilegi di amministratore, in tal caso infatti l'esecuzione di una +\func{setuid} comporta il cambiamento di tutti gli identificatori associati al +processo, rendendo impossibile riguadagnare i privilegi di amministratore. +Questo comportamento è corretto per l'uso che ne fa \cmd{login} una volta che +crea una nuova shell per l'utente; ma quando si vuole cambiare soltanto l'\textsl{user-ID effettivo} del processo per cedere i privilegi occorre -ricorrere ad altre funzioni (si veda ad esempio \secref{sec:proc_seteuid}). - +ricorrere ad altre funzioni. -\subsection{Le funzioni \func{setreuid} e \func{setresuid}} -\label{sec:proc_setreuid} - -Queste due funzioni derivano da BSD che, non supportando\footnote{almeno fino - alla versione 4.3+BSD TODO, FIXME verificare e aggiornare la nota.} gli -identificatori del gruppo \textit{saved}, le usa per poter scambiare fra di -loro \textit{effective} e \textit{real}. I loro prototipi sono: +Le due funzioni \funcd{setreuid} e \funcd{setregid} derivano da BSD che, non +supportando\footnote{almeno fino alla versione 4.3+BSD TODO, FIXME verificare + e aggiornare la nota.} gli identificatori del gruppo \textit{saved}, le usa +per poter scambiare fra di loro \textit{effective} e \textit{real}. I +rispettivi prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} @@ -1609,12 +1592,12 @@ specificati da \param{rgid} e \param{egid}. \end{functions} La due funzioni sono analoghe ed il loro comportamento è identico; quanto -detto per la prima prima riguardo l'user-ID, si applica immediatamente alla -seconda per il group-ID. I processi non privilegiati possono impostare solo i -valori del loro user-ID effettivo o reale; valori diversi comportano il -fallimento della chiamata; l'amministratore invece può specificare un valore -qualunque. Specificando un argomento di valore -1 l'identificatore -corrispondente verrà lasciato inalterato. +detto per la prima riguardo l'user-ID, si applica immediatamente alla seconda +per il group-ID. I processi non privilegiati possono impostare solo i valori +del loro user-ID effettivo o reale; valori diversi comportano il fallimento +della chiamata; l'amministratore invece può specificare un valore qualunque. +Specificando un argomento di valore -1 l'identificatore corrispondente verrà +lasciato inalterato. Con queste funzioni si possono scambiare fra loro gli user-ID reale e effettivo, e pertanto è possibile implementare un comportamento simile a @@ -1639,13 +1622,10 @@ che si imposta un qualunque valore diverso da quello dall'user-ID reale corrente, l'user-ID salvato viene automaticamente uniformato al valore dell'user-ID effettivo. - -\subsection{Le funzioni \funcd{seteuid} e \funcd{setegid}} -\label{sec:proc_seteuid} - -Queste funzioni sono un'estensione allo standard POSIX.1 (ma sono comunque -supportate dalla maggior parte degli Unix) e vengono usate per cambiare gli -identificatori del gruppo \textit{effective}; i loro prototipi sono: +Altre due funzioni, \funcd{seteuid} e \funcd{setegid}, sono un'estensione +dello standard POSIX.1, ma sono comunque supportate dalla maggior parte degli +Unix; esse vengono usate per cambiare gli identificatori del gruppo +\textit{effective} ed i loro prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} @@ -1668,12 +1648,11 @@ all'amministratore di impostare solo l'user-ID effettivo, dato che l'uso normale di \func{setuid} comporta l'impostazione di tutti gli identificatori. -\subsection{Le funzioni \funcd{setresuid} e \funcd{setresgid}} -\label{sec:proc_setresuid} - -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: +Le due funzioni \funcd{setresuid} e \funcd{setresgid} sono invece +un'estensione introdotta in Linux,\footnote{per essere precisi a partire dal + kernel 2.1.44.} e permettono un completo controllo su tutti e tre i gruppi +di identificatori (\textit{real}, \textit{effective} e \textit{saved}), i loro +prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} @@ -1696,7 +1675,7 @@ Le due funzioni sono identiche, quanto detto per la prima riguardo gli user-ID si applica alla seconda per i group-ID. I processi non privilegiati possono cambiare uno qualunque degli user-ID solo ad un valore corrispondente o all'user-ID reale, o a quello effettivo o a quello salvato, l'amministratore -può specificare i valori che vuole; un valore di -1 per un qualunque parametro +può specificare i valori che vuole; un valore di -1 per un qualunque argomento lascia inalterato l'identificatore corrispondente. Per queste funzioni esistono anche due controparti che permettono di leggere @@ -1720,33 +1699,33 @@ corrente. 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 -gli identificatori del gruppo \textit{saved}. +specificati come puntatori (è un altro esempio di +\index{\textit{value~result~argument}}\textit{value result argument}). Si noti +che queste funzioni sono le uniche in grado di leggere gli identificatori del +gruppo \textit{saved}. -\subsection{Le funzioni \func{setfsuid} e \func{setfsgid}} -\label{sec:proc_setfsuid} - -Queste funzioni sono usate per impostare gli identificatori del gruppo -\textit{filesystem} che usati da Linux per il controllo dell'accesso ai file. -Come già accennato in \secref{sec:proc_access_id} Linux definisce questo -ulteriore gruppo di identificatori, che di norma sono assolutamente -equivalenti a quelli del gruppo \textit{effective}, dato che ogni cambiamento -di questi ultimi viene immediatamente riportato su di essi. +Infine le funzioni \func{setfsuid} e \func{setfsgid} servono per impostare gli +identificatori del gruppo \textit{filesystem} che sono usati da Linux per il +controllo dell'accesso ai file. Come già accennato in +sez.~\ref{sec:proc_access_id} Linux definisce questo ulteriore gruppo di +identificatori, che in circostanze normali sono assolutamente equivalenti a +quelli del gruppo \textit{effective}, dato che ogni cambiamento di questi +ultimi viene immediatamente riportato su di essi. C'è un solo caso in cui si ha necessità di introdurre una differenza fra gli identificatori dei gruppi \textit{effective} e \textit{filesystem}, ed è per ovviare ad un problema di sicurezza che si presenta quando si deve -implementare un server NFS. Il server NFS infatti deve poter cambiare -l'identificatore con cui accede ai file per assumere l'identità del singolo -utente remoto, ma se questo viene fatto cambiando l'user-ID effettivo o -l'user-ID reale il server si espone alla ricezione di eventuali segnali ostili -da parte dell'utente di cui ha temporaneamente assunto l'identità. Cambiando -solo l'user-ID di filesystem si ottengono i privilegi necessari per accedere ai -file, mantenendo quelli originari per quanto riguarda tutti gli altri -controlli di accesso, così che l'utente non possa inviare segnali al server -NFS. +implementare un server NFS. + +Il server NFS infatti deve poter cambiare l'identificatore con cui accede ai +file per assumere l'identità del singolo utente remoto, ma se questo viene +fatto cambiando l'user-ID effettivo o l'user-ID reale il server si espone alla +ricezione di eventuali segnali ostili da parte dell'utente di cui ha +temporaneamente assunto l'identità. Cambiando solo l'user-ID di filesystem si +ottengono i privilegi necessari per accedere ai file, mantenendo quelli +originari per quanto riguarda tutti gli altri controlli di accesso, così che +l'utente non possa inviare segnali al server NFS. Le due funzioni usate per cambiare questi identificatori sono \funcd{setfsuid} e \funcd{setfsgid}, ovviamente sono specifiche di Linux e non devono essere @@ -1769,17 +1748,20 @@ coincide con uno dei di quelli del gruppo \textit{real}, \textit{effective} o \textit{saved}. -\subsection{Le funzioni \func{setgroups} e \func{getgroups}} +\subsection{Le funzioni per la gestione dei gruppi associati a un processo} \label{sec:proc_setgroups} Le ultime funzioni che esamineremo sono quelle che permettono di operare sui -gruppi supplementari. Ogni processo può avere fino a \const{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 è -\funcd{getgroups}; questa funzione è definita nello standard POSIX ed il suo -prototipo è: +gruppi supplementari cui un utente può appartenere. Ogni processo può avere +almeno \const{NGROUPS\_MAX} gruppi supplementari\footnote{il numero massimo di + gruppi secondari può essere ottenuto con \func{sysconf} (vedi + sez.~\ref{sec:sys_sysconf}), leggendo il parametro + \texttt{\_SC\_NGROUPS\_MAX}.} 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 associati ad un +processo è \funcd{getgroups}; questa funzione è definita nello standard +POSIX.1, ed il suo prototipo è: \begin{functions} \headdecl{sys/types.h} \headdecl{unistd.h} @@ -1805,7 +1787,7 @@ specifica un valore di \param{size} uguale a 0 \param{list} non viene modificato, ma si ottiene il numero di gruppi supplementari. Una seconda funzione, \funcd{getgrouplist}, può invece essere usata per -ottenere tutti i gruppi a cui appartiene un utente; il suo prototipo è: +ottenere tutti i gruppi a cui appartiene un certo utente; il suo prototipo è: \begin{functions} \headdecl{sys/types.h} \headdecl{grp.h} @@ -1817,12 +1799,12 @@ 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} -La funzione legge i gruppi supplementari dell'utente \param{user} eseguendo -una scansione del database dei gruppi (si veda \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. +La funzione legge i gruppi supplementari dell'utente specificato da +\param{user}, eseguendo una scansione del database dei gruppi (si veda +sez.~\ref{sec:sys_user_group}). Ritorna poi 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 impostare i gruppi supplementari di un processo ci sono due funzioni, che possono essere usate solo se si hanno i privilegi di amministratore. La prima @@ -1849,7 +1831,7 @@ La funzione imposta i gruppi supplementari del processo corrente ai valori specificati nel vettore passato con l'argomento \param{list}, di dimensioni date dall'argomento \param{size}. Il numero massimo di gruppi supplementari è un parametro di sistema, che può essere ricavato con le modalità spiegate in -\secref{sec:sys_characteristics}. +sez.~\ref{sec:sys_characteristics}. Se invece si vogliono impostare i gruppi supplementari del processo a quelli di un utente specifico, si può usare \funcd{initgroups} il cui prototipo è: @@ -1874,17 +1856,24 @@ con cui costruisce una lista di gruppi supplementari, a cui aggiunge anche \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 quando si definisce \macro{\_POSIX\_SOURCE} o si -compila con il flag \cmd{-ansi}. +compila con il flag \cmd{-ansi}, è pertanto meglio evitarle se si vuole +scrivere codice portabile. + + +%\subsection{La gestione delle capabilities} +%\label{sec:proc_capabilities} + + \section{La gestione della priorità di esecuzione} \label{sec:proc_priority} In questa sezione tratteremo più approfonditamente i meccanismi con il quale -lo \textit{scheduler}\index{scheduler} 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. +lo \textit{scheduler}\index{\textit{scheduler}} 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}} @@ -1901,9 +1890,9 @@ cosiddetto \textit{prehemptive multitasking}: questo significa che al contrario di altri sistemi (che usano invece il cosiddetto \textit{cooperative multitasking}) non sono i singoli processi, ma il kernel stesso a decidere quando la CPU deve essere passata ad un altro processo. Come accennato in -\secref{sec:proc_hierarchy} questa scelta viene eseguita da una sezione -apposita del kernel, lo \textit{scheduler}\index{scheduler}, il cui scopo è -quello di distribuire al meglio il tempo di CPU fra i vari processi. +sez.~\ref{sec:proc_hierarchy} questa scelta viene eseguita da una sezione +apposita del kernel, lo \textit{scheduler}\index{\textit{scheduler}}, il cui +scopo è quello di distribuire al meglio il tempo di CPU fra i vari processi. La cosa è resa ancora più complicata dal fatto che con le architetture multi-processore si deve anche scegliere quale sia la CPU più opportuna da @@ -1926,7 +1915,7 @@ kernel provvedere a mettere in esecuzione un altro processo. Tutte queste possibilità sono caratterizzate da un diverso \textsl{stato} del processo, in Linux un processo può trovarsi in uno degli stati riportati in -\tabref{tab:proc_proc_states}; ma soltanto i processi che sono nello stato +tab.~\ref{tab:proc_proc_states}; ma soltanto i processi che sono nello stato \textit{runnable} concorrono per l'esecuzione. Questo vuol dire che, qualunque sia la sua priorità, un processo non potrà mai essere messo in esecuzione fintanto che esso si trova in uno qualunque degli altri stati. @@ -1942,7 +1931,7 @@ fintanto che esso si trova in uno qualunque degli altri stati. \textbf{Runnable}& \texttt{R} & Il processo è in esecuzione o è pronto ad essere eseguito (cioè è in attesa che gli venga assegnata la CPU). \\ - \textbf{Sleep} & \texttt{S} & Il processo processo è in attesa di un + \textbf{Sleep} & \texttt{S} & Il processo è in attesa di un risposta dal sistema, ma può essere interrotto da un segnale. \\ \textbf{Uninterrutible Sleep}& \texttt{D} & Il processo è in @@ -1970,12 +1959,12 @@ non 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. +sez.~\ref{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 @@ -1996,7 +1985,7 @@ Ovviamente questo avviene solo per i processi che sono pronti per essere eseguiti (cioè nello stato \textit{runnable}). 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}. +sez.~\ref{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 @@ -2023,24 +2012,29 @@ questo la priorit essere eseguito, e quando un processo potrà subentrare ad un altro nell'esecuzione. -Il meccanismo usato da Linux è piuttosto semplice, ad ogni processo è -assegnata una \textit{time-slice}, cioè in intervallo di tempo (letteralmente -una fetta) per il quale esso deve essere eseguito. Il valore della -\textit{time-slice} è controllato dalla cosiddetta \textit{nice} (o -\textit{niceness}) del processo. Essa è contenuta nel campo \var{nice} di -\struct{task\_struct}; tutti i processi vengono creati con lo stesso valore, -ed essa specifica il valore della durata iniziale della \textit{time-slice} -che viene assegnato ad un altro campo della struttura (\var{counter}) quando -il processo viene eseguito per la prima volta e diminuito progressivamente ad -ogni interruzione del timer. - -Quando lo scheduler\index{scheduler} viene eseguito scandisce la coda dei -processi in stato \textit{runnable} associando, sulla base del valore di -\var{counter}, un peso a ciascun processo in attesa di esecuzione,\footnote{il +Il meccanismo usato da Linux è piuttosto semplice,\footnote{in realtà nella + serie 2.6.x lo \textit{scheduler} è stato riscritto da zero e può usare + diversi algoritmi, selezionabili sia in fase di compilazione, che, nelle + versioni più recenti, all'avvio (addirittura è stato ideato un sistema + modulare che permette di cambiare lo scheduler al volo, che comunque non è + incluso nel kernel ufficiale).} ad ogni processo è assegnata una +\textit{time-slice}, cioè un intervallo di tempo (letteralmente una fetta) per +il quale esso deve essere eseguito. Il valore della \textit{time-slice} è +controllato dalla cosiddetta \textit{nice} (o \textit{niceness}) del processo. +Essa è contenuta nel campo \var{nice} di \struct{task\_struct}; tutti i +processi vengono creati con lo stesso valore, ed essa specifica il valore +della durata iniziale della \textit{time-slice} che viene assegnato ad un +altro campo della struttura (\var{counter}) quando il processo viene eseguito +per la prima volta e diminuito progressivamente ad ogni interruzione del +timer. + +Durante la sua esecuzione lo scheduler\index{\textit{scheduler}} scandisce la +coda dei processi in stato \textit{runnable} associando, in base al valore di +\var{counter}, un peso ad ogni processo in attesa di esecuzione,\footnote{il calcolo del peso in realtà è un po' più complicato, ad esempio nei sistemi - multiprocessore viene favorito un processo che è eseguito sulla stessa CPU, - e a parità del valore di \var{counter} viene favorito chi ha una priorità - più elevata.} chi ha il peso più alto verrà posto in esecuzione, ed il + multiprocessore viene favorito un processo eseguito sulla stessa CPU, e a + parità del valore di \var{counter} viene favorito chi ha una priorità più + elevata.} chi ha il peso più alto verrà posto in esecuzione, ed il precedente processo sarà spostato in fondo alla coda. Dato che ad ogni interruzione del timer il valore di \var{counter} del processo corrente viene diminuito, questo assicura che anche i processi con priorità più bassa @@ -2048,13 +2042,13 @@ verranno messi in esecuzione. La priorità di un processo è così controllata attraverso il valore di \var{nice}, che stabilisce la durata della \textit{time-slice}; per il -meccanismo appena descritto infatti un valore più lungo infatti assicura una -maggiore attribuzione di CPU. L'origine del nome di questo parametro sta nel -fatto che generalmente questo viene usato per diminuire la priorità di un -processo, come misura di cortesia nei confronti degli altri. I processi -infatti vengono creati dal sistema con lo stesso valore di \var{nice} (nullo) -e nessuno è privilegiato rispetto agli altri; il valore può essere modificato -solo attraverso la funzione \funcd{nice}, il cui prototipo è: +meccanismo appena descritto infatti un valore più lungo assicura una maggiore +attribuzione di CPU. L'origine del nome di questo parametro sta nel fatto che +generalmente questo viene usato per diminuire la priorità di un processo, come +misura di cortesia nei confronti degli altri. I processi infatti vengono +creati dal sistema con lo stesso valore di \var{nice} (nullo) e nessuno è +privilegiato rispetto agli altri; il valore può essere modificato solo +attraverso la funzione \funcd{nice}, il cui prototipo è: \begin{prototype}{unistd.h} {int nice(int inc)} Aumenta il valore di \var{nice} per il processo corrente. @@ -2093,15 +2087,16 @@ Restituisce il valore di \var{nice} per l'insieme dei processi specificati. \item[\errcode{EINVAL}] il valore di \param{which} non è valido. \end{errlist}} \end{prototype} -\noindent (in vecchie versioni può essere necessario includere anche +\noindent nelle vecchie versioni può essere necessario includere anche \file{}, questo non è più necessario con versioni recenti delle -librerie, ma è comunque utile per portabilità). +librerie, ma è comunque utile per portabilità. -La funzione permette di leggere la priorità di un processo, di un gruppo di -processi (vedi \secref{sec:sess_proc_group}) o di un utente, a seconda del -valore di \param{which}, secondo la legenda di \tabref{tab:proc_getpriority}, -specificando un corrispondente valore per \param{who}; un valore nullo di -quest'ultimo indica il processo, il gruppo di processi o l'utente correnti. +La funzione permette, a seconda del valore di \param{which}, di leggere la +priorità di un processo, di un gruppo di processi (vedi +sez.~\ref{sec:sess_proc_group}) o di un utente, specificando un corrispondente +valore per \param{who} secondo la legenda di tab.~\ref{tab:proc_getpriority}; +un valore nullo di quest'ultimo indica il processo, il gruppo di processi o +l'utente correnti. \begin{table}[htb] \centering @@ -2161,48 +2156,57 @@ Ultrix, *BSD) la corrispondenza pu \subsection{Il meccanismo di \textit{scheduling real-time}} \label{sec:proc_real_time} -Come spiegato in \secref{sec:proc_sched} lo standard POSIX.1b ha introdotto le -priorità assolute per permettere la gestione di processi real-time. In realtà -nel caso di Linux non si tratta di un vero hard real-time, in quanto in +Come spiegato in sez.~\ref{sec:proc_sched} lo standard POSIX.1b ha introdotto +le priorità assolute per permettere la gestione di processi real-time. In +realtà nel caso di Linux non si tratta di un vero hard real-time, in quanto in presenza di eventuali interrupt il kernel interrompe l'esecuzione di un processo qualsiasi sia la sua priorità,\footnote{questo a meno che non si siano installate le patch di RTLinux, RTAI o Adeos, con i quali è possibile ottenere un sistema effettivamente hard real-time. In tal caso infatti gli interrupt vengono intercettati dall'interfaccia real-time (o nel caso di - Adeos gestiti dalle code del nano-kernel), in modo da poterlo controllare + Adeos gestiti dalle code del nano-kernel), in modo da poterli controllare direttamente qualora ci sia la necessità di avere un processo con priorità più elevata di un \textit{interrupt handler}.} mentre con l'incorrere in un -page fault\index{page fault} si possono avere ritardi non previsti. Se -l'ultimo problema può essere aggirato attraverso l'uso delle funzioni di -controllo della memoria virtuale (vedi \secref{sec:proc_mem_lock}), il primo +page fault\index{\textit{page~fault}} si possono avere ritardi non previsti. +Se l'ultimo problema può essere aggirato attraverso l'uso delle funzioni di +controllo della memoria virtuale (vedi sez.~\ref{sec:proc_mem_lock}), il primo non è superabile e può comportare ritardi non prevedibili riguardo ai tempi di esecuzione di qualunque processo. -In ogni caso occorre usare le priorità assolute con molta attenzione: se si dà -ad un processo una priorità assoluta e questo finisce in un loop infinito, -nessun altro processo potrà essere eseguito, ed esso sarà mantenuto in -esecuzione permanentemente assorbendo tutta la CPU e senza nessuna possibilità -di riottenere l'accesso al sistema. Per questo motivo è sempre opportuno, -quando si lavora con processi che usano priorità assolute, tenere attiva una -shell cui si sia assegnata la massima priorità assoluta, in modo da poter -essere comunque in grado di rientrare nel sistema. - -Quando c'è un processo con priorità assoluta lo scheduler\index{scheduler} lo -metterà in esecuzione prima di ogni processo normale. In caso di più processi -sarà eseguito per primo quello con priorità assoluta più alta. Quando ci sono -più processi con la stessa priorità assoluta questi vengono tenuti in una coda -tocca al kernel decidere quale deve essere eseguito. - -Il meccanismo con cui vengono gestiti questi processi dipende dalla politica -di scheduling che si è scelto; lo standard ne prevede due: -\begin{basedescript}{\desclabelwidth{2cm}\desclabelstyle{\nextlinelabel}} +Occorre usare le priorità assolute con molta attenzione: se si dà ad un +processo una priorità assoluta e questo finisce in un loop infinito, nessun +altro processo potrà essere eseguito, ed esso sarà mantenuto in esecuzione +permanentemente assorbendo tutta la CPU e senza nessuna possibilità di +riottenere l'accesso al sistema. Per questo motivo è sempre opportuno, quando +si lavora con processi che usano priorità assolute, tenere attiva una shell +cui si sia assegnata la massima priorità assoluta, in modo da poter essere +comunque in grado di rientrare nel sistema. + +Quando c'è un processo con priorità assoluta lo +scheduler\index{\textit{scheduler}} lo metterà in esecuzione prima di ogni +processo normale. In caso di più processi sarà eseguito per primo quello con +priorità assoluta più alta. Quando ci sono più processi con la stessa priorità +assoluta questi vengono tenuti in una coda e tocca al kernel decidere quale +deve essere eseguito. Il meccanismo con cui vengono gestiti questi processi +dipende dalla politica di scheduling che si è scelto; lo standard ne prevede +due: +\begin{basedescript}{\desclabelwidth{1.2cm}\desclabelstyle{\nextlinelabel}} \item[\textit{FIFO}] \textit{First In First Out}. Il processo viene eseguito - fintanto che non cede volontariamente la CPU, si blocca, finisce o viene - interrotto da un processo a priorità più alta. -\item[\textit{RR}] \textit{Round Robin}. Ciascun processo viene eseguito a - turno per un certo periodo di tempo (una \textit{time slice}). Solo i - processi con la stessa priorità ed in stato \textit{runnable} entrano nel - circolo. + fintanto che non cede volontariamente la CPU (con \func{sched\_yield}), si + blocca, finisce o viene interrotto da un processo a priorità più alta. Se il + processo viene interrotto da uno a priorità più alta esso resterà in cima + alla lista e sarà il primo ad essere eseguito quando i processi a priorità + più alta diverranno inattivi. Se invece lo si blocca volontariamente sarà + posto in coda alla lista (ed altri processi con la stessa priorità potranno + essere eseguiti). +\item[\textit{RR}] \textit{Round Robin}. Il comportamento è del tutto analogo + a quello precedente, con la sola differenza che ciascun processo viene + eseguito al massimo per un certo periodo di tempo (la cosiddetta + \textit{time slice}) dopo di che viene automaticamente posto in fondo alla + coda dei processi con la stessa priorità. In questo modo si ha comunque una + esecuzione a turno di tutti i processi, da cui il nome della politica. Solo + i processi con la stessa priorità ed in stato \textit{runnable} entrano nel + \textsl{girotondo}. \end{basedescript} La funzione per impostare le politiche di scheduling (sia real-time che @@ -2219,23 +2223,22 @@ prototipo \item[\errcode{EINVAL}] il valore di \param{policy} non esiste o il relativo valore di \param{p} non è valido. \item[\errcode{EPERM}] il processo non ha i privilegi per attivare la - politica richiesta (vale solo per \const{SCHED\_FIFO} e - \const{SCHED\_RR}). + politica richiesta. \end{errlist}} \end{prototype} La funzione esegue l'impostazione per il processo specificato dall'argomento \param{pid}; un valore nullo esegue l'impostazione per il processo corrente. -Solo un processo con i privilegi di amministratore può impostare delle -priorità assolute diverse da zero. La politica di scheduling è specificata -dall'argomento \param{policy} i cui possibili valori sono riportati in -\tabref{tab:proc_sched_policy}; un valore negativo per \param{policy} mantiene -la politica di scheduling corrente. +La politica di scheduling è specificata dall'argomento \param{policy} i cui +possibili valori sono riportati in tab.~\ref{tab:proc_sched_policy}; un valore +negativo per \param{policy} mantiene la politica di scheduling corrente. +Solo un processo con i privilegi di amministratore può impostare priorità +assolute diverse da zero o politiche \const{SCHED\_FIFO} e \const{SCHED\_RR}. \begin{table}[htb] \centering \footnotesize - \begin{tabular}[c]{|c|l|} + \begin{tabular}[c]{|l|l|} \hline \textbf{Policy} & \textbf{Significato} \\ \hline @@ -2246,36 +2249,39 @@ la politica di scheduling corrente. \const{SCHED\_OTHER}& Scheduling ordinario\\ \hline \end{tabular} - \caption{Valori dell'argomento \param{policy} per la funzione - \func{sched\_setscheduler}. } + \caption{Valori dell'argomento \param{policy} per la funzione + \func{sched\_setscheduler}.} \label{tab:proc_sched_policy} \end{table} Il valore della priorità è passato attraverso la struttura -\struct{sched\_param} (riportata in \figref{fig:sig_sched_param}), il cui solo -campo attualmente definito è \var{sched\_priority}, che nel caso delle +\struct{sched\_param} (riportata in fig.~\ref{fig:sig_sched_param}), il cui +solo campo attualmente definito è \var{sched\_priority}, che nel caso delle priorità assolute deve essere specificato nell'intervallo fra un valore -massimo ed uno minimo, che nel caso sono rispettivamente 1 e 99 (il valore -zero è legale, ma indica i processi normali). +massimo ed uno minimo, che nel caso sono rispettivamente 1 e 99; il valore +nullo è legale, ma indica i processi normali. -\begin{figure}[!htb] +\begin{figure}[!bht] \footnotesize \centering \begin{minipage}[c]{15cm} - \begin{lstlisting}[labelstep=0]{}%,frame=,indent=1cm]{} -struct sched_param { - int sched_priority; -}; - \end{lstlisting} + \includestruct{listati/sched_param.c} \end{minipage} \normalsize \caption{La struttura \structd{sched\_param}.} \label{fig:sig_sched_param} \end{figure} +Si tenga presente che quando si imposta una politica di scheduling real-time +per un processo (o se ne cambia la priorità con \func{sched\_setparam}) questo +viene messo in cima alla lista dei processi con la stessa priorità; questo +comporta che verrà eseguito subito, interrompendo eventuali altri processi con +la stessa priorità in quel momento in esecuzione. + Lo standard POSIX.1b prevede comunque che i due valori della massima e minima priorità statica possano essere ottenuti, per ciascuna delle politiche di -scheduling realtime, tramite le due funzioni \funcd{sched\_get\_priority\_max} -e \funcd{sched\_get\_priority\_min}, i cui prototipi sono: +scheduling \textit{real-time}, tramite le due funzioni +\funcd{sched\_get\_priority\_max} e \funcd{sched\_get\_priority\_min}, i cui +prototipi sono: \begin{functions} \headdecl{sched.h} @@ -2289,7 +2295,7 @@ e \funcd{sched\_get\_priority\_min}, i cui prototipi sono: \bodydesc{La funzioni ritornano il valore della priorità in caso di successo e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: \begin{errlist} - \item[\errcode{EINVAL}] il valore di \param{policy} è invalido. + \item[\errcode{EINVAL}] il valore di \param{policy} non è valido. \end{errlist}} \end{functions} @@ -2310,25 +2316,6 @@ volontariamente la CPU (in tal caso, tornando nello stato \textit{runnable} sarà reinserito in coda alla lista); l'esecuzione viene ripresa subito solo nel caso che esso sia stato interrotto da un processo a priorità più alta. -La priorità assoluta può essere riletta indietro dalla funzione -\funcd{sched\_getscheduler}, il cui prototipo è: -\begin{prototype}{sched.h} -{int sched\_getscheduler(pid\_t pid)} - Legge la politica di scheduling per il processo \param{pid}. - - \bodydesc{La funzione ritorna la politica di scheduling in caso di successo - e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: - \begin{errlist} - \item[\errcode{ESRCH}] il processo \param{pid} non esiste. - \item[\errcode{EINVAL}] il valore di \param{pid} è negativo. - \end{errlist}} -\end{prototype} - -La funzione restituisce il valore (secondo la quanto elencato in -\tabref{tab:proc_sched_policy}) della politica di scheduling per il processo -specificato; se \param{pid} è nullo viene restituito quello del processo -chiamante. - Se si intende operare solo sulla priorità assoluta di un processo si possono usare le funzioni \funcd{sched\_setparam} e \funcd{sched\_getparam}, i cui prototipi sono: @@ -2338,7 +2325,6 @@ prototipi sono: \funcdecl{int sched\_setparam(pid\_t pid, const struct sched\_param *p)} Imposta la priorità assoluta del processo \param{pid}. - \funcdecl{int sched\_getparam(pid\_t pid, struct sched\_param *p)} Legge la priorità assoluta del processo \param{pid}. @@ -2346,7 +2332,10 @@ prototipi sono: e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: \begin{errlist} \item[\errcode{ESRCH}] il processo \param{pid} non esiste. - \item[\errcode{EINVAL}] il valore di \param{pid} è negativo. + \item[\errcode{EINVAL}] il valore di \param{p} non ha senso per la + politica scelta. + \item[\errcode{EPERM}] il processo non ha i privilegi sufficienti per + eseguire l'operazione. \end{errlist}} \end{functions} @@ -2355,7 +2344,31 @@ L'uso di \func{sched\_setparam} che \func{sched\_setscheduler} specificando 0 come valore di \param{pid} si opera sul processo corrente. La disponibilità di entrambe le funzioni può essere verificata controllando la macro \macro{\_POSIX\_PRIORITY\_SCHEDULING} che è -definita nell'header \file{sched.h}. +definita nell'header \file{sched.h}. + +Si tenga presente che per eseguire la funzione il processo chiamante deve +avere un user-ID effettivo uguale all'user-ID reale o a quello effettivo del +processo di cui vuole cambiare la priorità, oppure deve avere i privilegi di +amministratore (con la capacità \const{CAP\_SYS\_NICE}). + +La priorità assoluta può essere riletta indietro dalla funzione +\funcd{sched\_getscheduler}, il cui prototipo è: +\begin{prototype}{sched.h} +{int sched\_getscheduler(pid\_t pid)} + Legge la politica di scheduling per il processo \param{pid}. + + \bodydesc{La funzione ritorna la politica di scheduling in caso di successo + e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EINVAL}] il valore di \param{pid} è negativo. + \end{errlist}} +\end{prototype} + +La funzione restituisce il valore (secondo quanto elencato in +tab.~\ref{tab:proc_sched_policy}) della politica di scheduling per il processo +specificato; se \param{pid} è nullo viene restituito quello del processo +chiamante. L'ultima funzione che permette di leggere le informazioni relative ai processi real-time è \funcd{sched\_rr\_get\_interval}, che permette di ottenere la @@ -2365,7 +2378,7 @@ il suo prototipo {int sched\_rr\_get\_interval(pid\_t pid, struct timespec *tp)} Legge in \param{tp} la durata della \textit{time slice} per il processo \param{pid}. - \bodydesc{La funzione ritorna 0in caso di successo e -1 in caso di errore, + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: \begin{errlist} \item[\errcode{ESRCH}] il processo \param{pid} non esiste. @@ -2375,7 +2388,10 @@ il suo prototipo La funzione restituisce il valore dell'intervallo di tempo usato per la politica \textit{round robin} in una struttura \struct{timespec}, (la cui -definizione si può trovare in \figref{fig:sys_timeval_struct}). +definizione si può trovare in fig.~\ref{fig:sys_timeval_struct}). In realtà +dato che in Linux questo intervallo di tempo è prefissato e non modificabile, +questa funzione ritorna sempre un valore di 150 millisecondi, e non importa +specificare il PID di un processo reale. Come accennato ogni processo che usa lo scheduling real-time può rilasciare @@ -2397,6 +2413,178 @@ l'esecuzione non sar in modalità \textit{fifo}, per permettere l'esecuzione degli altri processi con pari priorità quando la sezione più urgente è finita. +Infine con il supporto dei sistemi multiprocessore sono state introdotte delle +funzioni che permettono di controllare in maniera più dettagliata la scelta di +quale processore utilizzare per eseguire un certo programma. Uno dei problemi +che si pongono nei sistemi multiprocessore è infatti quello +dell'\textsl{effetto ping-pong}.\index{\textsl{effetto ping-pong}} Può +accadere cioè che lo scheduler, quando riavvia un processo precedentemente +interrotto, scegliendo il primo processore disponibile lo faccia eseguire da +un processore diverso rispetto a quello su cui era stato eseguito in +precedenza. Se il processo passa da un processore all'altro in questo modo +(cosa che avveniva abbastanza di frequente con i kernel della seria 2.4.x) si +ha l'\textsl{effetto ping-pong}. + +Questo tipo di comportamento può generare dei seri problemi di prestazioni; +infatti tutti i processori moderni utilizzano una memoria interna (la +\textit{cache}) contenente i dati più usati, che permette di evitare di +eseguire un accesso (molto più lento) alla memoria principale sulla scheda +madre. Chiaramente un processo sarà favorito se i suoi dati sono nella cache +del processore, ma è ovvio che questo può essere vero solo per un processore +alla volta, perché in presenza di più copie degli stessi dati su più +processori, non si potrebbe determinare quale di questi ha la versione dei +dati aggiornata rispetto alla memoria principale. + +Questo comporta che quando un processore inserisce un dato nella sua cache, +tutti gli altri processori che hanno lo stesso dato devono invalidarlo, e +questa operazione è molto costosa in termini di prestazioni. Il problema +diventa serio quando si verifica l'\textsl{effetto ping-pong}, in tal caso +infatti un processo \textsl{rimbalza} continuamente da un processore all'altro +e si ha una continua invalidazione della cache, che non diventa mai +disponibile. + +Per ovviare a questo tipo di problemi è nato il concetto di \textsl{affinità + di processore} (o \index{\textit{CPU~affinity}}\textit{CPU affinity}); la +possibilità cioè di far sì che un processo possa essere assegnato per +l'esecuzione sempre allo stesso processore. Lo scheduler dei kernel della +serie 2.4.x aveva una scarsa \textit{CPU affinity}, e l'effetto ping-pong era +comune; con il nuovo scheduler dei kernel della 2.6.x questo problema è stato +risolto ed esso cerca di mantenere il più possibile ciascun processo sullo +stesso processore. + +In certi casi però resta l'esigenza di poter essere sicuri che un processo sia +sempre eseguito dallo stesso processore,\footnote{quella che viene detta + \textit{hard CPU affinity}, in contrasto con quella fornita dallo scheduler, + detta \textit{soft CPU affinity}, che di norma indica solo una preferenza, + non un requisito assoluto.} e per poter risolvere questo tipo di +problematiche nei nuovi kernel\footnote{le due system call per la gestione + della \textit{CPU affinity} sono state introdotte nel kernel 2.5.8, e le + funzioni di libreria nelle \textsl{glibc} 2.3.} è stata introdotta +l'opportuna infrastruttura ed una nuova system call che permette di impostare +su quali processori far eseguire un determinato processo attraverso una +\textsl{maschera di affinità}. La corrispondente funzione di libreria è +\funcd{sched\_setaffinity} ed il suo prototipo\footnote{di questa funzione (e + della corrispondente \func{sched\_setaffinity}) esistono versioni diverse + per gli argomenti successivi a \param{pid}: la prima (quella riportata nella + pagina di manuale) prevedeva due ulteriori argomenti di tipo + \texttt{unsigned int len} e \texttt{unsigned long *mask}, poi l'argomento + \texttt{len} è stato eliminato, successivamente si è introdotta la versione + riportata con però un secondo argomento di tipo \texttt{size\_t cpusetsize} + (anche questa citata nella pagina di manuale); la versione citata è quella + riportata nel manuale delle \textsl{glibc} e corripondente alla definizione + presente in \file{sched.h}.} è: +\begin{prototype}{sched.h} + {int sched\_setaffinity (pid\_t pid, const cpu\_set\_t *cpuset)} + Imposta la maschera di affinità del processo \param{pid}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EINVAL}] il valore di \param{cpuset} contiene riferimenti a + processori non esistenti nel sistema. + \item[\errcode{EPERM}] il processo non ha i privilegi sufficienti per + eseguire l'operazione. + \end{errlist} + ed inoltre anche \errval{EFAULT}.} +\end{prototype} + +La funzione imposta, con l'uso del valore contenuto all'indirizzo +\param{cpuset}, l'insieme dei processori sui quali deve essere eseguito il +processo identificato tramite il valore passato in \param{pid}. Come in +precedenza il valore nullo di \param{pid} indica il processo corrente. Per +poter utilizzare questa funzione sono richiesti i privilegi di amministratore +(è necessaria la capacità \const{CAP\_SYS\_NICE}) altrimenti essa fallirà con +un errore di \errcode{EPERM}. Una volta impostata una maschera di affinità, +questa viene ereditata attraverso una \func{fork}, in questo modo diventa +possibile legare automaticamente un gruppo di processi ad un singolo +processore. + +Nell'uso comune, almeno con i kernel della serie 2.6.x, l'uso di questa +funzione non è necessario, in quanto è lo scheduler stesso che provvede a +mantenere al meglio l'affinità di processore. Esistono però esigenze +particolari, ad esempio quando un processo (o un gruppo di processi) è +utilizzato per un compito importante (ad esempio per applicazioni real-time o +la cui risposta è critica) e si vuole la massima velocità, con questa +interfaccia diventa possibile selezionare gruppi di processori utilizzabili in +maniera esclusiva. Lo stesso dicasi quando l'accesso a certe risorse (memoria +o periferiche) può avere un costo diverso a seconda del processore (come +avviene nelle architetture NUMA). + +Infine se un gruppo di processi accede alle stesse risorse condivise (ad +esempio una applicazione con più thread) può avere senso usare lo stesso +processore in modo da sfruttare meglio l'uso della sua cache; questo +ovviamente riduce i benefici di un sistema multiprocessore nell'esecuzione +contemporanea dei thread, ma in certi casi (quando i thread sono inerentemente +serializzati nell'accesso ad una risorsa) possono esserci sufficienti vantaggi +nell'evitare la perdita della cache da rendere conveniente l'uso dell'affinità +di processore. + +Per facilitare l'uso dell'argomento \param{cpuset} le \acr{glibc} hanno +introdotto un apposito dato di tipo, \ctyp{cpu\_set\_t},\footnote{questa è una + estensione specifica delle \acr{glibc}, da attivare definendo la macro + \macro{\_GNU\_SOURCE}, non esiste infatti una standardardizzazione per + questo tipo di interfaccia e POSIX al momento non prevede nulla al + riguardo.} che permette di identificare un insieme di processori. Il dato è +una maschera binaria: in generale è un intero a 32 bit in cui ogni bit +corrisponde ad un processore, ma dato che per architetture particolari il +numero di bit di un intero può non essere sufficiente, è stata creata questa +che è una interfaccia generica che permette di usare a basso livello un tipo +di dato qualunque rendendosi indipendenti dal numero di bit e dalla loro +disposizione. + +Questa interfaccia, oltre alla definizione del tipo di dato apposito, prevede +anche una serie di macro di preprocessore per la manipolazione dello stesso, +che consentono di svuotare un insieme, aggiungere o togliere un processore da +esso o verificare se vi è già presente: +\begin{functions} + \headdecl{sched.h} + \funcdecl{void \macro{CPU\_ZERO}(cpu\_set\_t *set)} + Inizializza l'insieme (vuoto). + + \funcdecl{void \macro{CPU\_SET}(int cpu, cpu\_set\_t *set)} + Inserisce il processore \param{cpu} nell'insieme. + + \funcdecl{void \macro{CPU\_CLR}(int cpu, cpu\_set\_t *set)} + Rimuove il processore \param{cpu} nell'insieme. + + \funcdecl{int \macro{CPU\_ISSET}(int cpu, cpu\_set\_t *set)} + Controlla se il processore \param{cpu} è nell'insieme. +\end{functions} + +Oltre a queste macro, simili alle analoghe usate per gli insiemi di file +descriptor (vedi sez.~\ref{sec:file_select}) è definita la costante +\const{CPU\_SETSIZE} che indica il numero massimo di processori che possono +far parte dell'insieme, e che costituisce un limite massimo al valore +dell'argomento \param{cpu}. + +In generale la maschera di affinità è preimpostata in modo che un processo +possa essere eseguito su qualunque processore, se può comunque leggere il +valore per un processo specifico usando la funzione +\funcd{sched\_getaffinity}, il suo prototipo è: +\begin{prototype}{sched.h} + {int sched\_getaffinity (pid\_t pid, const cpu\_set\_t *cpuset)} + Legge la maschera di affinità del processo \param{pid}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EFAULT}] il valore di \param{cpuset} non è un indirizzo + valido. + \end{errlist} } +\end{prototype} + +La funzione restituirà all'indirizzo specificato da \param{cpuset} il valore +della maschera di affinità del processo, così da poterla riutilizzare per una +successiva reimpostazione. In questo caso non sono necessari privilegi +paricolari. + +È chiaro che queste funzioni per la gestione dell'affinità hanno significato +soltanto su un sistema multiprocessore, esse possono comunque essere +utilizzate anche in un sistema con un processore singolo, nel qual caso però +non avranno alcun risultato effettivo. + \section{Problematiche di programmazione multitasking} \label{sec:proc_multi_prog} @@ -2426,15 +2614,15 @@ In un ambiente multitasking il concetto essere interrotto in qualunque momento dal kernel che mette in esecuzione un altro processo o dalla ricezione di un segnale; occorre pertanto essere accorti nei confronti delle possibili -\textit{race condition}\index{race condition} (vedi -\secref{sec:proc_race_cond}) derivanti da operazioni interrotte in una fase in -cui non erano ancora state completate. +\textit{race condition}\index{\textit{race~condition}} (vedi +sez.~\ref{sec:proc_race_cond}) derivanti da operazioni interrotte in una fase +in 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 nelle operazioni con i file (vedremo alcuni esempi in -\secref{sec:file_atomic}). In questi casi in genere l'uso delle appropriate +cap.~\ref{cha:IPC}) o nelle operazioni con i file (vedremo alcuni esempi in +sez.~\ref{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 non possono essere interrotte (o subire interferenze pericolose) da altri @@ -2446,7 +2634,7 @@ qualunque momento, e le operazioni di un eventuale \textit{signal handler} 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:sig_control}). +sez.~\ref{sec:sig_control}). 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ò @@ -2460,20 +2648,20 @@ condiviso, onde evitare problemi con le ottimizzazioni del codice. -\subsection{Le \textit{race condition}\index{race condition} e i - \textit{deadlock}\index{deadlock}} +\subsection{Le \textit{race condition} ed i \textit{deadlock}} \label{sec:proc_race_cond} -Si definiscono \textit{race condition}\index{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 è 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. +\index{\textit{race~condition}|(} +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 è 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. Dato che in un sistema multitasking ogni processo può essere interrotto in -qualunque momento per farne subentrare un'altro in esecuzione, niente può +qualunque momento per farne subentrare un altro in esecuzione, niente può assicurare un preciso ordine di esecuzione fra processi diversi o che una sezione di un programma possa essere eseguita senza interruzioni da parte di altri. Queste situazioni comportano pertanto errori estremamente subdoli e @@ -2483,37 +2671,39 @@ funzioner Per questo occorre essere ben consapevoli di queste problematiche, e del fatto che l'unico modo per evitarle è quello di riconoscerle come tali e prendere gli adeguati provvedimenti per far sì che non si verifichino. Casi tipici di -\textit{race condition}\index{race condition} si hanno quando diversi processi -accedono allo stesso 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 sulle risorse condivise (le cosiddette -\textsl{sezioni critiche}\index{sezioni critiche}) del programma, siano +\textit{race condition} si hanno quando diversi processi accedono allo stesso +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 sulle risorse condivise (le cosiddette +\textsl{sezioni critiche}\index{sezioni~critiche}) del programma, siano opportunamente protette da meccanismi di sincronizzazione (torneremo su queste -problematiche di questo tipo in \capref{cha:IPC}). +problematiche di questo tipo in cap.~\ref{cha:IPC}). -Un caso particolare di \textit{race condition}\index{race condition} sono poi -i cosiddetti \textit{deadlock}\index{deadlock}, particolarmente gravi in -quanto comportano spesso il blocco completo di un servizio, e non il -fallimento di una singola operazione. Per definizione un -\textit{deadlock}\index{deadlock} è una situazione in cui due o più processi +\index{\textit{deadlock}|(} +Un caso particolare di \textit{race condition} sono poi i cosiddetti +\textit{deadlock}, particolarmente gravi in quanto comportano spesso il blocco +completo di un servizio, e non il fallimento di una singola operazione. Per +definizione un \textit{deadlock} è una situazione in cui due o più processi non sono più in grado di proseguire perché ciascuno aspetta il risultato di una operazione che dovrebbe essere eseguita dall'altro. L'esempio tipico di una situazione che può condurre ad un -\textit{deadlock}\index{deadlock} è quello in cui un flag di +\textit{deadlock} è quello in cui un flag di ``\textsl{occupazione}'' viene rilasciato da un evento asincrono (come un segnale o un altro processo) fra il momento in cui lo si è controllato (trovandolo occupato) e la successiva operazione di attesa per lo sblocco. In questo caso, dato che l'evento di sblocco del flag è avvenuto senza che ce ne accorgessimo proprio fra il controllo e la messa in attesa, quest'ultima -diventerà perpetua (da cui il nome di \textit{deadlock}\index{deadlock}). +diventerà perpetua (da cui il nome di \textit{deadlock}). In tutti questi casi è di fondamentale importanza il concetto di atomicità -visto in \secref{sec:proc_atom_oper}; questi problemi infatti possono essere +visto in sez.~\ref{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. +\index{\textit{race~condition}|)} +\index{\textit{deadlock}|)} \subsection{Le funzioni rientranti}