X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=f18d49691edca1f0da63708c78dd5076dc5c981b;hp=5fe5845314eab353f3866216f577efe4110fc74a;hb=71bd769469078fc921d9646d62b0f9293b6ae47c;hpb=1cfff906c9b4642d18aa657f2b69b24a10cc5af3 diff --git a/prochand.tex b/prochand.tex index 5fe5845..f18d496 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-2006 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}. +\itindex{process~table} \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,47 +143,47 @@ 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 -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 -interrupt periodico secondo la frequenza specificata dalla costante -\const{HZ}, definita in \file{asm/param.h}, ed il cui valore è espresso in -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}).} -%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. +Come accennato in sez.~\ref{sec:intro_unix_struct} è lo \textit{scheduler} +\itindex{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.} +% TODO completare questa parte. +(ma può essere anche attivato esplicitamente). Il timer di sistema provvede +comunque a che esso sia invocato periodicamente, generando un interrupt +periodico secondo la frequenza specificata dalla costante \const{HZ}, definita +in \file{asm/param.h}, ed il cui valore è espresso in Hertz.\footnote{Fino al + kernel 2.4 il valore usuale di questa costante era 100, per tutte le + architetture eccetto l'alpha, per la quale era 1000. Occorre fare attenzione + a non confondere questo valore con quello dei clock tick (vedi + sez.~\ref{sec:sys_unix_time}).} +% TODO verificare gli ultimi cambiamenti del 2.6 +% Si ha cioè un interrupt dal timer ogni centesimo di secondo. + +Ogni volta che viene eseguito, lo \textit{scheduler} \itindex{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. @@ -231,18 +232,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 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 +264,30 @@ 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 \itindex{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}} @@ -318,22 +319,22 @@ prototipo della funzione 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 -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. +copia del padre, e riceve una copia dei segmenti di testo, \itindex{stack} +stack e dati (vedi 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\index{segmento!testo} 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} +\itindex{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 +346,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; i0$ & -- &attende per un figlio il cui \acr{pid} è uguale al - valore di \param{pid}.\\ + $<-1$& -- & attende per un figlio il cui + \itindex{process~group} \textit{process group} + (vedi sez.~\ref{sec:sess_proc_group}) è uguale + al valore assoluto di \param{pid}. \\ + $-1$& \const{WAIT\_ANY} & attende per un figlio qualsiasi, usata in + questa maniera è equivalente a \func{wait}.\\ + $0$ &\const{WAIT\_MYPGRP}&attende per un figlio il cui + \itindex{process~group} \textit{process group} è + uguale a quello del processo chiamante. \\ + $>0$& -- & attende per un figlio il cui \acr{pid} è uguale + al valore di \param{pid}.\\ \hline \end{tabular} \caption{Significato dei valori dell'argomento \param{pid} della funzione @@ -962,19 +926,20 @@ tracciare i processi bloccati. Il valore dell'opzione deve essere specificato come maschera binaria ottenuta con l'OR delle suddette costanti con zero. In genere si utilizza \const{WUNTRACED} all'interno del controllo di sessione, -(l'argomento è trattato in \secref{sec:sess_job_control}). In tal caso infatti -la funzione ritorna, restituendone il \acr{pid}, quando c'è un processo figlio -che è entrato in stato di sleep (vedi \tabref{tab:proc_proc_states}) e del -quale non si è ancora letto lo stato (con questa stessa opzione). In Linux -sono previste altre opzioni non standard relative al comportamento con i -thread, che riprenderemo in \secref{sec:thread_xxx}. +(l'argomento è trattato in sez.~\ref{sec:sess_job_control}). In tal caso +infatti la funzione ritorna, restituendone il \acr{pid}, quando c'è un +processo figlio che è entrato in stato di sleep (vedi +tab.~\ref{tab:proc_proc_states}) e del quale non si è ancora letto lo stato +(con questa stessa opzione). In Linux sono previste altre opzioni non standard +relative al comportamento con i thread, che riprenderemo in +sez.~\ref{sec:thread_xxx}. La terminazione di un processo figlio è chiaramente un evento asincrono rispetto all'esecuzione di un programma e può avvenire in un qualunque momento. Per questo motivo, come accennato nella sezione precedente, una delle azioni prese dal kernel alla conclusione di un processo è quella di mandare un segnale di \const{SIGCHLD} al padre. L'azione predefinita (si veda -\secref{sec:sig_base}) per questo segnale è di essere ignorato, ma la sua +sez.~\ref{sec:sig_base}) per questo segnale è di essere ignorato, ma la sua generazione costituisce il meccanismo di comunicazione asincrona con cui il kernel avverte il processo padre che uno dei suoi figli è terminato. @@ -984,7 +949,7 @@ per leggerne lo stato di chiusura (ed evitare la presenza di \textit{zombie}\index{zombie}), per questo la modalità più usata per chiamare queste funzioni è quella di utilizzarle all'interno di un \textit{signal handler} (vedremo un esempio di come gestire \const{SIGCHLD} con i segnali -in \secref{sec:sig_example}). In questo caso infatti, dato che il segnale è +in sez.~\ref{sec:sig_example}). In questo caso infatti, dato che il segnale è generato dalla terminazione di un figlio, avremo la certezza che la chiamata a \func{wait} non si bloccherà. @@ -997,26 +962,33 @@ generato dalla terminazione di un figlio, avremo la certezza che la chiamata a \hline \hline \macro{WIFEXITED(s)} & Condizione vera (valore non nullo) per un processo - figlio che sia terminato normalmente. \\ + figlio che sia terminato normalmente. \\ \macro{WEXITSTATUS(s)} & Restituisce gli otto bit meno significativi dello - stato di uscita del processo (passato attraverso \func{\_exit}, \func{exit} - o come valore di ritorno di \func{main}). Può essere valutata solo se - \val{WIFEXITED} ha restituito un valore non nullo.\\ + stato di uscita del processo (passato attraverso + \func{\_exit}, \func{exit} o come valore di + ritorno di \func{main}). Può essere valutata solo + se \val{WIFEXITED} ha restituito un valore non + nullo.\\ \macro{WIFSIGNALED(s)} & Vera se il processo figlio è terminato - in maniera anomala a causa di un segnale che non è stato catturato (vedi - \secref{sec:sig_notification}).\\ - \macro{WTERMSIG(s)} & restituisce il numero del segnale che ha causato - la terminazione anomala del processo. Può essere valutata solo se - \val{WIFSIGNALED} ha restituito un valore non nullo.\\ + in maniera anomala a causa di un segnale che non + è stato catturato (vedi + sez.~\ref{sec:sig_notification}).\\ + \macro{WTERMSIG(s)} & Restituisce il numero del segnale che ha causato + la terminazione anomala del processo. Può essere + valutata solo se \val{WIFSIGNALED} ha restituito + un valore non nullo.\\ \macro{WCOREDUMP(s)} & Vera se il processo terminato ha generato un - file si \textit{core dump}. Può essere valutata solo se - \val{WIFSIGNALED} ha restituito un valore non nullo.\footnotemark \\ + file di \itindex{core~dump}\textit{core + dump}. Può essere valutata solo se + \val{WIFSIGNALED} ha restituito un valore non + nullo.\footnotemark \\ \macro{WIFSTOPPED(s)} & Vera se il processo che ha causato il ritorno di - \func{waitpid} è bloccato. L'uso è possibile solo avendo specificato - l'opzione \const{WUNTRACED}. \\ - \macro{WSTOPSIG(s)} & restituisce il numero del segnale che ha bloccato - il processo, Può essere valutata solo se \val{WIFSTOPPED} ha - restituito un valore non nullo. \\ + \func{waitpid} è bloccato. L'uso è possibile solo + avendo specificato l'opzione \const{WUNTRACED}. \\ + \macro{WSTOPSIG(s)} & Restituisce il numero del segnale che ha bloccato + il processo. Può essere valutata solo se + \val{WIFSTOPPED} ha restituito un valore non + nullo. \\ \hline \end{tabular} \caption{Descrizione delle varie macro di preprocessore utilizzabili per @@ -1028,26 +1000,27 @@ generato dalla terminazione di un figlio, avremo la certezza che la chiamata a presente come estensione sia in Linux che in altri Unix.} Entrambe le funzioni di attesa restituiscono lo stato di terminazione del -processo tramite il puntatore \param{status} (se non interessa memorizzare lo -stato si può passare un puntatore nullo). Il valore restituito da entrambe le -funzioni dipende dall'implementazione, e tradizionalmente alcuni bit (in -genere 8) sono riservati per memorizzare lo stato di uscita, e altri per -indicare il segnale che ha causato la terminazione (in caso di conclusione -anomala), uno per indicare se è stato generato un core file, ecc.\footnote{le - definizioni esatte si possono trovare in \file{} ma - questo file non deve mai essere usato direttamente, esso viene incluso - attraverso \file{}.} +processo tramite il puntatore \param{status} (se non interessa memorizzare +lo stato si può passare un puntatore nullo). Il valore restituito da +entrambe le funzioni dipende dall'implementazione, e tradizionalmente alcuni +bit (in genere 8) sono riservati per memorizzare lo stato di uscita, e altri +per indicare il segnale che ha causato la terminazione (in caso di +conclusione anomala), uno per indicare se è stato generato un +\itindex{core~dump}\textit{core dump}, ecc.\footnote{le definizioni esatte + si possono trovare in \file{} ma questo file non deve + mai essere usato direttamente, esso viene incluso attraverso + \file{}.} Lo standard POSIX.1 definisce una serie di macro di preprocessore da usare per analizzare lo stato di uscita. Esse sono definite sempre in -\file{} ed elencate in \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}} @@ -1055,7 +1028,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 @@ -1064,10 +1037,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)} @@ -1077,8 +1050,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}} @@ -1089,13 +1062,13 @@ processi in Unix fatto attraverso una delle funzioni della famiglia \func{exec}. Quando un processo chiama una di queste funzioni esso viene completamente sostituito dal nuovo programma; il \acr{pid} del processo non cambia, dato che non viene -creato un nuovo processo, la funzione semplicemente rimpiazza lo stack, lo -heap, i dati ed il testo del processo corrente con un nuovo programma letto da -disco. +creato un nuovo processo, la funzione semplicemente rimpiazza lo +\itindex{stack} stack, lo \itindex{heap} heap, i dati ed il testo del processo +corrente con un nuovo programma letto da 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[])} @@ -1106,21 +1079,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, il processo viene tracciato, o il filesystem è montato con - l'opzione \cmd{nosuid}. + \item[\errcode{EPERM}] il file ha i bit \itindex{suid~bit} \acr{suid} o + \itindex{sgid~bit} \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}, @@ -1135,8 +1108,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} @@ -1148,7 +1121,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; @@ -1157,9 +1130,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). @@ -1171,9 +1144,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. @@ -1193,8 +1164,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$& \\ @@ -1208,10 +1179,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 @@ -1221,7 +1192,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. +\itindex{pathname}\textit{pathname} del programma. \begin{figure}[htb] \centering @@ -1231,34 +1202,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}). -\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}). + \textsl{group-ID supplementari} (vedi sez.~\ref{sec:proc_access_id}); +\item il \textit{session ID} (\acr{sid}) ed il \itindex{process~group} + \textit{process group ID} (\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 @@ -1266,11 +1238,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}\itindex{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} @@ -1278,31 +1250,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 +\func{opendir} (vedi sez.~\ref{sec:file_dir_read}) che effettua da sola +l'impostazione del flag di \textit{close-on-exec}\itindex{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 -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}). +di questi identificatori è trattato in sez.~\ref{sec:proc_access_id}), tranne +quando il file che si va ad eseguire abbia o il \itindex{suid~bit}\acr{suid} +bit o lo \itindex{sgid~bit} \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 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 programma valido (binario, non un altro script) che verrà +in genere questo è \file{/lib/ld-linux.so.1} per programmi collegati con le +\acr{libc5}, e \file{/lib/ld-linux.so.2} per programmi collegati con le +\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}. + filename}.\footnote{si tenga presente che con Linux quanto viene scritto + come \texttt{argomenti} viene passato all'interprete 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 restituisce 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 @@ -1317,7 +1301,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. @@ -1326,10 +1310,12 @@ 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 + flessibile e controllabile, come le \itindex{capabilities} + \textit{capabilities} illustrate in sez.~\ref{sec:proc_capabilities}, le ACL + per i file o il \textit{Mandatory Access Control} + \itindex{Mandatory~Access~Control~(MAC)} di SELinux; inoltre basandosi sul lavoro effettuato con SELinux, a partire dal kernel 2.5.x, è iniziato lo sviluppo di una infrastruttura di sicurezza, il \textit{Linux Security Modules}, o LSM, in grado di fornire diversi agganci a livello del kernel @@ -1339,15 +1325,11 @@ separazione fra l'amministratore (\textsl{root}, detto spesso anche \textit{superuser}) che non è sottoposto a restrizioni, ed il resto degli utenti, per i quali invece vengono effettuati i vari controlli di accesso. -%Benché il sistema sia piuttosto semplice (è basato su un solo livello di -% separazione) il sistema permette una -%notevole flessibilità, - Abbiamo già accennato come il sistema associ ad ogni utente e gruppo due 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. @@ -1355,7 +1337,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 @@ -1365,7 +1347,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 @@ -1409,7 +1392,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 @@ -1419,18 +1402,19 @@ 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 -\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 -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). +sez.~\ref{sec:proc_exec}, il programma che si è posto in esecuzione abbia i +bit \itindex{suid~bit} \acr{suid} o \itindex{sgid~bit} \acr{sgid} impostati +(il significato di questi bit è affrontato 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). Come nel caso del \acr{pid} e del \acr{ppid}, anche tutti questi identificatori possono essere letti attraverso le rispettive funzioni: @@ -1474,29 +1458,31 @@ 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 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. +dopo che questi sono stati impostati tenendo conto di eventuali +\itindex{suid~bit}\acr{suid} o \itindex{sgid~bit} \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} @@ -1526,10 +1512,11 @@ all'\textsl{user-ID salvato}. Negli altri casi viene segnalato un errore (con \errcode{EPERM}). 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 -necessita di privilegi aggiuntivi, ed eventualmente tornare indietro. +consentire ad un programma con i bit \itindex{suid~bit} \acr{suid} o +\itindex{sgid~bit} \acr{sgid} impostati (vedi 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 viene gestito l'accesso al file \file{/var/log/utmp}. In questo file viene @@ -1585,17 +1572,12 @@ 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}). - - -\subsection{Le funzioni \func{setreuid} e \func{setregid}} -\label{sec:proc_setreuid} +ricorrere ad altre funzioni. 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: +supportando\footnote{almeno fino alla versione 4.3+BSD.} 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} @@ -1613,12 +1595,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 @@ -1643,14 +1625,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 \func{seteuid} e \func{setegid}} -\label{sec:proc_seteuid} - -Le due funzioni \funcd{seteuid} e \funcd{setegid} 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} @@ -1673,13 +1651,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 \func{setresuid} e \func{setresgid}} -\label{sec:proc_setresuid} - -Le due funzioni \funcd{setresuid} e \funcd{setresgid} sono un'estensione -introdotta in Linux,\footnote{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: +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} @@ -1702,7 +1678,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 @@ -1726,20 +1702,19 @@ 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 +\itindex{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 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 \secref{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. +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 @@ -1776,17 +1751,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} @@ -1826,8 +1804,8 @@ ottenere tutti i gruppi a cui appartiene un certo utente; il suo prototipo La funzione legge i gruppi supplementari dell'utente specificato da \param{user}, eseguendo una scansione del database dei gruppi (si veda -\secref{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 +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. @@ -1856,7 +1834,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 è: @@ -1885,19 +1863,720 @@ compila con il flag \cmd{-ansi}, scrivere codice portabile. +\subsection{La gestione delle \textit{capabilities}} +\label{sec:proc_capabilities} + +\itindbeg{capabilities} + +Come accennato in sez.~\ref{sec:proc_access_id} l'architettura classica della +gestione dei privilegi in un sistema unix-like ha il sostanziale problema di +fornire all'amministratore dei poteri troppo ampi, questo comporta che anche +quando si siano predisposte delle misure di protezione per in essere in grado +di difendersi dagli effetti di una eventuale compromissione del +sistema,\footnote{come montare un filesystem in sola lettura per impedirne + modifiche, o marcare un file come immutabile.} una volta che questa sia +stata effettuata e si siano ottenuti i privilegi di amministratore, queste +potranno essere comunque rimosse.\footnote{nei casi elencati nella precedente + nota si potrà sempre rimontare il sistema in lettura-scrittura, o togliere + la marcatura di immutabilità.} + +Il problema consiste nel fatto che nell'architettura tradizionale di un +sistema unix-like i controlli di accesso sono basati su un solo livello di +separazione: per i processi normali essi sono posti in atto, mentre per i +processi con i privilegi di amministratore essi non vengono neppure eseguiti; +per questo motivo non era previsto alcun modo per evitare che un processo con +diritti di amministratore non potesse eseguire certe operazioni, o per cedere +definitivamente alcuni privilegi da un certo momento in poi. + +Per ovviare a tutto ciò, a partire dai kernel della serie 2.2, è stato +introdotto un meccanismo, detto \textit{capabilities}, che consentisse di +suddividere i vari privilegi tradizionalmente associati all'amministratore in +un insieme di \textsl{capacità} distinte. L'idea era che queste capacità +potessero essere abilitate e disabilitate in maniera indipendente per ciascun +processo con privilegi di amministratore, permettendo così una granularità +molto più fine nella distribuzione degli stessi che evitasse la originaria +situazione di \textsl{tutto o nulla}. + +Il meccanismo completo delle \textit{capabilities}\footnote{l'implementazione + di Linux si rifà ad una bozza per quello che dovrebbe divenire lo standard + POSIX.1e, che prevede questa funzionalità.} prevederebbe anche la +possibilità di associare le stesse \textit{capabilities} anche ai singoli file +eseguibili,\footnote{una descrizione sommaria di questa funzionalità è + riportata nella pagina di manuale che descrive l'implementazione delle + \textit{capabilities} con Linux (accessibile con \texttt{man capabilities}), + ma non essendo implementata non ne tratteremo qui.} in modo da poter +stabilire quali capacità possono essere utilizzate quando viene messo in +esecuzione uno specifico programma; attualmente però questa funzionalità non è +implementata.\footnote{per attualmente si intende fino al kernel 2.6.13, e + finora non è disponibile al momento neanche presente nessuna realizzazione + sperimentale delle specifiche POSIX.1e, anche se esistono dei patch di + sicurezza del kernel, come LIDS (vedi + \href{http://www.lids.org}{\texttt{http://www.lids.org/})} che realizzano + qualcosa di simile.} + + +\begin{table}[!h!bt] + \centering + \footnotesize + \begin{tabular}{|l|p{12cm}|} + \hline + \textbf{Capacità}&\textbf{Descrizione}\\ + \hline + \hline +% +% POSIX-draft defined capabilities. +% + \const{CAP\_CHOWN} & la capacità di cambiare proprietario e gruppo + proprietario di un file (vedi + sez.~\ref{sec:file_chown}).\\ + \const{CAP\_DAC\_OVERRIDE}& la capacità di evitare il controllo dei + permessi di lettura, scrittura ed esecuzione dei + file, (vedi sez.~\ref{sec:file_access_control}) + caratteristici del modello classico del + controllo di accesso chiamato + \itindex{Discrectionary~Access~Control~(DAC)} + \textit{Discrectionary Access Control} (da cui + il nome DAC).\\ + \const{CAP\_DAC\_READ\_SEARCH}& la capacità di evitare il controllo dei + permessi di lettura, scrittura ed esecuzione per + le directory (vedi + sez.~\ref{sec:file_access_control}).\\ + \const{CAP\_FOWNER} & la capacità di evitare il controllo che + l'user-ID effettivo del processo (o meglio il + \textit{filesystem user-ID}, vedi + sez.~\ref{sec:proc_setuid}) coincida con + quello del proprietario di un file per tutte + le operazioni privilegiate non coperte dalle + precedenti \const{CAP\_DAC\_OVERRIDE} e + \const{CAP\_DAC\_READ\_SEARCH}. Queste + comprendono i cambiamenti dei permessi e dei + tempi del file (vedi sez.~\ref{sec:file_chmod} e + sez.~\ref{sec:file_utime}), le impostazioni degli + attributi estesi (con il comando \cmd{chattr}) e + delle ACL, poter ignorare lo + \itindex{sticky~bit} \textit{sticky bit} nella + cancellazione dei file (vedi + sez.~\ref{sec:file_sticky}), la possibilità di + impostare il flag di \const{O\_NOATIME} con + \func{open} e \func{fcntl} (vedi + sez.~\ref{sec:file_open} e + sez.~\ref{sec:file_fcntl}).\\ + \const{CAP\_FSETID} & la capacità di evitare la cancellazione + automatica dei bit \itindex{suid~bit} \acr{suid} + e \itindex{sgid~bit} \acr{sgid} quando un file + per i quali sono impostati viene modificato da + un processo senza questa capacità e la capacità + di impostare il bit \acr{sgid} su un file anche + quando questo è relativo ad un gruppo cui non si + appartiene (vedi sez.~\ref{sec:file_chmod}).\\ + \const{CAP\_KILL} & la capacità di mandare segnali a qualunque + processo (vedi sez.~\ref{sec:sig_kill_raise}).\\ + \const{CAP\_SETGID} & la capacità di manipolare i group ID dei + processi, sia il principale che i supplementari, + (vedi sez.~\ref{sec:proc_setgroups} che quelli + trasmessi tramite i \index{socket} socket + \textit{unix domain} (vedi + sez.~\ref{sec:unix_socket}).\\ + \const{CAP\_SETUID} & la capacità di manipolare gli user ID del + processo (con \func{setuid}, \func{setreuid}, + \func{setresuid}, \func{setfsuid}) e di + trasmettere un valore arbitrario + dell'\textsl{uid} nel passaggio delle + credenziali coi socket unix domain (vedi + sez.~\ref{sec:unix_socket}).\\ +% +% Linux specific capabilities +% +\hline + \const{CAP\_SETPCAP} & la capacità di impostare o rimuovere una capacità + (limitatamente a quelle che il processo + chiamante ha nel suo insieme di capacità + permesse) da qualunque processo.\\ + \const{CAP\_LINUX\_IMMUTABLE}& la capacità di impostare gli attributi + \textit{immutable} e \itindex{append~mode} + \textit{append only} per i file su un + filesystem che supporta questi + attributi estesi.\\ + \const{CAP\_NET\_BIND\_SERVICE}& la capacità di porre in ascolto server + su porte riservate (vedi + sez.~\ref{sec:TCP_func_bind}).\\ + \const{CAP\_NET\_BROADCAST}& la capacità di consentire l'uso di socket in + \itindex{broadcast} \textit{broadcast} e + \itindex{multicast} \textit{multicast}.\\ + \const{CAP\_NET\_ADMIN} & la capacità di eseguire alcune operazioni + privilegiate sulla rete (impostare le opzioni + privilegiate dei socket, abilitare il + \itindex{multicast} \textit{multicasting}, + impostare interfacce di rete e + tabella di instradamento).\\ + \const{CAP\_NET\_RAW} & la capacità di usare socket \texttt{RAW} e + \texttt{PACKET} (quelli che permettono di creare + pacchetti nei protocolli di basso livello).\\ + \const{CAP\_IPC\_LOCK} & la capacità di effettuare il \textit{memory + locking} \itindex{memory~locking} con le + funzioni \func{mlock}, \func{mlockall}, + \func{shmctl}, \func{mmap} (vedi + sez.~\ref{sec:proc_mem_lock} e + sez.~\ref{sec:file_memory_map}). \\ + \const{CAP\_IPC\_OWNER} & la capacità di evitare il controllo dei permessi + per le operazioni sugli oggetti di + intercomunicazione fra processi (vedi + sez.~\ref{sec:ipc_sysv}).\\ + \const{CAP\_SYS\_MODULE}& la capacità di caricare e rimuovere moduli del + kernel. \\ + \const{CAP\_SYS\_RAWIO} & la capacità di eseguire operazioni sulle porte + di I/O con \func{ioperm} e \func{iopl} (vedi + sez.~\ref{sec:file_io_port}).\\ + \const{CAP\_SYS\_CHROOT}& la capacità di eseguire la funzione + \func{chroot} (vedi + sez.~\ref{sec:file_chroot}).\\ + \const{CAP\_SYS\_PTRACE}& consente di tracciare qualunque processo con + \func{ptrace} (vedi + sez.~\ref{sec:xxx_ptrace}).\\ +% TODO documentatare ptrace + \const{CAP\_SYS\_PACCT} & la capacità di usare le funzioni di + \textit{accounting} dei processi (vedi + sez.~\ref{sec:sys_bsd_accounting}).\\ + \const{CAP\_SYS\_ADMIN} & la capacità di eseguire una serie di compiti + amministrativi (come impostare le quote, + attivare e disattivare la swap, montare, + rimontare e smontare filesystem, ecc.). \\ + \const{CAP\_SYS\_BOOT} & la capacità di fare eseguire un riavvio del + sistema.\\ + \const{CAP\_SYS\_NICE} & la capacità di modificare le priorità dei + processi (vedi sez.~\ref{sec:proc_priority}). \\ + \const{CAP\_SYS\_RESOURCE}& la capacità di superare le limitazioni sulle + risorse, aumentare le quote disco, usare lo + spazio disco riservato all'amministratore.\\ + \const{CAP\_SYS\_TIME} & la capacità di modificare il tempo di sistema + (vedi sez.~\ref{sec:sys_time}).\\ + \const{CAP\_SYS\_TTY\_CONFIG}& la capacità di simulare un \textit{hangup} + della console, con la funzione + \func{vhangup}.\\ + \const{CAP\_MKNOD} & la capacità di creare file di dispositivo con la + funzione \func{mknod} (vedi + sez.~\ref{sec:file_mknod}).\footnotemark\\ + \const{CAP\_LEASE} & la capacità di creare dei \textit{file lease} + \index{file!lease} su di un file (vedi + sez.~\ref{sec:file_asyncronous_lease}) + indipendentemente dalla proprietà dello + stesso.\footnotemark\\ + \const{CAP\_SETFCAP} & la capacità di impostare le + \textit{capabilities} di un file (non + supportata).\\ + \hline + \end{tabular} + \caption{Le costanti che identificano le \textit{capabilities} presenti nel + kernel.} +\label{tab:proc_capabilities} +\end{table} + +\footnotetext[21]{questa capacità è presente soltanto a partire dai kernel + della serie 2.4.x.} + +\footnotetext{questa capacità è presente soltanto a partire dai kernel della + serie 2.4.x.} + +Per gestire questo nuovo meccanismo ciascun processo porta con sé tre distinti +insiemi di \textit{capabilities}, che vengono denominati rispettivamente +\textit{effective}, \textit{permitted} ed \textit{inherited}. Questi insiemi +vengono mantenuti in forma di tre diverse maschere binarie,\footnote{il kernel + li mantiene, come i vari identificatori di sez.~\ref{sec:proc_setuid}, + all'interno della \struct{task\_struct} di ciascun processo (vedi + fig.~\ref{fig:proc_task_struct}), nei tre campi \texttt{cap\_effective}, + \texttt{cap\_inheritable}, \texttt{cap\_permitted} del tipo + \texttt{kernel\_cap\_t}; questo è attualmente definito come intero a 32 bit, + il che comporta un massimo di 32 \textit{capabilities} distinte.} in cui +ciascun bit corrisponde ad una capacità diversa; se ne è riportato +l'elenco,\footnote{si tenga presente che l'elenco delle \textit{capabilities} + presentato questa tabella, ripreso dalla relativa pagina di manuale + (accessibile con \texttt{man capabilities}) e dalle definizioni in + \texttt{sys/capabilities.h}, è quello aggiornato al kernel 2.6.6.} con una +breve descrizione, ed il nome delle costanti che identificano i singoli bit, +in tab.~\ref{tab:proc_capabilities}; la tabella è divisa in due parti, la +prima riporta le \textit{capabilities} previste nella bozza dello standard +POSIX1.e, la seconda quelle specifiche di Linux. + +L'utilizzo di tre distinti insiemi serve a fornire una interfaccia flessibile +per l'uso delle \textit{capabilities}, con scopi analoghi a quelli per cui +sono mantenuti i diversi insiemi di identificatori di +sez.~\ref{sec:proc_setuid}; il loro significato è il seguente: +\begin{basedescript}{\desclabelwidth{2.0cm}\desclabelstyle{\nextlinelabel}} +\item[\textit{effective}] l'insieme delle \textit{capabilities} + ``\textsl{effettive}'', cioè di quelle che vengono effettivamente usate dal + kernel quando deve eseguire il controllo di accesso per le varie operazioni + compiute dal processo. +\item[\textit{permitted}] l'insieme delle \textit{capabilities} + ``\textsl{permesse}'', cioè l'insieme di quelle capacità che un processo + \textsl{può} impostare come \textsl{effettive}. Se un processo cancella una + capacità da questo insieme non potrà più riassumerla (almeno che non esegua + un programma che è \acr{suid} di root). +\item[\textit{inherited}] l'insieme delle \textit{capabilities} + ``\textsl{ereditabili}'', cioè quelle che vengono trasmesse ad un nuovo + programma eseguito attraverso una chiamata ad \func{exec} (con l'eccezione + del caso che questo sia \acr{suid} di root). +\label{sec:capabilities_set} +\end{basedescript} + +Oltre a questi tre insiemi, che sono relativi al singolo processo, il kernel +mantiene un insieme generale valido per tutto il sistema, chiamato +\itindex{capabilities~bounding~set} \textit{capabilities bounding set}. Ogni +volta che un programma viene posto in esecuzione con \func{exec} il contenuto +degli insiemi \textit{effective} e \textit{permitted} vengono mascherati con +un \textsl{AND} binario del contenuto corrente del \textit{capabilities + bounding set}, così che il nuovo processo potrà disporre soltanto delle +capacità in esso elencate. + +Il \textit{capabilities bounding set} è un parametro di sistema, accessibile +attraverso il contenuto del file \file{/proc/sys/kernel/cap-bound}, che per +questa sua caratteristica consente di impostare un limite generale alle +capacità che possono essere accordate ai vari processi. Questo valore può +essere impostato ad un valore arbitrario esclusivamente dal primo processo +eseguito nel sistema (di norma cioè da \texttt{/sbin/init}), ogni processo +eseguito successivamente (cioè con \textsl{pid} diverso da 1) anche se +eseguito con privilegi di amministratore potrà soltanto rimuovere uno dei bit +già presenti dell'insieme: questo significa che una volta rimossa una +\textit{capability} dal \textit{capabilities bounding set} essa non sarà più +disponibile, neanche per l'amministratore, a meno di un riavvio. + +Quando un programma viene messo in esecuzione\footnote{cioè quando viene + eseguita la \func{execve} con cui lo si lancia; in corrispondenza di una + \func{fork} le \textit{capabilities} non vengono modificate.} esso eredita +(nel senso che assume negli insiemi \textit{effective} e \textit{permitted}) +le \textit{capabilities} mantenute nell'insieme \textit{inherited}, a meno che +non sia eseguito un programma \acr{suid} di root o la \func{exec} sia stata +eseguita da un programma con \textsl{uid} reale zero; in tal caso il programma +ottiene tutte le \textit{capabilities} presenti nel \textit{capabilities + bounding set}. In questo modo si può far si che ad un processo eseguito in +un secondo tempo possano essere trasmesse solo un insieme limitato di +capacità, impedendogli di recuperare quelle assenti nell'insieme +\textit{inherited}. Si tenga presente invece che attraverso una \func{fork} +vengono mantenute le stesse capacità del processo padre. + +Per la gestione delle \textit{capabilities} il kernel mette a disposizione due +funzioni che permettono rispettivamente di leggere ed impostare i valori dei +tre insiemi illustrati in precedenza. Queste due funzioni sono \funcd{capget} +e \funcd{capset} e costituiscono l'interfaccia di gestione basso livello; i +loro rispettivi prototipi sono: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{int capget(cap\_user\_header\_t hdrp, cap\_user\_data\_t datap)} + Legge le \textit{capabilities}. + + \funcdecl{int capset(cap\_user\_header\_t hdrp, const cap\_user\_data\_t + datap)} + Imposta le \textit{capabilities}. + + + \bodydesc{Entrambe le funzioni ritornano 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}] si è fatto riferimento ad un processo inesistente. + \item[\errcode{EPERM}] si è tentato di aggiungere una capacità + nell'insieme delle \textit{capabilities} permesse, o di impostare una + capacità non presente nell'insieme di quelle permesse negli insieme + delle effettive o ereditate, o si è cercato di impostare una + \textit{capability} di un altro processo senza avare + \const{CAP\_SETPCAP}. + \end{errlist} + ed inoltre \errval{EFAULT} ed \errval{EINVAL}. +} + +\end{functions} + +Queste due funzioni prendono come argomenti due tipi di dati dedicati, +definiti come puntatori a due strutture specifiche di Linux, illustrate in +fig.~\ref{fig:cap_kernel_struct}. Per poterle utilizzare occorre anche +cancellare la macro \macro{\_POSIX\_SOURCE}.\footnote{per farlo occorre + utilizzare la direttiva di preprocessore \direct{undef}; si dovrà cioè + inserire una istruzione \texttt{\#undef \_POSIX\_SOURCE} prima di includere + \texttt{sys/capability.h}.} Si tenga presente che le strutture di +fig.~\ref{fig:cap_kernel_struct}, come i prototipi delle due funzioni +\func{capget} e \func{capset}, sono soggette ad essere modificate con il +cambiamento del kernel (in particolare i tipi di dati delle strutture) ed +anche se finora l'interfaccia è risultata stabile, non c'è nessuna +assicurazione che questa venga mantenuta. Pertanto se si vogliono scrivere +programmi portabili che possano essere eseguiti su qualunque versione del +kernel è opportuno utilizzare le interfacce di alto livello. + +\begin{figure}[!htb] + \footnotesize + \centering + \begin{minipage}[c]{15cm} + \includestruct{listati/cap_user_header_t.h} + \end{minipage} + \normalsize + \caption{Definizione delle strutture a cui fanno riferimento i puntatori + \structd{cap\_user\_header\_t} e \structd{cap\_user\_data\_t} usati per + l'interfaccia di gestione di basso livello delle \textit{capabilities}.} + \label{fig:cap_kernel_struct} +\end{figure} + +La struttura a cui deve puntare l'argomento \param{hdrp} serve ad indicare, +tramite il campo \var{pid}, il processo del quale si vogliono leggere o +modificare le \textit{capabilities}. Il campo \var{version} deve essere +impostato al valore della versione delle usata dal kernel (quello indicato +dalla costante \const{\_LINUX\_CAPABILITY\_VERSION} di +fig.~\ref{fig:cap_kernel_struct}) altrimenti le funzioni ritorneranno con un +errore di \errcode{EINVAL}, restituendo nel campo stesso il valore corretto +della versione in uso. La struttura a cui deve puntare l'argomento +\param{datap} invece conterrà i valori letti o da impostare per i tre insiemi +delle capacità del processo. + +Dato che le precedenti funzioni, oltre ad essere specifiche di Linux, non +garantiscono la stabilità nell'interfaccia, è sempre opportuno effettuare la +gestione delle \textit{capabilities} utilizzando le funzioni di libreria a +questo dedicate. Queste funzioni, che seguono quanto previsto nelle bozze +dello standard POSIX.1e, non fanno parte delle \acr{glibc} e sono fornite in +una libreria a parte,\footnote{la libreria è \texttt{libcap2}, nel caso di + Debian può essere installata con il pacchetto omonimo.} pertanto se un +programma le utilizza si dovrà indicare esplicitamente l'uso della suddetta +libreria attraverso l'opzione \texttt{-lcap} del compilatore. + +Le funzioni dell'interfaccia delle bozze di POSIX.1e prevedono l'uso di uno +tipo di dato opaco, \type{cap\_t}, come puntatore ai dati mantenuti nel +cosiddetto \textit{capability state},\footnote{si tratta in sostanza di un + puntatore ad una struttura interna utilizzata dalle librerie, i cui campi + non devono mai essere acceduti direttamente.} in sono memorizzati tutti i +dati delle \textit{capabilities}. In questo modo è possibile mascherare i +dettagli della gestione di basso livello, che potranno essere modificati senza +dover cambiare le funzioni dell'interfaccia, che faranno riferimento soltanto +ad oggetti di questo tipo. L'interfaccia pertanto non soltanto fornisce le +funzioni per modificare e leggere le \textit{capabilities}, ma anche quelle +per gestire i dati attraverso \type{cap\_t}. + +La prima funzione dell'interfaccia è quella che permette di inizializzare un +\textit{capability state}, allocando al contempo la memoria necessaria per i +relativi dati. La funzione è \funcd{cap\_init} ed il suo prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{cap\_t cap\_init(void)} + Crea ed inizializza un \textit{capability state}. + + \bodydesc{La funzione ritorna un valore non nullo in caso di successo e + \macro{NULL} in caso di errore, nel qual caso \var{errno} assumerà il + valore \errval{ENOMEM}. + } +\end{functions} + +La funzione restituisce il puntatore \type{cap\_t} ad uno stato inizializzato +con tutte le \textit{capabilities} azzerate. In caso di errore (cioè quando +non c'è memoria sufficiente ad allocare i dati) viene restituito \macro{NULL} +ed \var{errno} viene impostata a \errval{ENOMEM}. La memoria necessaria a +mantenere i dati viene automaticamente allocata da \func{cap\_init}, ma dovrà +essere disallocata esplicitamente quando non più necessaria utilizzando la +funzione \funcd{cap\_free}, il cui prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{int cap\_free(void *obj\_d)} + Disalloca la memoria allocata per i dati delle \textit{capabilities}. + + \bodydesc{La funzione ritorna 0 in caso di successo e $-1$ in caso di + errore, nel qual caso \var{errno} assumerà il valore \errval{EINVAL}. + } +\end{functions} + +La funzione permette di liberare la memoria allocata dalle altre funzioni +della libreria sia per un \textit{capability state}, nel qual caso l'argomento +dovrà essere un dato di tipo \type{cap\_t}, che per una descrizione testuale +dello stesso,\footnote{cioè quanto ottenuto tramite la funzione + \func{cap\_to\_text}.} nel qual caso l'argomento dovrà essere di tipo +\texttt{char *}. L'argomento \param{obj\_d} deve corrispondere ad un oggetto +ottenuto tramite altre funzioni della libreria, altrimenti la funzione fallirà +con un errore di \errval{EINVAL}. + +Infine si può creare una copia di un \textit{capability state} ottenuto in +precedenza tramite la funzione \funcd{cap\_dup}, il cui prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{cap\_t cap\_dup(cap\_t cap\_p)} + Duplica un \textit{capability state} restituendone una copia. + + \bodydesc{La funzione ritorna un valore non nullo in caso di successo e + \macro{NULL} in caso di errore, nel qual caso \var{errno} potrà assumere i + valori \errval{ENOMEM} o \errval{EINVAL}. + } +\end{functions} + +La funzione crea una copia del \textit{capability state} posto all'indirizzo +\param{cap\_p} che si è passato come argomento, restituendo il puntatore alla +copia, che conterrà gli stessi valori delle \textit{capabilities} presenti +nell'originale. La memoria necessaria viene allocata automaticamente dalla +funzione. Una volta effettuata la copia i due \textit{capability state} +potranno essere modificati in maniera completamente indipendente. + +Una seconda classe di funzioni di servizio sono quelle per la gestione dei +dati contenuti all'interno di un \textit{capability state}; la prima di esse è +\funcd{cap\_clear}, il cui prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{int cap\_clear(cap\_t cap\_p)} + Inizializza un \textit{capability state} cancellando tutte le + \textit{capabilities}. + + \bodydesc{La funzione ritorna 0 in caso di successo e $-1$ in caso di + errore, nel qual caso \var{errno} assumerà il valore \errval{EINVAL}. + } +\end{functions} + +La funzione si limita ad azzerare tutte le \textit{capabilities} presenti nel +\textit{capability state} all'indirizzo \param{cap\_p} passato come argomento, +restituendo uno stato \textsl{vuoto}, analogo a quello che si ottiene nella +creazione con \func{cap\_init}. + +Per la gestione dei valori delle \textit{capabilities} presenti in un +\textit{capability state} l'interfaccia prevede due funzioni, +\funcd{cap\_get\_flag} e \funcd{cap\_set\_flag}, che permettono +rispettivamente di leggere o impostare il valore di un flag delle +\textit{capabilities}; i rispettivi prototipi sono: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{int cap\_get\_flag(cap\_t cap\_p, cap\_value\_t cap, cap\_flag\_t + flag, cap\_flag\_value\_t *value\_p)} + Legge il valore di una \textit{capability}. + + \funcdecl{int cap\_set\_flag(cap\_t cap\_p, cap\_flag\_t flag, int ncap, + cap\_value\_t *caps, cap\_flag\_value\_t value)} + Imposta il valore di una \textit{capability}. + + \bodydesc{Le funzioni ritornano 0 in caso di successo e $-1$ in caso di + errore, nel qual caso \var{errno} assumerà il valore \errval{EINVAL}. +} +\end{functions} + +In entrambe le funzioni l'argomento \param{cap\_p} indica il puntatore al +\textit{capability state} su cui operare, mentre l'argomento \param{flag} +indica su quale dei tre insiemi illustrati a +pag.~\pageref{sec:capabilities_set} si intende operare. Questi devono essere +specificati con una variabile di tipo \type{cap\_flag\_t} che può assumere +esclusivamente\footnote{si tratta in effetti di un tipo enumerato, come si può + verificare dalla sua definizione che si trova in + \texttt{/usr/include/sys/capability.h}.} uno dei valori illustrati in +tab.~\ref{tab:cap_set_identifier}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{CAP\_EFFECTIVE} & Capacità dell'insieme \textsl{effettivo}.\\ + \const{CAP\_PERMITTED} & Capacità dell'insieme \textsl{permesso}.\\ + \const{CAP\_INHERITABLE}& Capacità dell'insieme \textsl{ereditabile}.\\ + \hline + \end{tabular} + \caption{Valori possibili per il tipo di dato \type{cap\_flag\_t} che + identifica gli insiemi delle \textit{capabilities}.} + \label{tab:cap_set_identifier} +\end{table} + +La capacità che si intende controllare o impostare invece deve essere +specificata attraverso una variabile di tipo \type{cap\_value\_t}, che può +prendere come valore uno qualunque di quelli riportati in +tab.~\ref{tab:proc_capabilities}, in questo caso però non è possibile +combinare diversi valori in una maschera binaria, una variabile di tipo +\type{cap\_value\_t} deve indicare una sola capacità.\footnote{nel file di + header citato nella nota precedente il tipo \type{cap\_value\_t} è definito + come \ctyp{int}, ma i valori validi sono soltanto quelli di + tab.~\ref{tab:proc_capabilities}.} Infine lo stato di una capacità è +descritto ad una variabile di tipo \type{cap\_flag\_value\_t}, che a sua volta +può assumere soltanto uno\footnote{anche questo è un tipo enumerato.} dei +valori di tab.~\ref{tab:cap_value_type}. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|l|l|} + \hline + \textbf{Valore} & \textbf{Significato} \\ + \hline + \hline + \const{CAP\_CLEAR}& La capacità non è impostata.\\ + \const{CAP\_SET} & La capacità è impostata.\\ + \hline + \end{tabular} + \caption{Valori possibili per il tipo di dato \type{cap\_flag\_value\_t} che + indica lo stato di una capacità.} + \label{tab:cap_value_type} +\end{table} + +La funzione \func{cap\_get\_flag} legge lo stato della capacità indicata +dall'argomento \param{cap} all'interno dell'insieme indicato dall'argomento +\param{flag} e ne restituisce il valore nella variabile posta all'indirizzo +puntato dall'argomento \param{value\_p}; è possibile cioè leggere soltanto uno +stato di una capacità alla volta. + +La funzione \func{cap\_set\_flag} può invece impostare in una sola chiamata +più capacità, anche se solo all'interno dello stesso insieme; per questo essa +prende un vettore di valori di tipo \type{cap\_value\_t} nell'argomento +\param{caps}, la cui dimensione è specificata dall'argomento \param{ncap}. Il +tipo di impostazione da eseguire (cancellazione o impostazione) viene indicato +dall'argomento \param{value}. + +Per la visualizzazione dello stato delle \textit{capabilities} l'interfaccia +prevede una funzione apposita, \funcd{cap\_to\_text}, il cui prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{char * cap\_to\_text(cap\_t caps, ssize\_t * length\_p)} + + Genera una visualizzazione testuale delle \textit{capabilities}. + + \bodydesc{La funzione ritorna un puntatore alla stringa con la descrizione + delle \textit{capabilities} in caso di successo e \val{NULL} in caso di + errore, nel qual caso \var{errno} può assumere i valori \errval{EINVAL} o + \errval{ENOMEM}. + } +\end{functions} + +La funzione ritorna l'indirizzo di una stringa contente la descrizione +testuale del contenuto del \textit{capabilities state} \param{caps} passato +come argomento, e, qualora l'argomento \param{length\_p} sia diverso da +\val{NULL}, restituisce nella variabile intera da questo puntata la lunghezza +della stringa. La stringa restituita viene allocata automaticamente dalla +funzione e deve essere liberata con \func{cap\_free}. + +Fin quei abbiamo trattato delle funzioni di manipolazione dei +\textit{capabilities state}; quando si vuole eseguire la lettura delle +\textit{capabilities} del processo corrente si deve usare la funzione +\funcd{cap\_get\_proc}, il cui prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{cap\_t cap\_get\_proc(void)} + Legge le \textit{capabilities} del processo corrente. + + \bodydesc{La funzione ritorna un valore diverso da \val{NULL} in caso di + successo e \val{NULL} in caso di errore, nel qual caso \var{errno} può + assumere i valori \errval{EINVAL}, \errval{EPERM} o \errval{ENOMEM}. } +\end{functions} + +La funzione legge il valore delle \textit{capabilities} del processo corrente +e restituisce il puntatore ad un \textit{capabilities state} contenente il +risultato, che provvede ad allocare autonomamente, e che occorrerà liberare +con \func{cap\_free} quando non sarà più utilizzato. + +Se invece si vogliono leggere le \textit{capabilities} di un processo +specifico occorre usare la funzione \funcd{capgetp}, il cui +prototipo\footnote{su alcune pagine di manuale la funzione è descritta con un + prototipo sbagliato, che prevede un valore di ritorno di tipo \type{cap\_t}, + ma il valore di ritorno è intero, come si può verificare anche dalla + dichiarazione della stessa in \texttt{sys/capability.h}.} è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{int capgetp(pid\_t pid, cap\_t cap\_d)} + Legge le \textit{capabilities} del processo indicato da \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 \errval{EINVAL}, + \errval{EPERM} o \errval{ENOMEM}. + } +\end{functions} + +La funzione legge il valore delle \textit{capabilities} del processo indicato +con l'argomento \param{pid}, salvando il risultato nel \textit{capabilities + state} all'indirizzo \param{cap\_d} che deve essere stato creato in +precedenza. Qualora il processo non esista si avrà un errore di +\errval{ESRCH}. Gli stessi valori possono essere letti direttamente nel +filesystem \textit{proc}, nei file \texttt{/proc//status}; ad esempio per +\texttt{init} si otterrà qualcosa del tipo: +\begin{Verbatim} +... +CapInh: 0000000000000000 +CapPrm: 00000000fffffeff +CapEff: 00000000fffffeff +\end{Verbatim} + +Infine per impostare le \textit{capabilities} del processo corrente (non +esiste una funzione che permetta di cambiare le \textit{capabilities} di un +altro processo) si deve usare la funzione \funcd{cap\_set\_proc}, il cui +prototipo è: +\begin{functions} + \headdecl{sys/capability.h} + + \funcdecl{int cap\_set\_proc(cap\_t cap\_p)} + Imposta le \textit{capabilities} del processo corrente. + + \bodydesc{La funzione ritorna 0 in caso di successo e $-1$ in caso di + errore, nel qual caso \var{errno} può assumere i valori \errval{EINVAL}, + \errval{EPERM} o \errval{ENOMEM}. + } +\end{functions} + +La funzione modifica le \textit{capabilities} del processo corrente secondo +quanto specificato con l'argomento \param{cap\_p}, posto che questo sia +possibile nei termini spiegati in precedenza (non sarà ad esempio possibile +impostare capacità non presenti nell'insieme di quelle permesse). In caso di +successo i nuovi valori saranno effettivi al ritorno della funzione, in caso +di fallimento invece lo stato delle capacità resterà invariato. Si tenga +presente che \textsl{tutte} le capacità specificate tramite \param{cap\_p} +devono essere permesse; se anche una sola non lo è la funzione fallirà, e per +quanto appena detto, lo stato delle \textit{capabilities} non verrà modificato +(neanche per le parti eventualmente permesse). + +Come esempio di utilizzo di queste funzioni nei sorgenti allegati alla guida +si è distribuito il programma \texttt{getcap.c}, che consente di leggere le +\textit{capabilities} del processo corrente\footnote{vale a dire di sé stesso, + quando lo si lancia, il che può sembrare inutile, ma serve a mostrarci quali + sono le \textit{capabilities} standard che ottiene un processo lanciato + dalla riga di comando.} o tramite l'opzione \texttt{-p}, quelle di un +processo qualunque il cui pid viene passato come parametro dell'opzione. + +\begin{figure}[htb] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \includecodesample{listati/getcap.c} + \end{minipage} + \normalsize + \caption{Corpo principale del programma \texttt{getcap.c}.} + \label{fig:proc_getcap} +\end{figure} + +La sezione principale del programma è riportata in fig.~\ref{fig:proc_getcap}, +e si basa su una condizione sulla variabile \var{pid} che se si è usato +l'opzione \texttt{-p} è impostata (nella sezione di gestione delle opzioni, +che si è tralasciata) al valore del \textsl{pid} del processo di cui si vuole +leggere le \textit{capabilities} e nulla altrimenti. Nel primo caso +(\texttt{\small 1--6}) si utilizza direttamente (\texttt{\small 2}) +\func{cap\_get\_proc} per ottenere lo stato delle capacità del processo, nel +secondo (\texttt{\small 7--14}) prima si inizializza (\texttt{\small 8}) uno +stato vuoto e poi (\texttt{\small 9}) si legge il valore delle capacità del +processo indicato. + +Il passo successivo è utilizzare (\texttt{\small 16}) \func{cap\_to\_text} per +tradurre in una stringa lo stato, e poi (\texttt{\small 17}) stamparlo; infine +(\texttt{\small 19--20}) si libera la memoria allocata dalle precedenti +funzioni con \func{cap\_free} per poi ritornare dal ciclo principale della +funzione. + +\itindend{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 +lo \textit{scheduler}\itindex{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}} \label{sec:proc_sched} +\itindbeg{scheduler} La scelta di un meccanismo che sia in grado di distribuire in maniera efficace il tempo di CPU per l'esecuzione dei processi è sempre una questione delicata, ed oggetto di numerose ricerche; in generale essa dipende in maniera @@ -1905,13 +2584,14 @@ essenziale anche dal tipo di utilizzo che deve essere fatto del sistema, per cui non esiste un meccanismo che sia valido per tutti gli usi. La caratteristica specifica di un sistema multitasking come Linux è quella del -cosiddetto \textit{prehemptive multitasking}: questo significa che al -contrario di altri sistemi (che usano invece il cosiddetto \textit{cooperative +cosiddetto \itindex{prehemptive~multitasking}\textit{prehemptive + multitasking}: questo significa che al contrario di altri sistemi (che usano +invece il cosiddetto \itindex{cooperative~multitasking}\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}, 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 @@ -1934,7 +2614,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. @@ -1978,12 +2658,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 @@ -2004,7 +2684,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 @@ -2031,28 +2711,31 @@ 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è 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. - -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 - 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 -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 -verranno messi in esecuzione. +Il meccanismo usato da Linux è piuttosto semplice,\footnote{in realtà nella + serie 2.6.x lo 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 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 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 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 @@ -2101,15 +2784,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 @@ -2119,9 +2803,10 @@ quest'ultimo indica il processo, il gruppo di processi o l'utente correnti. \param{which} & \param{who} & \textbf{Significato} \\ \hline \hline - \const{PRIO\_PROCESS} & \type{pid\_t} & processo \\ - \const{PRIO\_PRGR} & \type{pid\_t} & process group \\ - \const{PRIO\_USER} & \type{uid\_t} & utente \\ + \const{PRIO\_PROCESS} & \type{pid\_t} & processo \\ + \const{PRIO\_PRGR} & \type{pid\_t} & \itindex{process~group} + \textit{process group} \\ + \const{PRIO\_USER} & \type{uid\_t} & utente \\ \hline \end{tabular} \caption{Legenda del valore dell'argomento \param{which} e del tipo @@ -2169,48 +2854,56 @@ 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 +\textit{page fault}\itindex{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 -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{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 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 è +scelta; 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 @@ -2227,23 +2920,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 @@ -2254,36 +2946,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} @@ -2297,7 +2992,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} @@ -2318,25 +3013,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 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: @@ -2346,7 +3022,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}. @@ -2354,7 +3029,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} @@ -2363,7 +3041,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 @@ -2373,7 +3075,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. @@ -2383,7 +3085,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 @@ -2405,6 +3110,182 @@ 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{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. + +\itindbeg{CPU~affinity} +Per ovviare a questo tipo di problemi è nato il concetto di \textsl{affinità + di processore} (o \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 +\index{effetto~ping-pong} 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 corrispondente 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 standardizzazione 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 +particolari. + +È 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. +\itindend{scheduler} +\itindend{CPU~affinity} + + \section{Problematiche di programmazione multitasking} \label{sec:proc_multi_prog} @@ -2433,16 +3314,16 @@ di interruzione in una fase intermedia. In un ambiente multitasking il concetto è essenziale, dato che un processo può 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. +accorti nei confronti delle possibili \textit{race + condition}\itindex{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 @@ -2454,7 +3335,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ò @@ -2468,17 +3349,17 @@ 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. +\itindbeg{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ò @@ -2491,37 +3372,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{sezione~critica}) 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 +\itindbeg{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. +\itindend{race~condition} +\itindend{deadlock} \subsection{Le funzioni rientranti} @@ -2535,10 +3418,11 @@ multi-thread, ma si hanno gli stessi problemi quando si vogliono chiamare delle funzioni all'interno dei gestori dei segnali. Fintanto che una funzione opera soltanto con le variabili locali è rientrante; -queste infatti vengono allocate nello stack, e un'altra invocazione non fa -altro che allocarne un'altra copia. Una funzione può non essere rientrante -quando opera su memoria che non è nello stack. Ad esempio una funzione non è -mai rientrante se usa una variabile globale o statica. +queste infatti vengono allocate nello \itindex{stack} stack, ed un'altra +invocazione non fa altro che allocarne un'altra copia. Una funzione può non +essere rientrante quando opera su memoria che non è nello \itindex{stack} +stack. Ad esempio una funzione non è mai rientrante se usa una variabile +globale o statica. Nel caso invece la funzione operi su un oggetto allocato dinamicamente, la cosa viene a dipendere da come avvengono le operazioni: se l'oggetto è creato @@ -2558,8 +3442,51 @@ varie funzioni di libreria, che sono identificate aggiungendo il suffisso \code{\_r} al nome della versione normale. - %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil" %%% End: + +% LocalWords: multitasking like VMS child process identifier pid sez shell fig +% LocalWords: parent kernel init pstree keventd kswapd table struct linux call +% LocalWords: nell'header scheduler system interrupt timer HZ asm Hertz clock +% LocalWords: l'alpha tick fork wait waitpid exit exec image glibc int pgid ps +% LocalWords: sid threads thread Ingo Molnar ppid getpid getppid sys unistd LD +% LocalWords: void ForkTest tempnam pathname sibling cap errno EAGAIN ENOMEM +% LocalWords: stack read only copy write tab client spawn forktest sleep PATH +% LocalWords: source LIBRARY scheduling race condition printf descriptor dup +% LocalWords: close group session tms lock vfork execve BSD stream main abort +% LocalWords: SIGABRT SIGCHLD SIGHUP foreground SIGCONT termination signal ANY +% LocalWords: handler kill EINTR POSIX options WNOHANG ECHILD option WUNTRACED +% LocalWords: dump bits rusage getrusage heap const filename argv envp EACCES +% LocalWords: filesystem noexec EPERM suid sgid root nosuid ENOEXEC ENOENT ELF +% LocalWords: ETXTBSY EINVAL ELIBBAD BIG EFAULT EIO ENAMETOOLONG ELOOP ENOTDIR +% LocalWords: ENFILE EMFILE argc execl path execv execle execlp execvp vector +% LocalWords: list environ NULL umask pending utime cutime ustime fcntl linker +% LocalWords: opendir libc interpreter FreeBSD capabilities Mandatory Access +% LocalWords: Control MAC SELinux Security Modules LSM superuser uid gid saved +% LocalWords: effective euid egid dell' fsuid fsgid getuid geteuid getgid SVr +% LocalWords: getegid IDS NFS setuid setgid all' logout utmp screen xterm TODO +% LocalWords: setreuid setregid FIXME ruid rgid seteuid setegid setresuid size +% LocalWords: setresgid getresuid getresgid value result argument setfsuid DAC +% LocalWords: setfsgid NGROUPS sysconf getgroups getgrouplist groups ngroups +% LocalWords: setgroups initgroups patch LIDS CHOWN OVERRIDE Discrectionary PF +% LocalWords: SEARCH chattr sticky NOATIME socket domain immutable append mmap +% LocalWords: broadcast multicast multicasting memory locking mlock mlockall +% LocalWords: shmctl ioperm iopl chroot ptrace accounting swap reboot hangup +% LocalWords: vhangup mknod lease permitted inherited inheritable bounding AND +% LocalWords: capability capget capset header ESRCH undef version obj clear PT +% LocalWords: pag ssize length proc capgetp prehemptive cache runnable Stopped +% LocalWords: Uninterrutible SIGSTOP soft slice nice niceness counter which SC +% LocalWords: getpriority who setpriority RTLinux RTAI Adeos fault FIFO First +% LocalWords: yield Robin setscheduler policy param OTHER priority setparam to +% LocalWords: min getparam getscheduler interval robin ENOSYS fifo ping long +% LocalWords: affinity setaffinity unsigned mask cpu NUMA CLR ISSET SETSIZE RR +% LocalWords: getaffinity assembler deadlock REENTRANT SAFE tgz MYPGRP l'OR rr +% LocalWords: WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WCOREDUMP WIFSTOPPED +% LocalWords: WSTOPSIG opt char INTERP arg SIG IGN DFL mascheck grp FOWNER RAW +% LocalWords: FSETID SETPCAP BIND SERVICE ADMIN PACKET IPC OWNER MODULE RAWIO +% LocalWords: PACCT RESOURCE TTY CONFIG SETFCAP hdrp datap libcap lcap text tp +% LocalWords: get ncap caps CapInh CapPrm fffffeff CapEff getcap STAT dall'I +% LocalWords: inc PRIO SUSv PRGR prio SysV SunOS Ultrix sched timespec len sig +% LocalWords: cpusetsize cpuset atomic