X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=e632e248d6632736c00daa4ec64ffc301623d96e;hp=1824861819ac5f2282165d50426698226e4e6f23;hb=9a6d19e384fe9b1afbe4d9124ac34eaf7aa57562;hpb=310606a6731d75111c99aee10341625f56466acf diff --git a/prochand.tex b/prochand.tex index 1824861..651e145 100644 --- a/prochand.tex +++ b/prochand.tex @@ -1,64 +1,80 @@ +%% prochand.tex +%% +%% Copyright (C) 2000-2005 Simone Piccardi. Permission is granted to +%% copy, distribute and/or modify this document under the terms of the GNU Free +%% Documentation License, Version 1.1 or any later version published by the +%% Free Software Foundation; with the Invariant Sections being "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". +%% \chapter{La gestione dei processi} \label{cha:process_handling} -Come accennato nell'introduzione in un sistema unix ogni attività del sistema -viene svolta tramite i processi. In sostanza i processi costituiscono l'unità -base per l'allocazione e l'uso delle risorse del sistema. +Come accennato nell'introduzione in un sistema Unix tutte le operazioni +vengono svolte tramite opportuni processi. In sostanza questi ultimi vengono +a costituire l'unità base per l'allocazione e l'uso delle risorse del sistema. -Nel precedente capitolo abbiamo visto come funziona un singolo processo, in -questo capitolo affronteremo i dettagli della creazione e della distruzione -dei processi, della gestione dei loro attributi e privilegi, e di tutte le -funzioni a questo connesse. +Nel precedente capitolo abbiamo esaminato il funzionamento di un processo come +unità a se stante, in questo esamineremo il funzionamento dei processi +all'interno del sistema. Saranno cioè affrontati i dettagli della creazione e +della terminazione dei processi, della gestione dei loro attributi e +privilegi, e di tutte le funzioni a questo connesse. Infine nella sezione +finale introdurremo alcune problematiche generiche della programmazione in +ambiente multitasking. \section{Introduzione} \label{sec:proc_gen} -Partiremo con una introduzione generale ai concetti che stanno alla base della -gestione dei processi in unix. Introdurremo in questa sezione l'architettura -della gestione dei processi e le sue principali caratteristiche, e daremo una -panoramica sull'uso delle principali funzioni per la gestione dei processi. +Inizieremo con un'introduzione generale ai concetti che stanno alla base della +gestione dei processi in un sistema unix-like. Introdurremo in questa sezione +l'architettura della gestione dei processi e le sue principali +caratteristiche, dando una panoramica sull'uso delle principali funzioni di +gestione. -\subsection{La gerarchia dei processi} + +\subsection{L'architettura della gestione dei processi} \label{sec:proc_hierarchy} A differenza di quanto avviene in altri sistemi (ad esempio nel VMS la generazione di nuovi processi è un'operazione privilegiata) una delle -caratteristiche di unix (che esamineremo in dettaglio più avanti) è che +caratteristiche di Unix (che esamineremo in dettaglio più avanti) è che qualunque processo può a sua volta generarne altri, detti processi figli (\textit{child process}). Ogni processo è identificato presso il sistema da un -numero unico, il cosiddetto \textit{process identifier} o, più brevemente, -\acr{pid}. - -Una seconda caratteristica è che la generazione di un processo è una -operazione separata rispetto al lancio di un programma. In genere la sequenza -è sempre quella di creare un nuovo processo, il quale eseguirà, in un passo -successivo, il programma voluto: questo è ad esempio quello che fa la shell -quando mette in esecuzione il programma che gli indichiamo nella linea di -comando. - -Una terza caratteristica è che ogni processo viene sempre generato da un altro -che viene chiamato processo padre (\textit{parent process}). Questo vale per -tutti i processi, con una sola eccezione: dato che ci deve essere un punto di -partenza esiste sempre un processo speciale (che normalmente è +numero univoco, il cosiddetto \textit{process identifier} o, più brevemente, +\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 +processo è un'operazione separata rispetto al lancio di un programma. In +genere la sequenza è sempre quella di creare un nuovo processo, il quale +eseguirà, in un passo successivo, il programma desiderato: questo è ad esempio +quello che fa la shell quando mette in esecuzione il programma che gli +indichiamo nella linea di comando. + +Una terza caratteristica è che ogni processo è sempre stato generato da un +altro, che viene chiamato processo padre (\textit{parent process}). Questo +vale per tutti i processi, con una sola eccezione: dato che ci deve essere un +punto di partenza esiste un processo speciale (che normalmente è \cmd{/sbin/init}), che viene lanciato dal kernel alla conclusione della fase -di avvio, essendo questo il primo processo lanciato dal sistema ha sempre il +di avvio; essendo questo il primo processo lanciato dal sistema ha sempre il \acr{pid} uguale a 1 e non è figlio di nessun altro processo. Ovviamente \cmd{init} è un processo speciale che in genere si occupa di far 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 si alcuni di -essi in \secref{sec:proc_termination}) e non può mai essere terminato. La +amministrativi nelle operazioni ordinarie del sistema (torneremo su alcuni di +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 -posto, passando la riga \cmd{init=/bin/sh} come parametro di avvio). +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 +posto, passando la riga \cmd{init=/bin/sh} come parametro di avvio. \begin{figure}[!htb] \footnotesize \begin{verbatim} -[piccardi@selidor piccardi]$ pstree -n +[piccardi@gont piccardi]$ pstree -n init-+-keventd |-kapm-idled |-kreiserfsd @@ -98,37 +114,74 @@ init-+-keventd Dato che tutti i processi attivi nel sistema sono comunque generati da \cmd{init} o da uno dei suoi figli\footnote{in realtà questo non è del tutto - vero, in Linux ci sono alcuni processi che pur comparendo come figli di - init, o con \acr{pid} successivi, sono in realtà generati direttamente dal - kernel, (come \cmd{keventd}, \cmd{kswapd}, etc.)} si possono classificare i -processi con la relazione padre/figlio in una organizzazione gerarchica ad -albero, in maniera analoga a come i file sono organizzati in un albero di -directory (si veda \secref{sec:file_file_struct}); in \nfig\ si è mostrato il -risultato del comando \cmd{pstree} che permette di mostrare questa struttura, -alla cui base c'è \cmd{init} che è progenitore di tutti gli altri processi. + vero, in Linux ci sono alcuni processi speciali che pur comparendo come + figli di \cmd{init}, o con \acr{pid} successivi, sono in realtà generati + direttamente dal kernel, (come \cmd{keventd}, \cmd{kswapd}, 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 +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, +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 + \includegraphics[width=13cm]{img/task_struct} + \caption{Schema semplificato dell'architettura delle strutture usate dal + kernel nella gestione dei processi.} + \label{fig:proc_task_struct} +\end{figure} -\subsection{Una panoramica sulle funzioni di gestione} +Come accennato in sez.~\ref{sec:intro_unix_struct} è lo +\textit{scheduler}\index{\textit{scheduler}} che decide quale processo mettere +in esecuzione; esso viene eseguito ad ogni system call ed ad ogni +interrupt,\footnote{più in una serie di altre occasioni. NDT completare questa + parte.} (ma può essere anche attivato esplicitamente). Il timer di sistema +provvede comunque a che esso sia invocato periodicamente, generando un +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 + sez.~\ref{sec:sys_unix_time}).} +%Si ha cioè un interrupt dal timer ogni centesimo di secondo. + +Ogni volta che viene eseguito, lo \textit{scheduler}\index{\textit{scheduler}} +effettua il calcolo delle priorità dei vari processi attivi (torneremo su +questo in sez.~\ref{sec:proc_priority}) e stabilisce quale di essi debba +essere posto in esecuzione fino alla successiva invocazione. + + +\subsection{Una panoramica sulle funzioni fondamentali} \label{sec:proc_handling_intro} -I processi vengono creati dalla funzione \func{fork}; in molti unix questa è -una system call, Linux però usa un'altra nomenclatura, e la funzione 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 nuovo \acr{pid} e viene +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 \secref{sec:proc_fork}). +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 una informazione -abbastanza limitata (lo stato di terminazione) sulle cause della terminazione -del processo. +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. @@ -142,9 +195,9 @@ coi processi che Il programma che un processo sta eseguendo si chiama immagine del processo (o \textit{process image}), le funzioni della famiglia \func{exec} permettono di -caricare un'altro programma da disco sostituendo quest'ultimo all'immagine -corrente; questo fa si che l'immagine precedente venga completamente -cancellata. Questo significa che quando il nuovo programma esce anche il +caricare un altro programma da disco sostituendo quest'ultimo all'immagine +corrente; questo fa sì che l'immagine precedente venga completamente +cancellata. Questo significa che quando il nuovo programma termina, anche il processo termina, e non si può tornare alla precedente immagine. Per questo motivo la \func{fork} e la \func{exec} sono funzioni molto @@ -154,163 +207,153 @@ non ritorna mai (in quanto con essa viene eseguito un altro programma). -\section{La gestione dei processi} +\section{Le funzioni di base}% della gestione dei processi} \label{sec:proc_handling} -In questa sezione tratteremo le funzioni per la gestione dei processi, a -partire dalle funzioni elementari che permettono di leggerne gli -identificatori, alle varie funzioni di manipolazione dei processi, che -riguardano la loro creazione, terminazione, e la messa in esecuzione di altri +In questa sezione tratteremo le problematiche della gestione dei processi +all'interno del sistema, illustrandone tutti i dettagli. Inizieremo con le +funzioni elementari che permettono di leggerne gli identificatori, per poi +passare alla spiegazione delle funzioni base che si usano per la creazione e +la terminazione dei processi, e per la messa in esecuzione degli altri programmi. \subsection{Gli identificatori dei processi} \label{sec:proc_pid} -Come accennato nell'introduzione ogni processo viene identificato dal sistema -da un numero identificativo unico, il \textit{process id} o \acr{pid}; +Come accennato nell'introduzione, ogni processo viene identificato dal sistema +da un numero identificativo univoco, il \textit{process ID} o \acr{pid}; quest'ultimo è un tipo di dato standard, il \type{pid\_t} che in genere è un -intero con segno (nel caso di Linux e delle glibc il tipo usato è \type{int}). - -Il \acr{pid} viene assegnato in forma progressiva ogni volta che un nuovo -processo viene creato, fino ad un limite massimo (in genere essendo detto -numero memorizzato in un intero a 16 bit si arriva a 32767) oltre il quale si -riparte dal numero più basso disponibile (FIXME: verificare, non sono sicuro). -Per questo motivo processo il processo di avvio (\cmd{init}) ha sempre il -\acr{pid} uguale a uno. +intero con segno (nel caso di Linux e delle \acr{glibc} il tipo usato è +\ctyp{int}). + +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 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 -\textit{parent process id}). Questi due identificativi possono essere -ottenuti da programma usando le funzioni: +\textit{parent process ID}). Questi due identificativi possono essere +ottenuti usando le due funzioni \funcd{getpid} e \funcd{getppid}, i cui +prototipi sono: \begin{functions} -\headdecl{sys/types.h} -\headdecl{unistd.h} -\funcdecl{pid\_t getpid(void)} restituisce il pid del processo corrente. -\funcdecl{pid\_t getppid(void)} restituisce il pid del padre del processo - corrente. -Entrambe le funzioni non riportano condizioni di errore. + \headdecl{sys/types.h} + \headdecl{unistd.h} + \funcdecl{pid\_t getpid(void)} + + Restituisce il \acr{pid} del processo corrente. + + \funcdecl{pid\_t getppid(void)} + + Restituisce il \acr{pid} del padre del processo corrente. + +\bodydesc{Entrambe le funzioni non riportano condizioni di errore.} \end{functions} -esempi dell'uso di queste funzioni sono riportati in -\figref{fig:proc_fork_code}, nel programma di esempio \file{ForkTest.c}. +\noindent esempi dell'uso di queste funzioni sono riportati in +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 il -candidato ideale per generare ulteriori indicatori associati al processo di -cui diventa possibile garantire l'unicità: ad esempio la funzione -\func{tmpname} (si veda \secref{sec:file_temp_file}) usa il \acr{pid} per -generare un pathname univoco, che non potrà essere replicato da un'altro -processo che usi la stessa funzione. +Il fatto che il \acr{pid} sia un numero univoco per il sistema lo rende un +candidato per generare ulteriori indicatori associati al processo di cui +diventa possibile garantire l'unicità: ad esempio in alcune implementazioni la +funzione \func{tempnam} (si veda sez.~\ref{sec:file_temp_file}) usa il +\acr{pid} per generare un \index{\textit{pathname}}\textit{pathname} univoco, +che non potrà essere replicato da un altro processo che usi la stessa +funzione. Tutti i processi figli dello stesso processo padre sono detti \textit{sibling}, questa è una delle relazioni usate nel \textsl{controllo di sessione}, in cui si raggruppano i processi creati su uno stesso terminale, o relativi allo stesso login. Torneremo su questo argomento in dettaglio in -\secref{cap:session}, dove esamineremo i vari identificativi associati ad un -processo e le varie relazioni fra processi utilizzate per definire una +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 +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 sez.~\ref{sec:proc_perms}. + \subsection{La funzione \func{fork}} \label{sec:proc_fork} -La funzione \func{fork} è la funzione fondamentale della gestione dei +La funzione \funcd{fork} è la funzione fondamentale della gestione dei processi: come si è detto l'unico modo di creare un nuovo processo è attraverso l'uso di questa funzione, essa quindi riveste un ruolo centrale tutte le volte che si devono scrivere programmi che usano il multitasking. Il prototipo della funzione è: - \begin{functions} \headdecl{sys/types.h} \headdecl{unistd.h} \funcdecl{pid\_t fork(void)} - Restituisce zero al padre e il \acr{pid} al figlio in caso di successo, - ritorna -1 al padre (senza creare il figlio) in caso di errore; - \texttt{errno} può assumere i valori: + Crea un nuovo processo. + + \bodydesc{In caso di successo restituisce il \acr{pid} del figlio al padre e + zero al figlio; ritorna -1 al padre (senza creare il figlio) in caso di + errore; \var{errno} può assumere i valori: \begin{errlist} - \item \macro{EAGAIN} non ci sono risorse sufficienti per creare un'altro + \item[\errcode{EAGAIN}] non ci sono risorse sufficienti per creare un altro processo (per allocare la tabella delle pagine e le strutture del task) o si è esaurito il numero di processi disponibili. - \item \macro{ENOMEM} non è stato possibile allocare la memoria per le + \item[\errcode{ENOMEM}] non è stato possibile allocare la memoria per le strutture necessarie al kernel per creare il nuovo processo. - \end{errlist} + \end{errlist}} \end{functions} Dopo il successo dell'esecuzione di una \func{fork} sia il processo padre che -il processo figlio continuano ad essere eseguiti normalmente alla istruzione -seguente la \func{fork}; il processo figlio è però una copia del padre, e -riceve una copia dei segmenti di testo, stack e dati (vedi -\secref{sec:proc_mem_layout}), ed esegue esattamente lo stesso codice del -padre, ma la memoria è copiata, non condivisa\footnote{In generale il segmento - di testo, che è identico, è condiviso e tenuto in read-only, Linux poi - utilizza la tecnica del \textit{copy-on-write}, per cui la memoria degli - altri segmenti viene copiata dal kernel per il nuovo processo solo in caso - di scrittura, rendendo molto più efficiente il meccanismo} pertanto padre e -figlio vedono variabili diverse. +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 +sez.~\ref{sec:proc_mem_layout}), ed esegue esattamente lo stesso codice del +padre. Si tenga presente però che la memoria è copiata, non condivisa, +pertanto padre e figlio vedono variabili diverse. + +Per quanto riguarda la gestione della memoria, in generale il segmento di +testo, che è identico per i due processi, è condiviso e tenuto in read-only +per il padre e per i figli. Per gli altri segmenti Linux utilizza la tecnica +del \textit{copy on write}\index{\textit{copy~on~write}}; questa tecnica +comporta che una pagina di memoria viene effettivamente copiata per il nuovo +processo solo quando ci viene effettuata sopra una scrittura (e si ha quindi +una reale differenza fra padre e figlio). In questo modo si rende molto più +efficiente il meccanismo della creazione di un nuovo processo, non essendo più +necessaria la copia di tutto lo spazio degli indirizzi virtuali del padre, ma +solo delle pagine di memoria che sono state modificate, e solo al momento +della modifica stessa. La differenza che si ha nei due processi è che nel processo padre il valore di -ritorno della funzione fork è il \acr{pid} del processo figlio, mentre nel -figlio è zero; in questo modo il programma può identificare se viene eseguito -dal padre o dal figlio. Si noti come la funzione \func{fork} ritorni -\textbf{due} volte: una nel padre e una nel figlio. La sola differenza che si -ha nei due processi è il valore di ritorno restituito dalla funzione, che nel -padre è il \acr{pid} del figlio mentre nel figlio è zero; in questo modo il -programma può identificare se viene eseguito dal padre o dal figlio. - -La scelta di questi valori non è casuale, un processo infatti può avere più -figli, ed il valore di ritorno di \func{fork} è l'unico modo che permette di -identificare quello appena creato; al contrario un figlio ha sempre un solo -padre (il cui \acr{pid} può sempre essere ottenuto con \func{getppid}, vedi -\secref{sec:proc_pid}) e si usa il valore nullo, che non può essere il -\acr{pid} di nessun processo. +ritorno della funzione \func{fork} è il \acr{pid} del processo figlio, mentre +nel figlio è zero; in questo modo il programma può identificare se viene +eseguito dal padre o dal figlio. Si noti come la funzione \func{fork} ritorni +\textbf{due} volte: una nel padre e una nel figlio. + +La scelta di questi valori di ritorno non è casuale, un processo infatti può +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 sez.~\ref{sec:proc_pid}) per cui si usa il valore nullo, +che non è il \acr{pid} di nessun processo. \begin{figure}[!htb] - \footnotesize - \begin{lstlisting}{} -#include /* error definitions and routines */ -#include /* C standard library */ -#include /* unix standard library */ -#include /* standard I/O library */ -#include /* string functions */ - -/* Help printing routine */ -void usage(void); - -int main(int argc, char *argv[]) -{ -/* - * Variables definition - */ - int nchild, i; - pid_t pid; - int wait_child = 0; - int wait_parent = 0; - int wait_end = 0; - ... /* handling options */ - nchild = atoi(argv[optind]); - printf("Test for forking %d child\n", nchild); - /* loop to fork children */ - for (i=0; i output [piccardi@selidor sources]$ cat output @@ -448,107 +507,119 @@ Go to next child Spawned 3 child, pid 1970 Go to next child \end{verbatim} +\normalsize che come si vede è completamente diverso da quanto ottenevamo sul terminale. Il comportamento delle varie funzioni di interfaccia con i file è analizzato -in gran dettaglio in \capref{cha:file_unix_interface} e in -\secref{cha:files_std_interface}. Qui basta accennare che si sono usate le +in gran dettaglio in cap.~\ref{cha:file_unix_interface} e in +cap.~\ref{cha:files_std_interface}. Qui basta accennare che si sono usate le funzioni standard della libreria del C che prevedono l'output bufferizzato; e -questa bufferizzazione varia a seconda che si tratti di un file su disco (in -cui il buffer viene scaricato su disco solo quando necessario) o di un -terminale (nel qual caso il buffer viene scaricato ad ogni a capo). +questa bufferizzazione (trattata in dettaglio in sez.~\ref{sec:file_buffering}) +varia a seconda che si tratti di un file su disco (in cui il buffer viene +scaricato su disco solo quando necessario) o di un terminale (nel qual caso il +buffer viene scaricato ad ogni carattere di a capo). Nel primo esempio allora avevamo che ad ogni chiamata a \func{printf} il buffer veniva scaricato, e le singole righe erano stampate a video subito dopo l'esecuzione della \func{printf}. Ma con la redirezione su file la scrittura -non avviene più alla fine di ogni riga e l'output resta nel buffer, per questo -motivo, dato che ogni figlio riceve una copia della memoria del padre, esso -riceverà anche quanto c'è nel buffer delle funzioni di I/O, comprese le linee -scritte dal padre fino allora. Così quando all'uscita del figlio il buffer -viene scritto su disco, troveremo nel file anche tutto quello che il processo -padre aveva scritto prima della sua creazione. E alla fine del file, dato che -in questo caso il padre esce per ultimo, troviamo anche l'output del padre. - -Ma l'esempio ci mostra un'altro aspetto fondamentale dell'interazione con i -file, che era valido anche per l'esempio precedente, ma meno evidente; il -fatto cioè che non solo processi diversi possono scrivere in contemporanea -sullo stesso file (l'argomento della condivisione dei file in unix è trattato -in dettaglio in \secref{sec:file_sharing}), ma anche che, a differenza di -quanto avviene per le variabili, la posizione corrente sul file è condivisa -fra il padre e tutti i processi figli. +non avviene più alla fine di ogni riga e l'output resta nel buffer. Dato che +ogni figlio riceve una copia della memoria del padre, esso riceverà anche +quanto c'è nel buffer delle funzioni di I/O, comprese le linee scritte dal +padre fino allora. Così quando il buffer viene scritto su disco all'uscita del +figlio, troveremo nel file anche tutto quello che il processo padre aveva +scritto prima della sua creazione. E alla fine del file (dato che in questo +caso il padre esce per ultimo) troveremo anche l'output completo del padre. + +L'esempio ci mostra un altro aspetto fondamentale dell'interazione con i file, +valido anche per l'esempio precedente, ma meno evidente: il fatto cioè che non +solo processi diversi possono scrivere in contemporanea sullo stesso file +(l'argomento della condivisione dei file è trattato in dettaglio in +sez.~\ref{sec:file_sharing}), ma anche che, a differenza di quanto avviene per +le variabili, la posizione corrente sul file è condivisa fra il padre e tutti +i processi figli. Quello che succede è che quando lo standard output del padre viene rediretto, lo stesso avviene anche per tutti i figli; la funzione \func{fork} infatti ha -la caratteristica di duplicare (allo stesso modo in cui lo fa la funzione -\func{dup}, trattata in \secref{sec:file_dup}) nei figli tutti i file -descriptor aperti nel padre, il che comporta che padre e figli condividono -le stesse voci della file table (per la spiegazione di questi termini si veda -\secref{sec:file_sharing} e referenza a figura da fare) e quindi anche -l'offset corrente nel file. - -In questo modo se un processo scrive sul file aggiornerà l'offset sulla file -table, e tutti gli altri processi che condividono la file table vedranno il -nuovo valore; in questo modo si evita, in casi come quello appena mostrato in -cui diversi processi scrivono sullo stesso file, che l'output successivo di un -processo vada a sovrapporsi a quello dei precedenti (l'output potrà risultare -mescolato, ma non ci saranno parti perdute per via di una sovrascrittura). +la caratteristica di duplicare nei figli tutti i file descriptor aperti nel +padre (allo stesso modo in cui lo fa la funzione \func{dup}, trattata in +sez.~\ref{sec:file_dup}), il che comporta che padre e figli condividono le +stesse voci della \textit{file table} (per la spiegazione di questi termini si +veda sez.~\ref{sec:file_sharing}) fra cui c'è anche la posizione corrente nel +file. + +In questo modo se un processo scrive sul file aggiornerà la posizione corrente +sulla \textit{file table}, e tutti gli altri processi, che vedono la stessa +\textit{file table}, vedranno il nuovo valore. In questo modo si evita, in +casi come quello appena mostrato in cui diversi processi scrivono sullo stesso +file, che l'output successivo di un processo vada a sovrapporsi a quello dei +precedenti: l'output potrà risultare mescolato, ma non ci saranno parti +perdute per via di una sovrascrittura. Questo tipo di comportamento è essenziale in tutti quei casi in cui il padre -crea un figlio ed attende la sua conclusione per proseguire, ed entrambi -scrivono sullo stesso file, ad esempio lo standard output (un caso tipico è la -shell). Se l'output viene rediretto con questo comportamento avremo che il -padre potrà continuare a scrivere automaticamente in coda a quanto scritto dal -figlio; se così non fosse ottenere questo comportamento sarebbe estremamente -complesso necessitando di una qualche forma di comunicazione fra i due -processi. +crea un figlio e attende la sua conclusione per proseguire, ed entrambi +scrivono sullo stesso file (un caso tipico è la shell quando lancia un +programma, il cui output va sullo standard output). + +In questo modo, anche se l'output viene rediretto, il padre potrà sempre +continuare a scrivere in coda a quanto scritto dal figlio in maniera +automatica; se così non fosse ottenere questo comportamento sarebbe +estremamente complesso necessitando di una qualche forma di comunicazione fra +i due processi per far riprendere al padre la scrittura al punto giusto. In generale comunque non è buona norma far scrivere più processi sullo stesso -file senza una qualche forma di sincronizzazione in quanto, come visto con il -nostro esempio, le varie scritture risulteranno mescolate fra loro in una -sequenza impredicibile. Le modalità con cui in genere si usano i file dopo una -\func{fork} sono sostanzialmente due: -\begin{enumerate} +file senza una qualche forma di sincronizzazione in quanto, come visto anche +con il nostro esempio, le varie scritture risulteranno mescolate fra loro in +una sequenza impredicibile. Per questo le modalità con cui in genere si usano +i file dopo una \func{fork} sono sostanzialmente due: +\begin{enumerate*} \item Il processo padre aspetta la conclusione del figlio. In questo caso non è necessaria nessuna azione riguardo ai file, in quanto la sincronizzazione - degli offset dopo eventuali operazioni di lettura e scrittura effettuate dal - figlio è automatica. + della posizione corrente dopo eventuali operazioni di lettura e scrittura + effettuate dal figlio è automatica. \item L'esecuzione di padre e figlio procede indipendentemente. In questo caso - ciascuno dei due deve chiudere i file che non gli servono una volta che la - \func{fork} è stata eseguita, per evitare ogni forma di interferenza. -\end{enumerate} + ciascuno dei due processi deve chiudere i file che non gli servono una volta + che la \func{fork} è stata eseguita, per evitare ogni forma di interferenza. +\end{enumerate*} Oltre ai file aperti i processi figli ereditano dal padre una serie di altre -proprietà comuni; in dettaglio avremo che dopo l'esecuzione di una \func{fork} -padre e figlio avranno in comune: -\begin{itemize} -\item i file aperti (e gli eventuali flag di \textit{close-on-exec} se - settati). -\item gli identificatori per il controllo di accesso: il \textit{real user - id}, il \textit{real group id}, l'\textit{effective user id}, - l'\textit{effective group id} e i \textit{supplementary group id} (vedi - \secref{tab:proc_uid_gid}). +proprietà; la lista dettagliata delle proprietà che padre e figlio hanno in +comune dopo l'esecuzione di una \func{fork} è la seguente: +\begin{itemize*} +\item i file aperti e gli eventuali flag di + \textit{close-on-exec}\index{\textit{close-on-exec}} impostati (vedi + sez.~\ref{sec:proc_exec} e sez.~\ref{sec:file_fcntl}); +\item gli identificatori per il controllo di accesso: l'\textsl{user-ID + reale}, il \textsl{group-ID reale}, l'\textsl{user-ID effettivo}, il + \textsl{group-ID effettivo} ed i \textit{group-ID supplementari} (vedi + sez.~\ref{sec:proc_access_id}); \item gli identificatori per il controllo di sessione: il \textit{process - group id} e il \textit{session id} e il terminale di controllo. -\item i flag \acr{suid} e \acr{suid} (vedi \secref{sec:file_suid_sgid}). + group-ID} e il \textit{session id} ed il terminale di controllo (vedi + sez.~\ref{sec:sess_proc_group}); \item la directory di lavoro e la directory radice (vedi - \secref{sec:file_work_dir}). -\item la maschera dei permessi di creazione (vedi \secref{sec:file_umask}). -\item la maschera dei segnali. -\item i segmenti di memoria condivisa agganciati al processo. -\item i limiti sulle risorse -\item le variabili di ambiente (vedi \secref{sec:proc_environ}). -\end{itemize} -le differenze invece sono: -\begin{itemize} -\item il valore di ritorno di \func{fork}. -\item il \textit{process id}. -\item il \textit{parent process id} (quello del figlio viene settato al - \acr{pid} del padre). -\item i valori dei tempi di esecuzione (\var{tms\_utime}, \var{tms\_stime}, - \var{tms\_cutime}, \var{tms\_uetime}) che nel figlio sono posti a zero. -\item i \textit{file lock}, che non vengono ereditati dal figlio. -\item gli allarmi pendenti, che per il figlio vengono cancellati. -\end{itemize} + sez.~\ref{sec:file_work_dir} e sez.~\ref{sec:file_chroot}); +\item la maschera dei permessi di creazione (vedi sez.~\ref{sec:file_umask}); +\item la maschera dei segnali bloccati (vedi sez.~\ref{sec:sig_sigmask}) e le + azioni installate (vedi sez.~\ref{sec:sig_gen_beha}); +\item i segmenti di memoria condivisa agganciati al processo (vedi + sez.~\ref{sec:ipc_sysv_shm}); +\item i limiti sulle risorse (vedi sez.~\ref{sec:sys_resource_limit}); +\item le priorità real-time e le affinità di processore (vedi + sez.~\ref{sec:proc_real_time}); +\item le variabili di ambiente (vedi sez.~\ref{sec:proc_environ}). +\end{itemize*} +Le differenze fra padre e figlio dopo la \func{fork} invece sono: +\begin{itemize*} +\item il valore di ritorno di \func{fork}; +\item il \acr{pid} (\textit{process id}); +\item il \acr{ppid} (\textit{parent process id}), quello del figlio viene + impostato al \acr{pid} del padre; +\item i valori dei tempi di esecuzione della struttura \struct{tms} (vedi + sez.~\ref{sec:sys_cpu_times}) che nel figlio sono posti a zero; +\item i \textit{lock} sui file (vedi sez.~\ref{sec:file_locking}), che non + vengono ereditati dal figlio; +\item gli allarmi ed i segnali pendenti (vedi sez.~\ref{sec:sig_gen_beha}), che + per il figlio vengono cancellati. +\end{itemize*} \subsection{La funzione \func{vfork}} @@ -565,91 +636,99 @@ ritornare o uscire con \func{exit} ma usare esplicitamente \func{\_exit}. Questa funzione è un rimasuglio dei vecchi tempi in cui eseguire una \func{fork} comportava anche la copia completa del segmento dati del processo padre, che costituiva un inutile appesantimento in tutti quei casi in cui la -\func{fork} veniva fatto solo per poi eseguire una \func{exec}. La funzione +\func{fork} veniva fatta solo per poi eseguire una \func{exec}. La funzione venne introdotta in BSD per migliorare le prestazioni. -Dato che Linux supporta il \textit{copy on write} la perdita di prestazioni è +Dato che Linux supporta il \textit{copy on + write}\index{\textit{copy~on~write}} la perdita di prestazioni è assolutamente trascurabile, e l'uso di questa funzione (che resta un caso -speciale della funzione \func{clone}), è deprecato, per questo eviteremo di -trattarla ulteriormente. +speciale della system call \func{\_\_clone}) è deprecato; per questo eviteremo +di trattarla ulteriormente. -\subsection{La conclusione di un processo.} +\subsection{La conclusione di un processo} \label{sec:proc_termination} -In \secref{sec:proc_conclusion} abbiamo già affrontato le modalità con cui -concludere un programma, ma dal punto di vista del programma stesso; avendo a -che fare con un sistema multitasking occorre adesso affrontare l'argomento dal -punto di vista generale di come il sistema gestisce la conclusione dei -processi. +In sez.~\ref{sec:proc_conclusion} abbiamo già affrontato le modalità con cui +chiudere un programma, ma dall'interno del programma stesso; avendo a che fare +con un sistema multitasking resta da affrontare l'argomento dal punto di vista +di come il sistema gestisce la conclusione dei processi. -Abbiamo già visto in \secref{sec:proc_conclusion} le tre modalità con cui un +Abbiamo visto in sez.~\ref{sec:proc_conclusion} le tre modalità con cui un programma viene terminato in maniera normale: la chiamata di \func{exit} (che esegue le funzioni registrate per l'uscita e chiude gli stream), il ritorno dalla funzione \func{main} (equivalente alla chiamata di \func{exit}), e la chiamata ad \func{\_exit} (che passa direttamente alle operazioni di terminazione del processo da parte del kernel). -Ma oltre alla conclusione normale abbiamo accennato che esistono anche delle +Ma abbiamo accennato che oltre alla conclusione normale esistono anche delle modalità di conclusione anomala; queste sono in sostanza due: il programma può chiamare la funzione \func{abort} per invocare una chiusura anomala, o essere terminato da un segnale. In realtà anche la prima modalità si riconduce alla seconda, dato che \func{abort} si limita a generare il segnale -\macro{SIGABRT}. +\const{SIGABRT}. Qualunque sia la modalità di conclusione di un processo, il kernel esegue comunque una serie di operazioni: chiude tutti i file aperti, rilascia la memoria che stava usando, e così via; l'elenco completo delle operazioni eseguite alla chiusura di un processo è il seguente: -\begin{itemize} -\item tutti i descrittori dei file sono chiusi. -\item viene memorizzato lo stato di terminazione del processo. -\item ad ogni processo figlio viene assegnato un nuovo padre. -\item viene inviato il segnale \macro{SIGCHLD} al processo padre. -\item se il processo è un leader di sessione viene mandato un segnale di - \macro{SIGHUP} a tutti i processi in background e il terminale di controllo - viene disconnesso. -\item se la conclusione di un processe rende orfano un \textit{process group} - ciascun membro del gruppo viene bloccato, e poi gli vengono inviati in - successione i segnali \macro{SIGHUP} e \macro{SIGCONT}. -\end{itemize} -ma al di la di queste operazioni è necessario poter disporre di un meccanismo -ulteriore che consenta di sapere come questa terminazione è avvenuta; dato che -in un sistema unix-like tutto viene gestito attraverso i processi il -meccanismo scelto consiste nel riportare lo stato di terminazione -(\textit{termination status}) di cui sopra al processo padre. - -Nel caso di conclusione normale, lo stato di uscita del processo viene -caratterizzato tremite il valore del cosiddetto \textit{exit status}, cioè il +\begin{itemize*} +\item tutti i file descriptor sono chiusi; +\item viene memorizzato lo stato di terminazione del processo; +\item ad ogni processo figlio viene assegnato un nuovo padre (in genere + \cmd{init}); +\item viene inviato il segnale \const{SIGCHLD} al processo padre (vedi + sez.~\ref{sec:sig_sigchld}); +\item se il processo è un leader di sessione ed il suo terminale di controllo + è quello della sessione viene mandato un segnale di \const{SIGHUP} a tutti i + processi del gruppo di \textit{foreground} e il terminale di controllo viene + disconnesso (vedi sez.~\ref{sec:sess_ctrl_term}); +\item se la conclusione di un processo rende orfano un \textit{process + group} ciascun membro del gruppo viene bloccato, e poi gli vengono + inviati in successione i segnali \const{SIGHUP} e \const{SIGCONT} + (vedi ancora sez.~\ref{sec:sess_ctrl_term}). +\end{itemize*} + +Oltre queste operazioni è però necessario poter disporre di un meccanismo +ulteriore che consenta di sapere come la terminazione è avvenuta: dato che in +un sistema unix-like tutto viene gestito attraverso i processi, il meccanismo +scelto consiste nel riportare lo stato di terminazione (il cosiddetto +\textit{termination status}) al processo padre. + +Nel caso di conclusione normale, abbiamo visto in +sez.~\ref{sec:proc_conclusion} che lo stato di uscita del processo viene +caratterizzato tramite il valore del cosiddetto \textit{exit status}, cioè il valore passato alle funzioni \func{exit} o \func{\_exit} (o dal valore di ritorno per \func{main}). Ma se il processo viene concluso in maniera anomala il programma non può specificare nessun \textit{exit status}, ed è il kernel che deve generare autonomamente il \textit{termination status} per indicare le -ragioni della conclusione anomala. +ragioni della conclusione anomala. Si noti la distinzione fra \textit{exit status} e \textit{termination status}: quello che contraddistingue lo stato di chiusura del processo e viene riportato attraverso le funzioni \func{wait} o \func{waitpid} (vedi -\secref{sec:proc_wait}) è sempre quest'ultimo; in caso di conclusione normale +sez.~\ref{sec:proc_wait}) è sempre quest'ultimo; in caso di conclusione normale il kernel usa il primo (nel codice eseguito da \func{\_exit}) per produrre il secondo. La scelta di riportare al padre lo stato di terminazione dei figli, pur essendo l'unica possibile, comporta comunque alcune complicazioni: infatti se alla sua creazione è scontato che ogni nuovo processo ha un padre, non è detto -che sia così alla sua conclusione, dato che il padre protrebbe essere già +che sia così alla sua conclusione, dato che il padre potrebbe essere già terminato (si potrebbe avere cioè quello che si chiama un processo \textsl{orfano}). -Questa complicazione viene superata facendo in modo che il processo figlio -venga \textsl{adottato} da \cmd{init}: come già accennato quando un processo -termina il kernel controlla se è il padre di altri processi in esecuzione: in +Questa complicazione viene superata facendo in modo che il processo orfano +venga \textsl{adottato} da \cmd{init}. Come già accennato quando un processo +termina, il kernel controlla se è il padre di altri processi in esecuzione: in caso positivo allora il \acr{ppid} di tutti questi processi viene sostituito con il \acr{pid} di \cmd{init} (e cioè con 1); in questo modo ogni processo -avrà sempre un padre (nel caso \textsl{adottivo}) cui riportare il suo stato -di terminazione. Come verifica di questo comportamento possiamo eseguire il -comando \cmd{forktest} imponendo a ciascun processo figlio due -secondi di attesa prima di uscire, il risultato è: +avrà sempre un padre (nel caso possiamo parlare di un padre \textsl{adottivo}) +cui riportare il suo stato di terminazione. Come verifica di questo +comportamento possiamo eseguire il nostro programma \cmd{forktest} imponendo a +ciascun processo figlio due secondi di attesa prima di uscire, il risultato è: + +\footnotesize \begin{verbatim} [piccardi@selidor sources]$ ./forktest -c2 3 Process 1972: forking 3 child @@ -666,6 +745,7 @@ Go to next child Child 2, parent 1, exiting Child 1, parent 1, exiting \end{verbatim} +\normalsize come si può notare in questo caso il processo padre si conclude prima dei figli, tornando alla shell, che stampa il prompt sul terminale: circa due secondi dopo viene stampato a video anche l'output dei tre figli che @@ -679,22 +759,24 @@ informazioni riguardo ai processi che sta terminando. Questo viene fatto mantenendo attiva la voce nella tabella dei processi, e memorizzando alcuni dati essenziali, come il \acr{pid}, i tempi di CPU usati -dal processo (vedi \secref{sec:intro_unix_time}) e lo stato di terminazione -\footnote{NdA verificare esattamente cosa c'è!}, mentre la memoria in uso ed i -file aperti vengono rilasciati immediatamente. I processi che sono terminati, -ma il cui stato di terminazione non è stato ancora ricevuto dal padre sono -chiamati \textit{zombie}, essi restano presenti nella tabella dei processi ed -in genere possono essere identificati dall'output di \cmd{ps} per la presenza -di una \cmd{Z} nella colonna che ne indica lo stato. Quando il padre -effettuarà la lettura dello stato di uscita anche questa informazione, non più -necessaria, verrà scartata e la terminazione potrà dirsi completamente -conclusa. +dal processo (vedi sez.~\ref{sec:sys_unix_time}) e lo stato di terminazione, +mentre la memoria in uso ed i file aperti vengono rilasciati immediatamente. I +processi che sono terminati, ma il cui stato di terminazione non è stato +ancora ricevuto dal padre sono chiamati \textit{zombie}\index{zombie}, essi +restano presenti nella tabella dei processi ed in genere possono essere +identificati dall'output di \cmd{ps} per la presenza di una \texttt{Z} nella +colonna che ne indica lo stato (vedi tab.~\ref{tab:proc_proc_states}). Quando +il padre effettuerà la lettura dello stato di uscita anche questa +informazione, non più necessaria, verrà scartata e la terminazione potrà dirsi +completamente conclusa. Possiamo utilizzare il nostro programma di prova per analizzare anche questa -condizione: lanciamo il comando \cmd{forktest} in background, indicando al -processo padre di aspettare 10 secondi prima di uscire; in questo caso, usando -\cmd{ps} sullo stesso terminale (prima dello scadere dei 10 secondi) -otterremo: +condizione: lanciamo il comando \cmd{forktest} in \textit{background} (vedi +sez.~\ref{sec:sess_job_control}), indicando al processo padre di aspettare 10 +secondi prima di uscire; in questo caso, usando \cmd{ps} sullo stesso +terminale (prima dello scadere dei 10 secondi) otterremo: + +\footnotesize \begin{verbatim} [piccardi@selidor sources]$ ps T PID TTY STAT TIME COMMAND @@ -705,181 +787,206 @@ otterremo: 571 pts/0 Z 0:00 [forktest ] 572 pts/0 R 0:00 ps T \end{verbatim} %$ -e come si vede, dato che non si è fatto nulla per riceverne lo stato di -terminazione, i tre processi figli sono ancora presenti pur essendosi -conclusi, con lo stato di zombie e l'indicazione che sono stati terminati. - -La possibilità di avere degli zombie deve essere tenuta sempre presente quando -si scrive un programma che deve essere mantenuto in esecuzione a lungo e -creare molti figli. In questo caso si deve sempre avere cura di far leggere -l'eventuale stato di uscita di tutti i figli (in genere questo si fa +\normalsize e come si vede, dato che non si è fatto nulla per riceverne lo +stato di terminazione, i tre processi figli sono ancora presenti pur essendosi +conclusi, con lo stato di zombie\index{zombie} e l'indicazione che sono stati +terminati. + +La possibilità di avere degli zombie\index{zombie} deve essere tenuta sempre +presente quando si scrive un programma che deve essere mantenuto in esecuzione +a lungo e creare molti figli. In questo caso si deve sempre avere cura di far +leggere l'eventuale stato di uscita di tutti i figli (in genere questo si fa attraverso un apposito \textit{signal handler}, che chiama la funzione -\func{wait}, vedi \secref{sec:sig_xxx} e \secref{sec:proc_wait}). Questa -operazione è necessaria perché anche se gli \textit{zombie} non consumano -risorse di memoria o processore, occupano comunque una voce nella tabella dei -processi, che a lungo andare potrebbe esaurirsi. +\func{wait}, vedi sez.~\ref{sec:sig_sigchld} e sez.~\ref{sec:proc_wait}). +Questa operazione è necessaria perché anche se gli +\textit{zombie}\index{zombie} non consumano risorse di memoria o processore, +occupano comunque una voce nella tabella dei processi, che a lungo andare +potrebbe esaurirsi. Si noti che quando un processo adottato da \cmd{init} termina, esso non -diviene uno \textit{zombie}; questo perché una delle funzioni di \cmd{init} è -appunto quella di chiamare la funzione \func{wait} per i processi cui fa da -padre, completandone la terminazione. Questo è quanto avviene anche quando, -come nel caso del precedente esempio con \cmd{forktest}, il padre termina con -dei figli in stato di zombie: alla sua terminazione infatti tutti i suoi figli -vengono ereditati (compresi gli zombie) verranno adottati da \cmd{init}, il -quale provvederà a completarne la terminazione. - -Si tenga presente infine che siccome gli zombie sono processi già usciti, non -c'è modo di eliminarli con il comando \cmd{kill}; l'unica possibilità è quella -di terminare il processo che li ha generati, in modo che \cmd{init} possa -adottarli e provvedere a concludere la terminazione. - - -\subsection{Le funzioni \texttt{wait} e \texttt{waitpid}} +diviene uno \textit{zombie}\index{zombie}; questo perché una delle funzioni di +\cmd{init} è appunto quella di chiamare la funzione \func{wait} per i processi +cui fa da padre, completandone la terminazione. Questo è quanto avviene anche +quando, come nel caso del precedente esempio con \cmd{forktest}, il padre +termina con dei figli in stato di zombie\index{zombie}: alla sua terminazione +infatti tutti i suoi figli (compresi gli zombie\index{zombie}) verranno +adottati da \cmd{init}, il quale provvederà a completarne la terminazione. + +Si tenga presente infine che siccome gli zombie\index{zombie} sono processi +già usciti, non c'è modo di eliminarli con il comando \cmd{kill}; l'unica +possibilità di cancellarli dalla tabella dei processi è quella di terminare il +processo che li ha generati, in modo che \cmd{init} possa adottarli e +provvedere a concluderne la terminazione. + + +\subsection{Le funzioni \func{wait} e \func{waitpid}} \label{sec:proc_wait} -Abbiamo già accennato come uno degli usi possibili delle capacità multitasking -di un sistema unix-like consista nella creazione di programmi di tipo server, -in cui un processo principale attende le richieste che vengono poi soddisfatte -creando una serie di processi figli. Si è già sottolineato al paragrafo -precedente come in questo caso diventi necessario gestire esplicitamente la -conclusione dei vari processi figli onde evitare di riempire di -\textit{zombie} la tabella dei processi; le funzioni deputate a questo compito -sono sostanzialmente due, \func{wait} e \func{waitpid}. La prima, il cui -prototipo è: +Uno degli usi più comuni delle capacità multitasking di un sistema unix-like +consiste nella creazione di programmi di tipo server, in cui un processo +principale attende le richieste che vengono poi soddisfatte da una serie di +processi figli. Si è già sottolineato al paragrafo precedente come in questo +caso diventi necessario gestire esplicitamente la conclusione dei figli onde +evitare di riempire di \textit{zombie}\index{zombie} la tabella dei processi; +le funzioni deputate a questo compito sono sostanzialmente due, \funcd{wait} e +\func{waitpid}. La prima, il cui prototipo è: \begin{functions} \headdecl{sys/types.h} \headdecl{sys/wait.h} -\funcdecl{pid\_t wait(int * status)} +\funcdecl{pid\_t wait(int *status)} Sospende il processo corrente finché un figlio non è uscito, o finché un -segnale termina il processo o chiama una funzione di gestione. Se un figlio è -già uscito la funzione ritorna immediatamente. Al ritorno lo stato di -termininazione del processo viene salvato nella variabile puntata da -\var{status} e tutte le informazioni relative al processo (vedi -\secref{sec:proc_termination}) vengono rilasciate. - -La funzione restituisce il \acr{pid} del figlio in caso di successo e -1 in -caso di errore; \var{errno} può assumere i valori: +segnale termina il processo o chiama una funzione di gestione. + +\bodydesc{La funzione restituisce il \acr{pid} del figlio in caso di successo + e -1 in caso di errore; \var{errno} può assumere i valori: \begin{errlist} - \item \macro{EINTR} la funzione è stata interrotta da un segnale. - \end{errlist} + \item[\errcode{EINTR}] la funzione è stata interrotta da un segnale. + \end{errlist}} \end{functions} -è presente fin dalle prime versioni di unix; la funzione ritorna alla -conclusione del primo figlio (o immediatamente se un figlio è già uscito). Nel -caso un processo abbia più figli il valore di ritorno permette di identificare -qual'è quello che è uscito. - -Questa funzione però ha il difetto di essere poco flessibile, in quanto -ritorna all'uscita di un figlio qualunque. Nelle occasioni in cui è necessario -attendere la conclusione di un processo specifico occorre predisporre un -meccanismo che tenga conto dei processi già terminati, e ripeta la chiamata -alla funzione nel caso il processo cercato sia ancora attivo. - -Per questo motivo lo standard POSIX.1 ha introdotto la funzione \func{waitpid} -che effettua lo stesso servizio, ma dispone di una serie di funzionalità più -ampie, legate anche al controllo di sessione. Dato che è possibile ottenere -lo stesso comportamento di \func{wait} si consiglia di utilizzare sempre -questa funzione; il suo prototipo è: +\noindent +è presente fin dalle prime versioni di Unix; la funzione ritorna non appena un +processo figlio termina. Se un figlio è già terminato la funzione ritorna +immediatamente, se più di un figlio è terminato occorre chiamare la funzione +più volte se si vuole recuperare lo stato di terminazione di tutti quanti. + +Al ritorno della funzione lo stato di terminazione del figlio viene salvato +nella variabile puntata da \param{status} e tutte le risorse del kernel +relative al processo (vedi sez.~\ref{sec:proc_termination}) vengono rilasciate. +Nel caso un processo abbia più figli il valore di ritorno (il \acr{pid} del +figlio) permette di identificare qual è quello che è uscito. + +Questa funzione ha il difetto di essere poco flessibile, in quanto ritorna +all'uscita di un qualunque processo figlio. Nelle occasioni in cui è +necessario attendere la conclusione di un processo specifico occorrerebbe +predisporre un meccanismo che tenga conto dei processi già terminati, e +provvedere a ripetere la chiamata alla funzione nel caso il processo cercato +sia ancora attivo. + +Per questo motivo lo standard POSIX.1 ha introdotto la funzione +\funcd{waitpid} che effettua lo stesso servizio, ma dispone di una serie di +funzionalità più ampie, legate anche al controllo di sessione (si veda +sez.~\ref{sec:sess_job_control}). Dato che è possibile ottenere lo stesso +comportamento di \func{wait} si consiglia di utilizzare sempre questa +funzione, il cui prototipo è: \begin{functions} \headdecl{sys/types.h} \headdecl{sys/wait.h} -\funcdecl{pid\_t waitpid(pid\_t pid, int * status, int options)} +\funcdecl{pid\_t waitpid(pid\_t pid, int *status, int options)} +Attende la conclusione di un processo figlio. -La funzione restituisce il \acr{pid} del processo che è uscito, 0 se è stata -specificata l'opzione \macro{WNOHANG} e il processo non è uscito e -1 per un -errore, nel qual caso \var{errno} assumerà i valori: +\bodydesc{La funzione restituisce il \acr{pid} del processo che è uscito, 0 se + è stata specificata l'opzione \const{WNOHANG} e il processo non è uscito e + -1 per un errore, nel qual caso \var{errno} assumerà i valori: \begin{errlist} - \item \macro{EINTR} se non è stata specificata l'opzione \macro{WNOHANG} e + \item[\errcode{EINTR}] se non è stata specificata l'opzione \const{WNOHANG} e la funzione è stata interrotta da un segnale. - \item \macro{ECHILD} il processo specificato da \var{pid} non esiste o non è - figlio del processo chiamante. - \end{errlist} + \item[\errcode{ECHILD}] il processo specificato da \param{pid} non esiste o + non è figlio del processo chiamante. + \end{errlist}} \end{functions} Le differenze principali fra le due funzioni sono che \func{wait} si blocca sempre fino a che un processo figlio non termina, mentre \func{waitpid} ha la -possibilità si specificare un'opzione \macro{WNOHANG} che ne previene il -blocco; inoltre \func{waitpid} può specificare quale processo attendere sulla -base del valore specificato tramite la variabile \var{pid}, secondo lo -specchietto riportato in \ntab: +possibilità si specificare un'opzione \const{WNOHANG} che ne previene il +blocco; inoltre \func{waitpid} può specificare in maniera flessibile quale +processo attendere, sulla base del valore fornito dall'argomento \param{pid}, +secondo lo specchietto riportato in tab.~\ref{tab:proc_waidpid_pid}. + \begin{table}[!htb] \centering - \begin{tabular}[c]{|c|p{10cm}|} + \footnotesize + \begin{tabular}[c]{|c|c|p{8cm}|} \hline - \textbf{Valore} & \textbf{Significato}\\ + \textbf{Valore} & \textbf{Opzione} &\textbf{Significato}\\ \hline \hline - $<-1$& attende per un figlio il cui \textit{process group} è uguale al - valore assoluto di \var{pid}. \\ - $-1$ & attende per un figlio qualsiasi, usata in questa maniera è - equivalente a \func{wait}.\\ - $0$ & attende per un figlio il cui \textit{process group} è uguale a - quello del processo chiamante. \\ - $>0$ & attende per un figlio il cui \acr{pid} è uguale al - valore di \var{pid}.\\ + $<-1$& -- & attende per un figlio il cui \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 \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 del parametro \var{pid} della funzione + \caption{Significato dei valori dell'argomento \param{pid} della funzione \func{waitpid}.} \label{tab:proc_waidpid_pid} \end{table} -Il comportamento di \func{waitpid} può essere modificato passando delle -opportune opzioni tramite la variabile \var{option}. I valori possibili sono -il già citato \macro{WNOHANG}, che previene il blocco della funzione quando il -processo figlio non è terminato, e \macro{WUNTRACED} (usata per il controllo -di sessione, trattato in \capref{cha:session}) che fa ritornare la funzione -anche per i processi figli che sono bloccati ed il cui stato non è stato -ancora riportato al padre. Il valore dell'opzione deve essere specificato come -mashera binaria ottenuta con l'OR delle suddette costanti con zero. +Il comportamento di \func{waitpid} può inoltre essere modificato passando +delle opportune opzioni tramite l'argomento \param{option}. I valori possibili +sono il già citato \const{WNOHANG}, che previene il blocco della funzione +quando il processo figlio non è terminato, e \const{WUNTRACED} che permette di +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 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 si è visto nella sezione precedente, una -delle azioni prese dal kernel alla conclusione di un processo è quella di -mandare un segnale di \macro{SIGCHLD} al padre. Questo segnale viene ignorato -di default, ma costituisce il meccanismo di comunicazione asincrona con cui il -kernel avverte un processo padre che uno dei suoi figli è terminato. +momento. Per questo motivo, come accennato nella sezione precedente, una delle +azioni prese dal kernel alla conclusione di un processo è quella di mandare un +segnale di \const{SIGCHLD} al padre. L'azione predefinita (si veda +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. In genere in un programma non si vuole essere forzati ad attendere la conclusione di un processo per proseguire, specie se tutto questo serve solo -per leggerne lo stato di chiusura (ed evitare la presenza di \textit{zombie}), -per questo la modalità più usata per chiamare queste funzioni è quella di -utilizzarle all'interno di un \textit{signal handler} (torneremo sui segnali e -su come gestire \macro{SIGCHLD} in \secref{sec:sig_sigwait_xxx}) nel qual -caso, dato che il segnale è generato dalla terminazione un figlio, avremo la -certezza che la chiamata a \func{wait} non si bloccherà. +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 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à. \begin{table}[!htb] \centering + \footnotesize \begin{tabular}[c]{|c|p{10cm}|} \hline \textbf{Macro} & \textbf{Descrizione}\\ \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 - \macro{WIFEXITED} ha restitituito 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 - \macro{WIFSIGNALED} ha restitituito 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 - \macro{WIFSIGNALED} ha restitituito un valore non nullo\footnote{questa - macro non è definita dallo standard POSIX.1, ma è presente come estensione - sia in Linux che in altri unix}.\\ + file di \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 \macro{WUNTRACED}. \\ - \macro{WSTOPSIG(s)} & restituisce il numero del segnale che ha bloccato - il processo, Può essere valutata solo se \macro{WIFSTOPPED} ha - restitituito 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 @@ -887,143 +994,120 @@ certezza che la chiamata a \func{wait} non si bloccher \label{tab:proc_status_macro} \end{table} - -Entrambe le funzioni restituiscono lo stato di terminazione del processo -tramite il puntatore \var{status} (se non interessa memorizzare lo stato si -può passare un puntatore nullo). Il valore restituito da entrambe le funzioni -dipende dall'implementazione, e tradizionalmente alcuni bit sono riservati per -memorizzare lo stato di uscita (in genere 8) altri per indicare il segnale che -ha causato la terminazione (in caso di conclusione anomala), uno per indicare -se è stato generato un core file, etc.\footnote{le definizioni esatte si - possono trovare in \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 \curtab\ (si tenga presente che -queste macro prendono come parametro la variabile di tipo \type{int} puntata -da \var{status}). +\footnotetext{questa macro non è definita dallo standard POSIX.1, ma è + 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{}.} + +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 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 -\macro{WTERMSIG} può essere controllato contro le costanti definite in -\file{signal.h}, e stampato usando le funzioni definite in -\secref{sec:sig_strsignal}. - -Linux, seguendo una estensione di BSD, supporta altre due funzioni per la -lettura dello stato di terminazione di un processo, analoghe a \func{wait} e -\func{waitpid}, ma che prevedono un ulteriore parametro attraverso il quale il -kernel può restituire al processo padre ulteriori informazioni sulle risorse -usate dal processo terminato e dai vari figli. -Queste funzioni diventano accessibili definendo la costante \macro{\_USE\_BSD} -sono: +\val{WTERMSIG} può essere confrontato con le costanti definite in +\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}} +\label{sec:proc_wait4} +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 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 +sono: \begin{functions} - \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)} - La funzione è identica a \func{waitpid} sia per comportamento che per i - valori dei parametri, ma restituisce in \var{rusage} un sommario delle - risorse usate dal processo (per i dettagli vedi \secref{sec:xxx_limit_res}) - \funcdecl{pid\_t wait3(int *status, int options, struct rusage *rusage)} - Prima versione, equivalente a \func{wait4(-1, \&status, opt, rusage)} ormai - deprecata in favore di \func{wait4}. + \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 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)} + Prima versione, equivalente a \code{wait4(-1, \&status, opt, rusage)} è + ormai deprecata in favore di \func{wait4}. \end{functions} -la struttura \type{rusage} è definita in \file{sys/resource.h}, e viene -utilizzata anche dalla funzione \func{getrusage} per ottenere le risorse di -sistema usate dal processo; in Linux è definita come: -\begin{figure}[!htb] - \footnotesize - \centering - \begin{minipage}[c]{15cm} - \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} -struct rusage { - struct timeval ru_utime; /* user time used */ - struct timeval ru_stime; /* system time used */ - long ru_maxrss; /* maximum resident set size */ - long ru_ixrss; /* integral shared memory size */ - long ru_idrss; /* integral unshared data size */ - long ru_isrss; /* integral unshared stack size */ - long ru_minflt; /* page reclaims */ - long ru_majflt; /* page faults */ - long ru_nswap; /* swaps */ - long ru_inblock; /* block input operations */ - long ru_oublock; /* block output operations */ - long ru_msgsnd; /* messages sent */ - long ru_msgrcv; /* messages received */ - long ru_nsignals; ; /* signals received */ - long ru_nvcsw; /* voluntary context switches */ - long ru_nivcsw; /* involuntary context switches */ -}; - \end{lstlisting} - \end{minipage} - \normalsize - \caption{La struttura \texttt{rusage} per la lettura delle informazioni dei - delle risorse usate da un processo.} - \label{fig:proc_rusage_struct} -\end{figure} -In genere includere esplicitamente \file{} non è più necessario, -ma aumenta la portabiltà, e serve in caso si debba accedere ai campi di -\var{rusage} definiti come \type{struct timeval}. La struttura è ripresa dalla -versione 4.3 Reno di BSD, attualmente (con il kernel 2.4.x) i soli campi che -sono mantenuti sono: \var{ru\_utime}, \var{ru\_stime}, \var{ru\_minflt}, -\var{ru\_majflt}, e \var{ru\_nswap}. - -\subsection{Le funzioni \texttt{exec}} +\noindent +la struttura \struct{rusage} è definita in \file{sys/resource.h}, e viene +utilizzata anche dalla funzione \func{getrusage} (vedi +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}} \label{sec:proc_exec} Abbiamo già detto che una delle modalità principali con cui si utilizzano i -processi in unix è quella di usarli per lanciare nuovi programmi: questo viene +processi in Unix è quella di usarli per lanciare nuovi programmi: questo viene 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, o +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. Ci sono sei diverse versioni di \func{exec} (per questo la si è chiamata -famiglia di funzioni) che possono essere usate per questo compito, che in -realtà (come mostrato in \figref{fig:proc_exec_relat}), costituiscono un -front-end a \func{execve}. Il prototipo di quest'ultima è: - +famiglia di funzioni) che possono essere usate per questo compito, in realtà +(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[])} +{int execve(const char *filename, char *const argv[], char *const envp[])} + Esegue il programma contenuto nel file \param{filename}. - La funzione esegue il file o lo script indicato da \var{filename}, - passandogli la lista di argomenti indicata da \var{argv} e come ambiente la - lista di stringhe indicata da \var{envp}; entrambe le liste devono essere - terminate da un puntatore nullo. I vettori degli argomenti e dell'ambiente - possono essere acceduti dal nuovo programma quando la sua funzione - \func{main} è dichiarata nella forma \func{main(int argc, char *argv[], char - *envp[])}. - - La funzione ritorna -1 solo in caso di errore, nel qual caso caso la - variabile \texttt{errno} è settata come: + \bodydesc{La funzione ritorna solo in caso di errore, restituendo -1; nel + qual caso \var{errno} può assumere i valori: \begin{errlist} - \item \macro{EACCES} il file non è eseguibile, oppure il filesystem è - montato in \cmd{noexec}, oppure non è un file normale o un interprete. - \item \macro{EPERM} il file ha i bit \acr{suid} o \acr{sgid} ma l'utente non - è root o il filesystem è montato con \cmd{nosuid}, oppure - \item \macro{ENOEXEC} il file è in un formato non eseguibile o non + \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{ENOEXEC}] il file è in un formato non eseguibile o non riconosciuto come tale, o compilato per un'altra architettura. - \item \macro{ENOENT} il file o una delle librerie dinamiche o l'inteprete + \item[\errcode{ENOENT}] il file o una delle librerie dinamiche o l'interprete necessari per eseguirlo non esistono. - \item \macro{ETXTBSY} L'eseguibile è aperto in scrittura da uno o più + \item[\errcode{ETXTBSY}] l'eseguibile è aperto in scrittura da uno o più processi. - \item \macro{EINVAL} L'eseguibile ELF ha più di un segmento - \macro{PF\_INTERP}, cioè chiede di essere eseguito da più di un interprete. - \item \macro{ELIBBAD} Un interprete ELF non è in un formato riconoscibile. + \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 + riconoscibile. + \item[\errcode{E2BIG}] la lista degli argomenti è troppo grande. \end{errlist} - ed inoltre anche \macro{EFAULT}, \macro{ENOMEM}, \macro{EIO}, - \macro{ENAMETOOLONG}, \macro{E2BIG}, \macro{ELOOP}, \macro{ENOTDIR}, - \macro{ENFILE}, \macro{EMFILE}. + ed inoltre anche \errval{EFAULT}, \errval{ENOMEM}, \errval{EIO}, + \errval{ENAMETOOLONG}, \errval{ELOOP}, \errval{ENOTDIR}, \errval{ENFILE}, + \errval{EMFILE}.} \end{prototype} -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 -prototipi sono: +La funzione \func{exec} esegue il file o lo script indicato da +\param{filename}, passandogli la lista di argomenti indicata da \param{argv} +e come ambiente la lista di stringhe indicata da \param{envp}; entrambe le +liste devono essere terminate da un puntatore nullo. I vettori degli +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 di +possibili diverse interfacce per la creazione di un nuovo processo. I loro +prototipi sono: \begin{functions} \headdecl{unistd.h} \funcdecl{int execl(const char *path, const char *arg, ...)} @@ -1034,21 +1118,22 @@ 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. -Queste funzioni ritornano solo in caso di errore, restituendo -1; nel qual -caso \var{errno} andrà ad assumere i valori visti in precedenza per -\func{execve}. +\bodydesc{Queste funzioni ritornano solo in caso di errore, restituendo -1; + nel qual caso \var{errno} assumerà i valori visti in precedenza per + \func{execve}.} \end{functions} Per capire meglio le differenze fra le funzioni della famiglia si può fare -riferimento allo specchietto riportato in \ntab. 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 \var{argv} e \var{argc} visti -dalla funzione \func{main} del programma chiamato). +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). -Queste modalità sono due e sono riassunte dagli mnenonici \func{v} e \func{l} +Queste modalità sono due e sono riassunte dagli mnemonici \code{v} e \code{l} che stanno rispettivamente per \textit{vector} e \textit{list}. Nel primo caso gli argomenti sono passati tramite il vettore di puntatori \var{argv[]} a stringhe terminate con zero che costituiranno gli argomenti a riga di comando, @@ -1056,9 +1141,7 @@ questo vettore \emph{deve} essere terminato da un puntatore nullo. Nel secondo caso le stringhe degli argomenti sono passate alla funzione come lista di puntatori, nella forma: -\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} - char * arg0, char * arg1, ..., char * argn, NULL -\end{lstlisting} +\includecodesnip{listati/char_list.c} che deve essere terminata da un puntatore nullo. In entrambi i casi vale la convenzione che il primo argomento (\var{arg0} o \var{argv[0]}) viene usato per indicare il nome del file che contiene il programma che verrà eseguito. @@ -1071,15 +1154,15 @@ per indicare il nome del file che contiene il programma che verr \multicolumn{1}{|c|}{\textbf{Caratteristiche}} & \multicolumn{6}{|c|}{\textbf{Funzioni}} \\ \hline - &\func{execl\ }&\func{execlp}&\func{execle} - &\func{execv\ }& \func{execvp}& \func{execve} \\ + &\func{execl}\texttt{ }&\func{execlp}&\func{execle} + &\func{execv}\texttt{ }& \func{execvp}& \func{execve} \\ \hline \hline 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$& \\ @@ -1091,151 +1174,1574 @@ per indicare il nome del file che contiene il programma che verr \end{table} La seconda differenza fra le funzioni riguarda le modalità con cui si -specifica il programma che si vuole eseguire. Con lo mnemonico \func{p} si -indicano le due funzioni che necessitano del \textit{pathname} assoluto del -programma come valore del parametro \var{path}. Le altre quattro funzioni -invece usano come parametro \var{file} un nome che viene cercato -automaticamente fra i file presenti nella lista di directory specificate dalla -variabile di ambiente \var{PATH}. +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 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 +proseguita nelle eventuali ulteriori directory indicate in \var{PATH}; solo se +non viene trovato nessun altro file viene finalmente restituito +\errcode{EACCES}. + +Le altre quattro funzioni si limitano invece a cercare di eseguire il file +indicato dall'argomento \param{path}, che viene interpretato come il +\index{\textit{pathname}}\textit{pathname} del programma. + +\begin{figure}[htb] + \centering + \includegraphics[width=15cm]{img/exec_rel} + \caption{La interrelazione fra le sei funzioni della famiglia \func{exec}.} + \label{fig:proc_exec_relat} +\end{figure} La terza differenza è come viene passata la lista delle variabili di ambiente. -Con lo mnemonico \func{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 \macro{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}). -\item il \textit{real user ID} ed il \textit{real group ID} (vedi - \secref{sec:proc_user_group}). -\item i \textit{supplementary group ID} (vedi \secref{sec:proc_user_group}). -\item il \textit{session ID} ed il \textit{process group ID} (vedi - \secref{sec:sess_xxx}). -\item il terminale di controllo (vedi \secref{sec:sess_xxx}). -\item il tempo restante ad un allarme. +\begin{itemize*} +\item il \textit{process id} (\acr{pid}) ed il \textit{parent process id} + (\acr{ppid}); +\item l'\textsl{user-ID reale}, il \textit{group-ID reale} ed i + \textsl{group-ID supplementari} (vedi sez.~\ref{sec:proc_access_id}); +\item il \textit{session id} (\acr{sid}) ed il \textit{process group-ID} + (\acr{pgid}), vedi 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_xxx}). -\item i segnali sospesi (\textit{pending}) e la maschera dei segnali. -\item i limiti sulle risorse (vedi \secref{sec:limits_xxx}).. + 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 + 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:xxx_xxx}).. -\end{itemize} - - - - + \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 +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 +sez.~\ref{sec:sig_gen_beha}). + +La gestione dei file aperti dipende dal valore che ha il flag di +\textit{close-on-exec}\index{\textit{close-on-exec}} (vedi anche +sez.~\ref{sec:file_fcntl}) per ciascun file descriptor. I file per cui è +impostato vengono chiusi, tutti gli altri file restano aperti. Questo +significa che il comportamento predefinito è che i file restano aperti +attraverso una \func{exec}, a meno di una chiamata esplicita a \func{fcntl} +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 sez.~\ref{sec:file_dir_read}) che effettua da sola +l'impostazione del flag di +\textit{close-on-exec}\index{\textit{close-on-exec}} sulle directory che apre, +in maniera trasparente all'utente. + +Abbiamo detto che l'\textsl{user-ID reale} ed il \textsl{group-ID reale} +restano gli stessi all'esecuzione di \func{exec}; lo stesso vale per +l'\textsl{user-ID effettivo} ed il \textsl{group-ID effettivo} (il significato +di questi identificatori è trattato in sez.~\ref{sec:proc_access_id}), tranne +quando il file che si va ad eseguire abbia o il \acr{suid} bit o lo \acr{sgid} +bit impostato, in questo caso l'\textsl{user-ID effettivo} ed il +\textsl{group-ID effettivo} vengono impostati rispettivamente all'utente o al +gruppo cui il file appartiene (per i dettagli vedi 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{/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 [argomenti]} dove l'interprete indicato +deve essere un programma valido (binario, non un altro script) che verrà +chiamato come se si fosse eseguito il comando \cmd{interpreter [argomenti] + filename}.\footnote{si tenga presente che con Linux quanto viene scritto + come \texttt{argomenti} viene passato all'inteprete come un unico argomento + con una unica stringa di lunghezza massima di 127 caratteri e se questa + dimensione viene ecceduta la stringa viene troncata; altri Unix hanno + dimensioni massime diverse, e diversi comportamenti, ad esempio FreeBSD + esegue la scansione della riga e la divide nei vari argomenti e se è troppo + lunga restitituisce un errore di \const{ENAMETOOLONG}, una comparazione dei + vari comportamenti si trova su + \href{http://www.in-ulm.de/~mascheck/various/shebang/} + {\texttt{http://www.in-ulm.de/\tild mascheck/various/shebang/}}.} Con la famiglia delle \func{exec} si chiude il novero delle funzioni su cui è -basato il controllo dei processi in unix: con \func{fork} si crea un nuovo -processo, con \func{exec} si avvia un nuovo programma, con \func{exit} e -\func{wait} si effettua e si gestisce la conclusione dei programmi. Tutte le -altre funzioni sono ausiliarie e servono la lettura e il settaggio dei vari -parametri connessi ai processi. +basata la gestione dei processi in Unix: con \func{fork} si crea un nuovo +processo, con \func{exec} si lancia un nuovo programma, con \func{exit} e +\func{wait} si effettua e verifica la conclusione dei processi. Tutte le +altre funzioni sono ausiliarie e servono per la lettura e l'impostazione dei +vari parametri connessi ai processi. + \section{Il controllo di accesso} \label{sec:proc_perms} In questa sezione esamineremo le problematiche relative al controllo di -accesso dal punto di vista del processi; gli identificativi usati, come questi -vengono modificati nella creazione e nel lancio di nuovi processi, e le varie -funzioni per la loro manipolazione diretta. +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. + + +\subsection{Gli identificatori del controllo di accesso} +\label{sec:proc_access_id} + +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 + 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 + per modularizzare tutti i possibili controlli di accesso.} di sicurezza di +un sistema unix-like è fondato sui concetti di utente e gruppo, e sulla +separazione fra l'amministratore (\textsl{root}, detto spesso anche +\textit{superuser}) che non è sottoposto a restrizioni, ed il resto degli +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 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. + +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 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 +disporre di privilegi diversi, o dover impersonare un altro utente per un +limitato insieme di operazioni. Per questo motivo in generale tutti gli Unix +prevedono che i processi abbiano almeno due gruppi di identificatori, chiamati +rispettivamente \textit{real} ed \textit{effective} (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 +tab.~\ref{tab:proc_uid_gid}. + +\begin{table}[htb] + \footnotesize + \centering + \begin{tabular}[c]{|c|c|l|p{7.3cm}|} + \hline + \textbf{Suffisso} & \textbf{Gruppo} & \textbf{Denominazione} + & \textbf{Significato} \\ + \hline + \hline + \acr{uid} & \textit{real} & \textsl{user-ID reale} + & indica l'utente che ha lanciato il programma\\ + \acr{gid} & '' &\textsl{group-ID reale} + & indica il gruppo principale dell'utente che ha lanciato + il programma \\ + \hline + \acr{euid} & \textit{effective} &\textsl{user-ID effettivo} + & indica l'utente usato nel controllo di accesso \\ + \acr{egid} & '' & \textsl{group-ID effettivo} + & indica il gruppo usato nel controllo di accesso \\ + -- & -- & \textsl{group-ID supplementari} + & indicano gli ulteriori gruppi cui l'utente appartiene \\ + \hline + -- & \textit{saved} & \textsl{user-ID salvato} + & è una copia dell'\acr{euid} iniziale\\ + -- & '' & \textsl{group-ID salvato} + & è una copia dell'\acr{egid} iniziale \\ + \hline + \acr{fsuid} & \textit{filesystem} &\textsl{user-ID di filesystem} + & indica l'utente effettivo per l'accesso al filesystem \\ + \acr{fsgid} & '' & \textsl{group-ID di filesystem} + & indica il gruppo effettivo per l'accesso al filesystem \\ + \hline + \end{tabular} + \caption{Identificatori di utente e gruppo associati a ciascun processo con + indicazione dei suffissi usati dalle varie funzioni di manipolazione.} + \label{tab:proc_uid_gid} +\end{table} + +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 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 +imposta questi identificatori ai valori corrispondenti all'utente che entra +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 nelle verifiche dei permessi del processo e per il +controllo di accesso ai file (argomento affrontato in dettaglio in +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 +sez.~\ref{sec:proc_exec}, il programma che si è posto in esecuzione abbia i bit +\acr{suid} o \acr{sgid} impostati (il significato di questi bit è affrontato +in dettaglio in 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: +\funcd{getuid}, \funcd{geteuid}, \funcd{getgid} e \funcd{getegid}, i loro +prototipi sono: +\begin{functions} + \headdecl{unistd.h} + \headdecl{sys/types.h} + \funcdecl{uid\_t getuid(void)} Restituisce l'\textsl{user-ID reale} del + processo corrente. + + \funcdecl{uid\_t geteuid(void)} Restituisce l'\textsl{user-ID effettivo} del + processo corrente. + + \funcdecl{gid\_t getgid(void)} Restituisce il \textsl{group-ID reale} del + processo corrente. + + \funcdecl{gid\_t getegid(void)} Restituisce il \textsl{group-ID effettivo} + del processo corrente. + + \bodydesc{Queste funzioni non riportano condizioni di errore.} +\end{functions} + +In generale l'uso di privilegi superiori deve essere limitato il più +possibile, per evitare abusi e problemi di sicurezza, per questo occorre anche +un meccanismo che consenta ad un programma di rilasciare gli eventuali +maggiori privilegi necessari, una volta che si siano effettuate le operazioni +per i quali erano richiesti, e a poterli eventualmente recuperare in caso +servano di nuovo. + +Questo in Linux viene fatto usando altri due gruppi di identificatori, il +\textit{saved} ed il \textit{filesystem}. Il primo gruppo è lo stesso usato in +SVr4, e previsto dallo standard POSIX quando è definita la costante +\macro{\_POSIX\_SAVED\_IDS},\footnote{in caso si abbia a cuore la portabilità + del programma su altri Unix è buona norma controllare sempre la + disponibilità di queste funzioni controllando se questa costante è + definita.} il secondo gruppo è specifico di Linux e viene usato per +migliorare la sicurezza con NFS. + +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. + +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 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 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 di gestione degli identificatori dei processi} +\label{sec:proc_setuid} + +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} + +\funcdecl{int setuid(uid\_t uid)} Imposta l'\textsl{user-ID} del processo +corrente. + +\funcdecl{int setgid(gid\_t gid)} Imposta il \textsl{group-ID} del processo +corrente. + +\bodydesc{Le funzioni restituiscono 0 in caso di successo e -1 in caso + di fallimento: l'unico errore possibile è \errval{EPERM}.} +\end{functions} + +Il funzionamento di queste due funzioni è analogo, per cui considereremo solo +la prima; la seconda si comporta esattamente allo stesso modo facendo +riferimento al \textsl{group-ID} invece che all'\textsl{user-ID}. Gli +eventuali \textsl{group-ID supplementari} non vengono modificati. + +L'effetto della chiamata è diverso a seconda dei privilegi del processo; se +l'\textsl{user-ID effettivo} è zero (cioè è quello dell'amministratore di +sistema) allora tutti gli identificatori (\textit{real}, \textit{effective} e +\textit{saved}) vengono impostati al valore specificato da \param{uid}, +altrimenti viene impostato solo l'\textsl{user-ID effettivo}, e soltanto se il +valore specificato corrisponde o all'\textsl{user-ID reale} o +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 +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 +registrato chi sta usando il sistema al momento corrente; chiaramente non può +essere lasciato aperto in scrittura a qualunque utente, che potrebbe +falsificare la registrazione. Per questo motivo questo file (e l'analogo +\file{/var/log/wtmp} su cui vengono registrati login e logout) appartengono ad +un gruppo dedicato (\acr{utmp}) ed i programmi che devono accedervi (ad +esempio tutti i programmi di terminale in X, o il programma \cmd{screen} che +crea terminali multipli su una console) appartengono a questo gruppo ed hanno +il bit \acr{sgid} impostato. + +Quando uno di questi programmi (ad esempio \cmd{xterm}) viene lanciato, la +situazione degli identificatori è la seguente: +\begin{eqnarray*} + \label{eq:1} + \textsl{group-ID reale} &=& \textrm{\acr{gid} (del chiamante)} \\ + \textsl{group-ID effettivo} &=& \textrm{\acr{utmp}} \\ + \textsl{group-ID salvato} &=& \textrm{\acr{utmp}} +\end{eqnarray*} +in questo modo, dato che il \textsl{group-ID effettivo} è quello giusto, il +programma può accedere a \file{/var/log/utmp} in scrittura ed aggiornarlo. A +questo punto il programma può eseguire una \code{setgid(getgid())} per +impostare il \textsl{group-ID effettivo} a quello dell'utente (e dato che il +\textsl{group-ID reale} corrisponde la funzione avrà successo), in questo modo +non sarà possibile lanciare dal terminale programmi che modificano detto file, +in tal caso infatti la situazione degli identificatori sarebbe: +\begin{eqnarray*} + \label{eq:2} + \textsl{group-ID reale} &=& \textrm{\acr{gid} (invariato)} \\ + \textsl{group-ID effettivo} &=& \textrm{\acr{gid}} \\ + \textsl{group-ID salvato} &=& \textrm{\acr{utmp} (invariato)} +\end{eqnarray*} +e ogni processo lanciato dal terminale avrebbe comunque \acr{gid} come +\textsl{group-ID effettivo}. All'uscita dal terminale, per poter di nuovo +aggiornare lo stato di \file{/var/log/utmp} il programma eseguirà una +\code{setgid(utmp)} (dove \var{utmp} è il valore numerico associato al gruppo +\acr{utmp}, ottenuto ad esempio con una precedente \func{getegid}), dato che +in questo caso il valore richiesto corrisponde al \textsl{group-ID salvato} la +funzione avrà successo e riporterà la situazione a: +\begin{eqnarray*} + \label{eq:3} + \textsl{group-ID reale} &=& \textrm{\acr{gid} (invariato)} \\ + \textsl{group-ID effettivo} &=& \textrm{\acr{utmp}} \\ + \textsl{group-ID salvato} &=& \textrm{\acr{utmp} (invariato)} +\end{eqnarray*} +consentendo l'accesso a \file{/var/log/utmp}. + +Occorre però tenere conto che tutto questo non è possibile con un processo con +i privilegi di amministratore, in tal caso infatti l'esecuzione di una +\func{setuid} comporta il cambiamento di tutti gli identificatori associati al +processo, rendendo impossibile riguadagnare i privilegi di amministratore. +Questo comportamento è corretto per l'uso che ne fa \cmd{login} una volta che +crea una nuova shell per l'utente; ma quando si vuole cambiare soltanto +l'\textsl{user-ID effettivo} del processo per cedere i privilegi occorre +ricorrere ad altre funzioni. + +Le due funzioni \funcd{setreuid} e \funcd{setregid} derivano da BSD che, non +supportando\footnote{almeno fino alla versione 4.3+BSD TODO, FIXME verificare + e aggiornare la nota.} gli identificatori del gruppo \textit{saved}, le usa +per poter scambiare fra di loro \textit{effective} e \textit{real}. I +rispettivi prototipi sono: +\begin{functions} +\headdecl{unistd.h} +\headdecl{sys/types.h} + +\funcdecl{int setreuid(uid\_t ruid, uid\_t euid)} Imposta l'\textsl{user-ID + reale} e l'\textsl{user-ID effettivo} del processo corrente ai valori +specificati da \param{ruid} e \param{euid}. + +\funcdecl{int setregid(gid\_t rgid, gid\_t egid)} Imposta il \textsl{group-ID + reale} ed il \textsl{group-ID effettivo} del processo corrente ai valori +specificati da \param{rgid} e \param{egid}. + +\bodydesc{Le funzioni restituiscono 0 in caso di successo e -1 in caso + di fallimento: l'unico errore possibile è \errval{EPERM}.} +\end{functions} + +La due funzioni sono analoghe ed il loro comportamento è identico; quanto +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 +quello visto in precedenza per \func{setgid}, cedendo i privilegi con un primo +scambio, e recuperandoli, eseguito il lavoro non privilegiato, con un secondo +scambio. + +In questo caso però occorre porre molta attenzione quando si creano nuovi +processi nella fase intermedia in cui si sono scambiati gli identificatori, in +questo caso infatti essi avranno un user-ID reale privilegiato, che dovrà +essere esplicitamente eliminato prima di porre in esecuzione un nuovo +programma (occorrerà cioè eseguire un'altra chiamata dopo la \func{fork} e +prima della \func{exec} per uniformare l'user-ID reale a quello effettivo) in +caso contrario il nuovo programma potrebbe a sua volta effettuare uno scambio +e riottenere privilegi non previsti. + +Lo stesso problema di propagazione dei privilegi ad eventuali processi figli +si pone per l'user-ID salvato: questa funzione deriva da un'implementazione che +non ne prevede la presenza, e quindi non è possibile usarla per correggere la +situazione come nel caso precedente. Per questo motivo in Linux tutte le volte +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. + +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} + +\funcdecl{int seteuid(uid\_t uid)} Imposta l'user-ID effettivo del processo +corrente a \param{uid}. + +\funcdecl{int setegid(gid\_t gid)} Imposta il group-ID effettivo del processo +corrente a \param{gid}. + +\bodydesc{Le funzioni restituiscono 0 in caso di successo e -1 in caso + di fallimento: l'unico errore è \errval{EPERM}.} +\end{functions} + +Come per le precedenti le due funzioni sono identiche, per cui tratteremo solo +la prima. Gli utenti normali possono impostare l'user-ID effettivo solo al +valore dell'user-ID reale o dell'user-ID salvato, l'amministratore può +specificare qualunque valore. Queste funzioni sono usate per permettere +all'amministratore di impostare solo l'user-ID effettivo, dato che l'uso +normale di \func{setuid} comporta l'impostazione di tutti gli identificatori. + + +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} + +\funcdecl{int setresuid(uid\_t ruid, uid\_t euid, uid\_t suid)} Imposta +l'user-ID reale, l'user-ID effettivo e l'user-ID salvato del processo corrente +ai valori specificati rispettivamente da \param{ruid}, \param{euid} e +\param{suid}. + +\funcdecl{int setresgid(gid\_t rgid, gid\_t egid, gid\_t sgid)} Imposta il +group-ID reale, il group-ID effettivo ed il group-ID salvato del processo +corrente ai valori specificati rispettivamente da \param{rgid}, \param{egid} e +\param{sgid}. + +\bodydesc{Le funzioni restituiscono 0 in caso di successo e -1 in caso + di fallimento: l'unico errore è \errval{EPERM}.} +\end{functions} + +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 argomento +lascia inalterato l'identificatore corrispondente. + +Per queste funzioni esistono anche due controparti che permettono di leggere +in blocco i vari identificatori: \funcd{getresuid} e \funcd{getresgid}; i loro +prototipi sono: +\begin{functions} +\headdecl{unistd.h} +\headdecl{sys/types.h} + +\funcdecl{int getresuid(uid\_t *ruid, uid\_t *euid, uid\_t *suid)} Legge +l'user-ID reale, l'user-ID effettivo e l'user-ID salvato del processo corrente. + +\funcdecl{int getresgid(gid\_t *rgid, gid\_t *egid, gid\_t *sgid)} Legge il +group-ID reale, il group-ID effettivo e il group-ID salvato del processo +corrente. + +\bodydesc{Le funzioni restituiscono 0 in caso di successo e -1 in caso di + fallimento: l'unico errore possibile è \errval{EFAULT} se gli indirizzi delle + variabili di ritorno non sono validi.} +\end{functions} + +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 +\index{\textit{value~result~argument}}\textit{value result argument}). Si noti +che queste funzioni sono le uniche in grado di leggere gli identificatori del +gruppo \textit{saved}. + + +Infine le funzioni \func{setfsuid} e \func{setfsgid} servono per impostare gli +identificatori del gruppo \textit{filesystem} che sono usati da Linux per il +controllo dell'accesso ai file. Come già accennato in +sez.~\ref{sec:proc_access_id} Linux definisce questo ulteriore gruppo di +identificatori, che in circostanze normali sono assolutamente equivalenti a +quelli del gruppo \textit{effective}, dato che ogni cambiamento di questi +ultimi viene immediatamente riportato su di essi. + +C'è un solo caso in cui si ha necessità di introdurre una differenza fra gli +identificatori dei gruppi \textit{effective} e \textit{filesystem}, ed è per +ovviare ad un problema di sicurezza che si presenta quando si deve +implementare un server NFS. + +Il server NFS infatti deve poter cambiare l'identificatore con cui accede ai +file per assumere l'identità del singolo utente remoto, ma se questo viene +fatto cambiando l'user-ID effettivo o l'user-ID reale il server si espone alla +ricezione di eventuali segnali ostili da parte dell'utente di cui ha +temporaneamente assunto l'identità. Cambiando solo l'user-ID di filesystem si +ottengono i privilegi necessari per accedere ai file, mantenendo quelli +originari per quanto riguarda tutti gli altri controlli di accesso, così che +l'utente non possa inviare segnali al server NFS. + +Le due funzioni usate per cambiare questi identificatori sono \funcd{setfsuid} +e \funcd{setfsgid}, ovviamente sono specifiche di Linux e non devono essere +usate se si intendono scrivere programmi portabili; i loro prototipi sono: +\begin{functions} +\headdecl{sys/fsuid.h} + +\funcdecl{int setfsuid(uid\_t fsuid)} Imposta l'user-ID di filesystem del +processo corrente a \param{fsuid}. + +\funcdecl{int setfsgid(gid\_t fsgid)} Imposta il group-ID di filesystem del +processo corrente a \param{fsgid}. + +\bodydesc{Le funzioni restituiscono 0 in caso di successo e -1 in caso + di fallimento: l'unico errore possibile è \errval{EPERM}.} +\end{functions} +\noindent queste funzioni hanno successo solo se il processo chiamante ha i +privilegi di amministratore o, per gli altri utenti, se il valore specificato +coincide con uno dei di quelli del gruppo \textit{real}, \textit{effective} o +\textit{saved}. + + +\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 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} + + \funcdecl{int getgroups(int size, gid\_t list[])} + + Legge gli identificatori dei gruppi supplementari. + + \bodydesc{La funzione restituisce il numero di gruppi letti in caso di + successo e -1 in caso di fallimento, nel qual caso \var{errno} assumerà + i valori: + \begin{errlist} + \item[\errcode{EFAULT}] \param{list} non ha un indirizzo valido. + \item[\errcode{EINVAL}] il valore di \param{size} è diverso da zero ma + minore del numero di gruppi supplementari del processo. + \end{errlist}} +\end{functions} + +La funzione legge gli identificatori dei gruppi supplementari del processo sul +vettore \param{list} di dimensione \param{size}. Non è specificato se la +funzione inserisca o meno nella lista il group-ID effettivo del processo. Se si +specifica un valore di \param{size} uguale a 0 \param{list} non viene +modificato, ma si ottiene il numero di gruppi supplementari. + +Una seconda funzione, \funcd{getgrouplist}, può invece essere usata per +ottenere tutti i gruppi a cui appartiene un certo utente; il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{grp.h} + + \funcdecl{int getgrouplist(const char *user, gid\_t group, gid\_t *groups, + int *ngroups)} Legge i gruppi supplementari. + + \bodydesc{La funzione legge fino ad un massimo di \param{ngroups} valori, + restituisce 0 in caso di successo e -1 in caso di fallimento.} +\end{functions} + +La funzione legge i gruppi supplementari dell'utente specificato da +\param{user}, eseguendo una scansione del database dei gruppi (si veda +sez.~\ref{sec:sys_user_group}). Ritorna poi in \param{groups} la lista di +quelli a cui l'utente appartiene. Si noti che \param{ngroups} è passato come +puntatore perché, qualora il valore specificato sia troppo piccolo, la +funzione ritorna -1, passando indietro il numero dei gruppi trovati. +Per impostare i gruppi supplementari di un processo ci sono due funzioni, che +possono essere usate solo se si hanno i privilegi di amministratore. La prima +delle due è \funcd{setgroups}, ed il suo prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{grp.h} + + \funcdecl{int setgroups(size\_t size, gid\_t *list)} + + Imposta i gruppi supplementari del processo. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + fallimento, nel qual caso \var{errno} assumerà i valori: + \begin{errlist} + \item[\errcode{EFAULT}] \param{list} non ha un indirizzo valido. + \item[\errcode{EPERM}] il processo non ha i privilegi di amministratore. + \item[\errcode{EINVAL}] il valore di \param{size} è maggiore del valore + massimo consentito. + \end{errlist}} +\end{functions} -\subsection{Utente e gruppo di un processo} -\label{sec:proc_user_group} +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 +sez.~\ref{sec:sys_characteristics}. -Abbiamo già accennato in \secref{sec:intro_multiuser} ad ogni utente ed gruppo -sono associati due identificatori univoci, lo \acr{uid} e il \acr{gid} che li -contraddistinguono nei confronti del kernel. Questi identificatori stanno alla -base del sistema di permessi e protezioni di un sistema unix, e vengono usati -anche nella gestione dei privilegi di accesso dei processi. +Se invece si vogliono impostare i gruppi supplementari del processo a quelli di +un utente specifico, si può usare \funcd{initgroups} il cui prototipo è: +\begin{functions} + \headdecl{sys/types.h} + \headdecl{grp.h} + + \funcdecl{int initgroups(const char *user, gid\_t group)} + + Inizializza la lista dei gruppi supplementari. + + \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di + fallimento, nel qual caso \var{errno} assumerà gli stessi valori di + \func{setgroups} più \errval{ENOMEM} quando non c'è memoria sufficiente + per allocare lo spazio per informazioni dei gruppi.} +\end{functions} + +La funzione esegue la scansione del database dei gruppi (usualmente +\file{/etc/groups}) cercando i gruppi di cui è membro l'utente \param{user} +con cui costruisce una lista di gruppi supplementari, a cui aggiunge anche +\param{group}, infine imposta questa lista per il processo corrente usando +\func{setgroups}. Si tenga presente che sia \func{setgroups} che +\func{initgroups} non sono definite nello standard POSIX.1 e che pertanto non +è possibile utilizzarle quando si definisce \macro{\_POSIX\_SOURCE} o si +compila con il flag \cmd{-ansi}, è pertanto meglio evitarle se si vuole +scrivere codice portabile. + + +%\subsection{La gestione delle capabilities} +%\label{sec:proc_capabilities} + + + + +\section{La gestione della priorità di esecuzione} +\label{sec:proc_priority} + +In questa sezione tratteremo più approfonditamente i meccanismi con il quale +lo \textit{scheduler}\index{\textit{scheduler}} assegna la CPU ai vari +processi attivi. In particolare prenderemo in esame i vari meccanismi con cui +viene gestita l'assegnazione del tempo di CPU, ed illustreremo le varie +funzioni di gestione. + + +\subsection{I meccanismi di \textit{scheduling}} +\label{sec:proc_sched} + +La scelta di un meccanismo che sia in grado di distribuire in maniera efficace +il tempo di CPU per l'esecuzione dei processi è sempre una questione delicata, +ed oggetto di numerose ricerche; in generale essa dipende in maniera +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 + 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 +sez.~\ref{sec:proc_hierarchy} questa scelta viene eseguita da una sezione +apposita del kernel, lo \textit{scheduler}\index{\textit{scheduler}}, il cui +scopo è quello di distribuire al meglio il tempo di CPU fra i vari processi. + +La cosa è resa ancora più complicata dal fatto che con le architetture +multi-processore si deve anche scegliere quale sia la CPU più opportuna da +utilizzare.\footnote{nei processori moderni la presenza di ampie cache può + rendere poco efficiente trasferire l'esecuzione di un processo da una CPU ad + un'altra, per cui effettuare la migliore scelta fra le diverse CPU non è + banale.} Tutto questo comunque appartiene alle sottigliezze +dell'implementazione del kernel; dal punto di vista dei programmi che girano +in user space, anche quando si hanno più processori (e dei processi che sono +eseguiti davvero in contemporanea), le politiche di scheduling riguardano +semplicemente l'allocazione della risorsa \textsl{tempo di esecuzione}, la cui +assegnazione sarà governata dai meccanismi di scelta delle priorità che +restano gli stessi indipendentemente dal numero di processori. + +Si tenga conto poi che i processi non devono solo eseguire del codice: ad +esempio molto spesso saranno impegnati in operazioni di I/O, o potranno +venire bloccati da un comando dal terminale, o sospesi per un certo periodo di +tempo. In tutti questi casi la CPU diventa disponibile ed è compito dello +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 +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. -In realtà ad ogni processo è associato un certo numero di identificatori, il -cui elenco è riportato \ntab, in genere questi derivano direttamente -dall'utente che ha lanciato il processo (attraverso i valori di \acr{uid} e -\acr{gid}), e vengono usati sia per il controllo di accesso ai file che per la -gestione dei privilegi associati ai processi stessi. \begin{table}[htb] \footnotesize \centering - \begin{tabular}[c]{|c|l|p{8cm}|} + \begin{tabular}[c]{|p{2.8cm}|c|p{10cm}|} \hline - \textbf{Sigla} & \textbf{Significato} & \textbf{Utilizzo} \\ + \textbf{Stato} & \texttt{STAT} & \textbf{Descrizione} \\ \hline \hline - \acr{ruid} & \textit{real user id} & indica l'utente reale che ha lanciato - il programma\\ - \acr{rgid} & \textit{real group id} & indica il gruppo reale dell'utente - che ha lanciato il programma \\ - \acr{euid} & \textit{effective user id} & indica l'utente effettivo usato - dal programma \\ - \acr{egid} & \textit{effective group id} & indica il gruppo effettivo usato - dal programma \\ - & \textit{supplementary group id} & indica i gruppi cui - l'utente appartiene \\ - \acr{suid} & \textit{saved user id} & indica l'utente \\ - \acr{sgid} & \textit{daved group id} & indica il gruppo \\ - \acr{fsuid} & \textit{filesystem user id} & indica l'utente effettivo per - il filesystem \\ - \acr{fsgid} & \textit{filesystem group id} & indica il gruppo effettivo - per il filesystem \\ + \textbf{Runnable}& \texttt{R} & Il processo è in esecuzione o è pronto ad + essere eseguito (cioè è in attesa che gli + venga assegnata la CPU). \\ + \textbf{Sleep} & \texttt{S} & Il processo è in attesa di un + risposta dal sistema, ma può essere + interrotto da un segnale. \\ + \textbf{Uninterrutible Sleep}& \texttt{D} & Il processo è in + attesa di un risposta dal sistema (in + genere per I/O), e non può essere + interrotto in nessuna circostanza. \\ + \textbf{Stopped} & \texttt{T} & Il processo è stato fermato con un + \const{SIGSTOP}, o è tracciato.\\ + \textbf{Zombie}\index{zombie} & \texttt{Z} & Il processo è terminato ma il + suo stato di terminazione non è ancora + stato letto dal padre. \\ \hline \end{tabular} - \caption{Identificatori di utente e gruppo associati a ciascun processo.} - \label{tab:proc_uid_gid} + \caption{Elenco dei possibili stati di un processo in Linux, nella colonna + \texttt{STAT} si è riportata la corrispondente lettera usata dal comando + \cmd{ps} nell'omonimo campo.} + \label{tab:proc_proc_states} \end{table} -Il \textit{real user id} e il \textit{real group id} indicano l'utente che ha -lanciato il processo, e vengono settati al login al valore standard di -\acr{uid} e \acr{gid} dell'utente letti direttamente da \file{/etc/passwd}. -Questi non vengono mai cambiati nella creazione di nuovi processi e restano -sempre gli stessi per tutti i processi avviati in una sessione. In realtà è -possibile modificarli (vedi \secref{sec:proc_setuid}), ma solo per un processo -che abbia i privilegi di amministratore (ed è così infatti che \cmd{login}, -che gira con i privilegi di amministratore, li setta ai valori corrispondenti -all'utente che entra nel sistema). - -L'\textit{effective user id}, l'\textit{effective group id} e gli eventuali -\textit{supplementary group id} sono gli identificativi usati per il controllo -di accesso ai file secondo quanto descritto in dettaglio in -\secref{sec:file_perm_overview}. Normalmente sono uguali al \textit{real user - id} e al \textit{real group id}, a meno che il file posto in esecuzione non -abbia i bit \acr{suid} o \acr{sgid} settati, nel qual caso vengono settati -rispettivamente all'\acr{uid} e \acr{gid} del file. - -Il \textit{saved user id} e il \textit{saved group id} sono copie -dell'\textit{effective user id} e dell'\textit{effective group id} del -processo padre, e vengono settati all'avvio del processo, prima che -\textit{effective user id} e \textit{effective group id} vengano modificati -per tener conto di eventuali \acr{suid} o \acr{sgid}. - - -\subsection{Le funzioni \texttt{setuid} e \texttt{setgid}} -\label{sec:proc_setuid} +Si deve quindi tenere presente che l'utilizzo della CPU è soltanto una delle +risorse che sono necessarie per l'esecuzione di un programma, e a seconda +dello scopo del programma non è detto neanche che sia la più importante (molti +programmi dipendono in maniera molto più critica dall'I/O). Per questo motivo +non è affatto detto che dare ad un programma la massima priorità di esecuzione +abbia risultati significativi in termini di prestazioni. + +Il meccanismo tradizionale di scheduling di Unix (che tratteremo in +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 +alla normale priorità dinamica), per tenere conto dei sistemi +real-time,\footnote{per sistema real-time si intende un sistema in grado di + eseguire operazioni in un tempo ben determinato; in genere si tende a + distinguere fra l'\textit{hard real-time} in cui è necessario che i tempi di + esecuzione di un programma siano determinabili con certezza assoluta (come + nel caso di meccanismi di controllo di macchine, dove uno sforamento dei + tempi avrebbe conseguenze disastrose), e \textit{soft-real-time} in cui un + occasionale sforamento è ritenuto accettabile.} in cui è vitale che i +processi che devono essere eseguiti in un determinato momento non debbano +aspettare la conclusione di altri che non hanno questa necessità. + +Il concetto di priorità assoluta dice che quando due processi si contendono +l'esecuzione, vince sempre quello con la priorità assoluta più alta. +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 +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 +assegnazione della CPU è fatta solo con il meccanismo tradizionale della +priorità dinamica. In Linux tuttavia è possibile assegnare anche una priorità +assoluta, nel qual caso un processo avrà la precedenza su tutti gli altri di +priorità inferiore, che saranno eseguiti solo quando quest'ultimo non avrà +bisogno della CPU. + + +\subsection{Il meccanismo di \textit{scheduling} standard} +\label{sec:proc_sched_stand} + +A meno che non si abbiano esigenze specifiche, l'unico meccanismo di +scheduling con il quale si avrà a che fare è quello tradizionale, che prevede +solo priorità dinamiche. È di questo che, di norma, ci si dovrà preoccupare +nella programmazione. + +Come accennato in Linux tutti i processi ordinari hanno la stessa priorità +assoluta. Quello che determina quale, fra tutti i processi in attesa di +esecuzione, sarà eseguito per primo, è la priorità dinamica, che è chiamata +così proprio perché varia nel corso dell'esecuzione di un processo. Oltre a +questo la priorità dinamica determina quanto a lungo un processo continuerà ad +essere eseguito, e quando un processo potrà subentrare ad un altro +nell'esecuzione. + +Il meccanismo usato da Linux è piuttosto semplice,\footnote{in realtà nella + serie 2.6.x lo \textit{scheduler} è stato riscritto da zero e può usare + diversi algoritmi, selezionabili sia in fase di compilazione, che, nelle + versioni più recenti, all'avvio (addirittura è stato ideato un sistema + modulare che permette di cambiare lo scheduler al volo, che comunque non è + incluso nel kernel ufficiale).} ad ogni processo è assegnata una +\textit{time-slice}, cioè un intervallo di tempo (letteralmente una fetta) per +il quale esso deve essere eseguito. Il valore della \textit{time-slice} è +controllato dalla cosiddetta \textit{nice} (o \textit{niceness}) del processo. +Essa è contenuta nel campo \var{nice} di \struct{task\_struct}; tutti i +processi vengono creati con lo stesso valore, ed essa specifica il valore +della durata iniziale della \textit{time-slice} che viene assegnato ad un +altro campo della struttura (\var{counter}) quando il processo viene eseguito +per la prima volta e diminuito progressivamente ad ogni interruzione del +timer. + +Durante la sua esecuzione lo scheduler\index{\textit{scheduler}} scandisce la +coda dei processi in stato \textit{runnable} associando, in base al valore di +\var{counter}, un peso ad ogni processo in attesa di esecuzione,\footnote{il + calcolo del peso in realtà è un po' più complicato, ad esempio nei sistemi + multiprocessore viene favorito un processo 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 +meccanismo appena descritto infatti un valore più lungo assicura una maggiore +attribuzione di CPU. L'origine del nome di questo parametro sta nel fatto che +generalmente questo viene usato per diminuire la priorità di un processo, come +misura di cortesia nei confronti degli altri. I processi infatti vengono +creati dal sistema con lo stesso valore di \var{nice} (nullo) e nessuno è +privilegiato rispetto agli altri; il valore può essere modificato solo +attraverso la funzione \funcd{nice}, il cui prototipo è: +\begin{prototype}{unistd.h} +{int nice(int inc)} + Aumenta il valore di \var{nice} per il processo corrente. + + \bodydesc{La funzione ritorna zero in caso di successo e -1 in caso di + errore, nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{EPERM}] un processo senza i privilegi di amministratore ha + specificato un valore di \param{inc} negativo. + \end{errlist}} +\end{prototype} +L'argomento \param{inc} indica l'incremento del valore di \var{nice}: +quest'ultimo può assumere valori compresi fra \const{PRIO\_MIN} e +\const{PRIO\_MAX} (che nel caso di Linux sono $-19$ e $20$), ma per +\param{inc} si può specificare un valore qualunque, positivo o negativo, ed il +sistema provvederà a troncare il risultato nell'intervallo consentito. Valori +positivi comportano maggiore \textit{cortesia} e cioè una diminuzione della +priorità, ogni utente può solo innalzare il valore di un suo processo. Solo +l'amministratore può specificare valori negativi che permettono di aumentare +la priorità di un processo. + +In SUSv2 la funzione ritorna il nuovo valore di \var{nice}; Linux non segue +questa convenzione, e per leggere il nuovo valore occorre invece usare la +funzione \funcd{getpriority}, derivata da BSD, il cui prototipo è: +\begin{prototype}{sys/resource.h} +{int getpriority(int which, int who)} + +Restituisce il valore di \var{nice} per l'insieme dei processi specificati. -\subsection{Le funzioni \texttt{seteuid} e \texttt{setegid}} -\label{sec:proc_seteuid} + \bodydesc{La funzione ritorna la priorità in caso di successo e -1 in caso di + errore, nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] non c'è nessun processo che corrisponda ai valori di + \param{which} e \param{who}. + \item[\errcode{EINVAL}] il valore di \param{which} non è valido. + \end{errlist}} +\end{prototype} +\noindent nelle vecchie versioni può essere necessario includere anche +\file{}, questo non è più necessario con versioni recenti delle +librerie, ma è comunque utile per portabilità. +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. -\subsection{Le \textit{race condition}} -\label{sec:proc_race_cond} +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|c|c|l|} + \hline + \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 \\ + \hline + \end{tabular} + \caption{Legenda del valore dell'argomento \param{which} e del tipo + dell'argomento \param{who} delle funzioni \func{getpriority} e + \func{setpriority} per le tre possibili scelte.} + \label{tab:proc_getpriority} +\end{table} + +La funzione restituisce la priorità più alta (cioè il valore più basso) fra +quelle dei processi specificati; dato che -1 è un valore possibile, per poter +rilevare una condizione di errore è necessario cancellare sempre \var{errno} +prima della chiamata alla funzione, per verificare che essa resti uguale a +zero. -Si definisce una \textit{race condition} il caso in cui diversi processi -stanno cercando di fare qualcosa con una risorsa comune ed il risultato finale -viene a dipendere dall'ordine di esecuzione dei medesimi. Ovviamente dato che -l'ordine di esecuzione di un processo, senza appositi meccanismi di -sincronizzazione, non è assolutamente prevedibile, queste situazioni sono -fonti di errori molto subdoli, che possono verificarsi solo in condizioni -particolari e quindi difficilmente riproducibili. +Analoga a \func{getpriority} la funzione \funcd{setpriority} permette di +impostare la priorità di uno o più processi; il suo prototipo è: +\begin{prototype}{sys/resource.h} +{int setpriority(int which, int who, int prio)} + Imposta la priorità per l'insieme dei processi specificati. + + \bodydesc{La funzione ritorna la priorità in caso di successo e -1 in caso di + errore, nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] non c'è nessun processo che corrisponda ai valori di + \param{which} e \param{who}. + \item[\errcode{EINVAL}] il valore di \param{which} non è valido. + \item[\errcode{EPERM}] un processo senza i privilegi di amministratore ha + specificato un valore di \param{inc} negativo. + \item[\errcode{EACCES}] un processo senza i privilegi di amministratore ha + cercato di modificare la priorità di un processo di un altro utente. + \end{errlist}} +\end{prototype} + +La funzione imposta la priorità al valore specificato da \param{prio} per +tutti i processi indicati dagli argomenti \param{which} e \param{who}. La +gestione dei permessi dipende dalle varie implementazioni; in Linux, secondo +le specifiche dello standard SUSv3, e come avviene per tutti i sistemi che +derivano da SysV, è richiesto che l'user-ID reale o effettivo del processo +chiamante corrispondano al real user-ID (e solo quello) del processo di cui si +vuole cambiare la priorità; per i sistemi derivati da BSD invece (SunOS, +Ultrix, *BSD) la corrispondenza può essere anche con l'user-ID effettivo. + + + +\subsection{Il meccanismo di \textit{scheduling real-time}} +\label{sec:proc_real_time} + +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 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{\textit{page~fault}} si possono avere ritardi non previsti. +Se l'ultimo problema può essere aggirato attraverso l'uso delle funzioni di +controllo della memoria virtuale (vedi sez.~\ref{sec:proc_mem_lock}), il primo +non è superabile e può comportare ritardi non prevedibili riguardo ai tempi di +esecuzione di qualunque processo. + +Occorre usare le priorità assolute con molta attenzione: se si dà ad un +processo una priorità assoluta e questo finisce in un loop infinito, nessun +altro processo potrà essere eseguito, ed esso sarà mantenuto in esecuzione +permanentemente assorbendo tutta la CPU e senza nessuna possibilità di +riottenere l'accesso al sistema. Per questo motivo è sempre opportuno, quando +si lavora con processi che usano priorità assolute, tenere attiva una shell +cui si sia assegnata la massima priorità assoluta, in modo da poter essere +comunque in grado di rientrare nel sistema. + +Quando c'è un processo con priorità assoluta lo +scheduler\index{\textit{scheduler}} lo metterà in esecuzione prima di ogni +processo normale. In caso di più processi sarà eseguito per primo quello con +priorità assoluta più alta. Quando ci sono più processi con la stessa priorità +assoluta questi vengono tenuti in una coda e tocca al kernel decidere quale +deve essere eseguito. Il meccanismo con cui vengono gestiti questi processi +dipende dalla politica di scheduling che si è scelto; lo standard ne prevede +due: +\begin{basedescript}{\desclabelwidth{1.2cm}\desclabelstyle{\nextlinelabel}} +\item[\textit{FIFO}] \textit{First In First Out}. Il processo viene eseguito + fintanto che non cede volontariamente la CPU (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 +ordinarie) ed i relativi parametri è \funcd{sched\_setscheduler}; il suo +prototipo è: +\begin{prototype}{sched.h} +{int sched\_setscheduler(pid\_t pid, int policy, const struct sched\_param *p)} + Imposta priorità e politica di scheduling. + + \bodydesc{La funzione ritorna la priorità 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{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. + \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. +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]{|l|l|} + \hline + \textbf{Policy} & \textbf{Significato} \\ + \hline + \hline + \const{SCHED\_FIFO} & Scheduling real-time con politica \textit{FIFO} \\ + \const{SCHED\_RR} & Scheduling real-time con politica \textit{Round + Robin} \\ + \const{SCHED\_OTHER}& Scheduling ordinario\\ + \hline + \end{tabular} + \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 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 +nullo è legale, ma indica i processi normali. + +\begin{figure}[!bht] + \footnotesize \centering + \begin{minipage}[c]{15cm} + \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 \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} + + \funcdecl{int sched\_get\_priority\_max(int policy)} Legge il valore + massimo della priorità statica per la politica di scheduling \param{policy}. + + + \funcdecl{int sched\_get\_priority\_min(int policy)} Legge il valore minimo + della priorità statica per la politica di scheduling \param{policy}. + + \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} non è valido. + \end{errlist}} +\end{functions} + + +I processi con politica di scheduling \const{SCHED\_OTHER} devono specificare +un valore nullo (altrimenti si avrà un errore \errcode{EINVAL}), questo valore +infatti non ha niente a che vedere con la priorità dinamica determinata dal +valore di \var{nice}, che deve essere impostato con le funzioni viste in +precedenza. + +Il kernel mantiene i processi con la stessa priorità assoluta in una lista, ed +esegue sempre il primo della lista, mentre un nuovo processo che torna in +stato \textit{runnable} viene sempre inserito in coda alla lista. Se la +politica scelta è \const{SCHED\_FIFO} quando il processo viene eseguito viene +automaticamente rimesso in coda alla lista, e la sua esecuzione continua +fintanto che non viene bloccato da una richiesta di I/O, o non rilascia +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. + +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: +\begin{functions} + \headdecl{sched.h} + + \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}. + + \bodydesc{La funzione ritorna la priorità 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{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} + +L'uso di \func{sched\_setparam} che è del tutto equivalente a +\func{sched\_setscheduler} con \param{priority} uguale a -1. Come per +\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}. + +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 +lunghezza della \textit{time slice} usata dalla politica \textit{round robin}; +il suo prototipo è: +\begin{prototype}{sched.h} + {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 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{ENOSYS}] la system call non è stata implementata. + \end{errlist}} +\end{prototype} + +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 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 +volontariamente la CPU; questo viene fatto attraverso la funzione +\funcd{sched\_yield}, il cui prototipo è: +\begin{prototype}{sched.h} + {int sched\_yield(void)} + + Rilascia volontariamente l'esecuzione. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} viene impostata opportunamente.} +\end{prototype} + +La funzione fa sì che il processo rilasci la CPU, in modo da essere rimesso in +coda alla lista dei processi da eseguire, e permettere l'esecuzione di un +altro processo; se però il processo è l'unico ad essere presente sulla coda +l'esecuzione non sarà interrotta. In genere usano questa funzione i processi +in modalità \textit{fifo}, per permettere l'esecuzione degli altri processi +con pari priorità quando la sezione più urgente è finita. + +Infine con il supporto dei sistemi multiprocessore sono state introdotte delle +funzioni che permettono di controllare in maniera più dettagliata la scelta di +quale processore utilizzare per eseguire un certo programma. Uno dei problemi +che si pongono nei sistemi multiprocessore è infatti quello +dell'\textsl{effetto ping-pong}.\index{\textsl{effetto ping-pong}} Può +accadere cioè che lo scheduler, quando riavvia un processo precedentemente +interrotto, scegliendo il primo processore disponibile lo faccia eseguire da +un processore diverso rispetto a quello su cui era stato eseguito in +precedenza. Se il processo passa da un processore all'altro in questo modo +(cosa che avveniva abbastanza di frequente con i kernel della seria 2.4.x) si +ha l'\textsl{effetto ping-pong}. + +Questo tipo di comportamento può generare dei seri problemi di prestazioni; +infatti tutti i processori moderni utilizzano una memoria interna (la +\textit{cache}) contenente i dati più usati, che permette di evitare di +eseguire un accesso (molto più lento) alla memoria principale sulla scheda +madre. Chiaramente un processo sarà favorito se i suoi dati sono nella cache +del processore, ma è ovvio che questo può essere vero solo per un processore +alla volta, perché in presenza di più copie degli stessi dati su più +processori, non si potrebbe determinare quale di questi ha la versione dei +dati aggiornata rispetto alla memoria principale. + +Questo comporta che quando un processore inserisce un dato nella sua cache, +tutti gli altri processori che hanno lo stesso dato devono invalidarlo, e +questa operazione è molto costosa in termini di prestazioni. Il problema +diventa serio quando si verifica l'\textsl{effetto ping-pong}, in tal caso +infatti un processo \textsl{rimbalza} continuamente da un processore all'altro +e si ha una continua invalidazione della cache, che non diventa mai +disponibile. + +Per ovviare a questo tipo di problemi è nato il concetto di \textsl{affinità + di processore} (o \index{\textit{CPU~affinity}}\textit{CPU affinity}); la +possibilità cioè di far sì che un processo possa essere assegnato per +l'esecuzione sempre allo stesso processore. Lo scheduler dei kernel della +serie 2.4.x aveva una scarsa \textit{CPU affinity}, e l'effetto ping-pong era +comune; con il nuovo scheduler dei kernel della 2.6.x questo problema è stato +risolto ed esso cerca di mantenere il più possibile ciascun processo sullo +stesso processore. + +In certi casi però resta l'esigenza di poter essere sicuri che un processo sia +sempre eseguito dallo stesso processore,\footnote{quella che viene detta + \textit{hard CPU affinity}, in contrasto con quella fornita dallo scheduler, + detta \textit{soft CPU affinity}, che di norma indica solo una preferenza, + non un requisito assoluto.} e per poter risolvere questo tipo di +problematiche nei nuovi kernel\footnote{le due system call per la gestione + della \textit{CPU affinity} sono state introdotte nel kernel 2.5.8, e le + funzioni di libreria nelle \textsl{glibc} 2.3.} è stata introdotta +l'opportuna infrastruttura ed una nuova system call che permette di impostare +su quali processori far eseguire un determinato processo attraverso una +\textsl{maschera di affinità}. La corrispondente funzione di libreria è +\funcd{sched\_setaffinity} ed il suo prototipo\footnote{di questa funzione (e + della corrispondente \func{sched\_setaffinity}) esistono versioni diverse + per gli argomenti successivi a \param{pid}: la prima (quella riportata nella + pagina di manuale) prevedeva due ulteriori argomenti di tipo + \texttt{unsigned int len} e \texttt{unsigned long *mask}, poi l'argomento + \texttt{len} è stato eliminato, successivamente si è introdotta la versione + riportata con però un secondo argomento di tipo \texttt{size\_t cpusetsize} + (anche questa citata nella pagina di manuale); la versione citata è quella + riportata nel manuale delle \textsl{glibc} e corripondente alla definizione + presente in \file{sched.h}.} è: +\begin{prototype}{sched.h} + {int sched\_setaffinity (pid\_t pid, const cpu\_set\_t *cpuset)} + Imposta la maschera di affinità del processo \param{pid}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EINVAL}] il valore di \param{cpuset} contiene riferimenti a + processori non esistenti nel sistema. + \item[\errcode{EPERM}] il processo non ha i privilegi sufficienti per + eseguire l'operazione. + \end{errlist} + ed inoltre anche \errval{EFAULT}.} +\end{prototype} + +La funzione imposta, con l'uso del valore contenuto all'indirizzo +\param{cpuset}, l'insieme dei processori sui quali deve essere eseguito il +processo identificato tramite il valore passato in \param{pid}. Come in +precedenza il valore nullo di \param{pid} indica il processo corrente. Per +poter utilizzare questa funzione sono richiesti i privilegi di amministratore +(è necessaria la capacità \const{CAP\_SYS\_NICE}) altrimenti essa fallirà con +un errore di \errcode{EPERM}. Una volta impostata una maschera di affinità, +questa viene ereditata attraverso una \func{fork}, in questo modo diventa +possibile legare automaticamente un gruppo di processi ad un singolo +processore. + +Nell'uso comune, almeno con i kernel della serie 2.6.x, l'uso di questa +funzione non è necessario, in quanto è lo scheduler stesso che provvede a +mantenere al meglio l'affinità di processore. Esistono però esigenze +particolari, ad esempio quando un processo (o un gruppo di processi) è +utilizzato per un compito importante (ad esempio per applicazioni real-time o +la cui risposta è critica) e si vuole la massima velocità, con questa +interfaccia diventa possibile selezionare gruppi di processori utilizzabili in +maniera esclusiva. Lo stesso dicasi quando l'accesso a certe risorse (memoria +o periferiche) può avere un costo diverso a seconda del processore (come +avviene nelle architetture NUMA). + +Infine se un gruppo di processi accede alle stesse risorse condivise (ad +esempio una applicazione con più thread) può avere senso usare lo stesso +processore in modo da sfruttare meglio l'uso della sua cache; questo +ovviamente riduce i benefici di un sistema multiprocessore nell'esecuzione +contemporanea dei thread, ma in certi casi (quando i thread sono inerentemente +serializzati nell'accesso ad una risorsa) possono esserci sufficienti vantaggi +nell'evitare la perdita della cache da rendere conveniente l'uso dell'affinità +di processore. + +Per facilitare l'uso dell'argomento \param{cpuset} le \acr{glibc} hanno +introdotto un apposito dato di tipo, \ctyp{cpu\_set\_t},\footnote{questa è una + estensione specifica delle \acr{glibc}, da attivare definendo la macro + \macro{\_GNU\_SOURCE}, non esiste infatti una standardardizzazione per + questo tipo di interfaccia e POSIX al momento non prevede nulla al + riguardo.} che permette di identificare un insieme di processori. Il dato è +una maschera binaria: in generale è un intero a 32 bit in cui ogni bit +corrisponde ad un processore, ma dato che per architetture particolari il +numero di bit di un intero può non essere sufficiente, è stata creata questa +che è una interfaccia generica che permette di usare a basso livello un tipo +di dato qualunque rendendosi indipendenti dal numero di bit e dalla loro +disposizione. + +Questa interfaccia, oltre alla definizione del tipo di dato apposito, prevede +anche una serie di macro di preprocessore per la manipolazione dello stesso, +che consentono di svuotare un insieme, aggiungere o togliere un processore da +esso o verificare se vi è già presente: +\begin{functions} + \headdecl{sched.h} + \funcdecl{void \macro{CPU\_ZERO}(cpu\_set\_t *set)} + Inizializza l'insieme (vuoto). + + \funcdecl{void \macro{CPU\_SET}(int cpu, cpu\_set\_t *set)} + Inserisce il processore \param{cpu} nell'insieme. + + \funcdecl{void \macro{CPU\_CLR}(int cpu, cpu\_set\_t *set)} + Rimuove il processore \param{cpu} nell'insieme. + + \funcdecl{int \macro{CPU\_ISSET}(int cpu, cpu\_set\_t *set)} + Controlla se il processore \param{cpu} è nell'insieme. +\end{functions} + +Oltre a queste macro, simili alle analoghe usate per gli insiemi di file +descriptor (vedi sez.~\ref{sec:file_select}) è definita la costante +\const{CPU\_SETSIZE} che indica il numero massimo di processori che possono +far parte dell'insieme, e che costituisce un limite massimo al valore +dell'argomento \param{cpu}. + +In generale la maschera di affinità è preimpostata in modo che un processo +possa essere eseguito su qualunque processore, se può comunque leggere il +valore per un processo specifico usando la funzione +\funcd{sched\_getaffinity}, il suo prototipo è: +\begin{prototype}{sched.h} + {int sched\_getaffinity (pid\_t pid, const cpu\_set\_t *cpuset)} + Legge la maschera di affinità del processo \param{pid}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EFAULT}] il valore di \param{cpuset} non è un indirizzo + valido. + \end{errlist} } +\end{prototype} + +La funzione restituirà all'indirizzo specificato da \param{cpuset} il valore +della maschera di affinità del processo, così da poterla riutilizzare per una +successiva reimpostazione. In questo caso non sono necessari privilegi +paricolari. + +È chiaro che queste funzioni per la gestione dell'affinità hanno significato +soltanto su un sistema multiprocessore, esse possono comunque essere +utilizzate anche in un sistema con un processore singolo, nel qual caso però +non avranno alcun risultato effettivo. + + +\section{Problematiche di programmazione multitasking} +\label{sec:proc_multi_prog} + +Benché i processi siano strutturati in modo da apparire il più possibile come +indipendenti l'uno dall'altro, nella programmazione in un sistema multitasking +occorre tenere conto di una serie di problematiche che normalmente non +esistono quando si ha a che fare con un sistema in cui viene eseguito un solo +programma alla volta. + +Pur essendo questo argomento di carattere generale, ci è parso opportuno +introdurre sinteticamente queste problematiche, che ritroveremo a più riprese +in capitoli successivi, in questa sezione conclusiva del capitolo in cui +abbiamo affrontato la gestione dei processi. + + +\subsection{Le operazioni atomiche} +\label{sec:proc_atom_oper} + +La nozione di \textsl{operazione atomica} deriva dal significato greco della +parola atomo, cioè indivisibile; si dice infatti che un'operazione è atomica +quando si ha la certezza che, qualora essa venga effettuata, tutti i passaggi +che devono essere compiuti per realizzarla verranno eseguiti senza possibilità +di interruzione in una fase intermedia. + +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{\textit{race~condition}} (vedi +sez.~\ref{sec:proc_race_cond}) derivanti da operazioni interrotte in una fase +in cui non erano ancora state completate. + +Nel caso dell'interazione fra processi la situazione è molto più semplice, ed +occorre preoccuparsi della atomicità delle operazioni solo quando si ha a che +fare con meccanismi di intercomunicazione (che esamineremo in dettaglio in +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 +processi. + +Nel caso dei segnali invece la situazione è molto più delicata, in quanto lo +stesso processo, e pure alcune system call, possono essere interrotti in +qualunque momento, e le operazioni di un eventuale \textit{signal handler} +sono compiute nello stesso spazio di indirizzi del processo. Per questo, anche +il solo accesso o l'assegnazione di una variabile possono non essere più +operazioni atomiche (torneremo su questi aspetti in +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ò +assumere che, in ogni piattaforma su cui è implementato Linux, il tipo +\ctyp{int}, gli altri interi di dimensione inferiore ed i puntatori sono +atomici. Non è affatto detto che lo stesso valga per interi di dimensioni +maggiori (in cui l'accesso può comportare più istruzioni in assembler) o per +le strutture. In tutti questi casi è anche opportuno marcare come +\direct{volatile} le variabili che possono essere interessate ad accesso +condiviso, onde evitare problemi con le ottimizzazioni del codice. + + + +\subsection{Le \textit{race condition} ed i \textit{deadlock}} +\label{sec:proc_race_cond} +\index{\textit{race~condition}|(} +Si definiscono \textit{race condition} tutte quelle situazioni in cui processi +diversi operano su una risorsa comune, ed in cui il risultato viene a +dipendere dall'ordine in cui essi effettuano le loro operazioni. Il caso +tipico è quello di un'operazione che viene eseguita da un processo in più +passi, e può essere compromessa dall'intervento di un altro processo che +accede alla stessa risorsa quando ancora non tutti i passi sono stati +completati. + +Dato che in un sistema multitasking ogni processo può essere interrotto in +qualunque momento per farne subentrare un altro in esecuzione, niente può +assicurare un preciso ordine di esecuzione fra processi diversi o che una +sezione di un programma possa essere eseguita senza interruzioni da parte di +altri. Queste situazioni comportano pertanto errori estremamente subdoli e +difficili da tracciare, in quanto nella maggior parte dei casi tutto +funzionerà regolarmente, e solo occasionalmente si avranno degli errori. + +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} si hanno quando diversi processi accedono allo stesso +file, o nell'accesso a meccanismi di intercomunicazione come la memoria +condivisa. In questi casi, se non si dispone della possibilità di eseguire +atomicamente le operazioni necessarie, occorre che quelle parti di codice in +cui si compiono le operazioni sulle risorse condivise (le cosiddette +\textsl{sezioni critiche}\index{sezioni~critiche}) del programma, siano +opportunamente protette da meccanismi di sincronizzazione (torneremo su queste +problematiche di questo tipo in cap.~\ref{cha:IPC}). + +\index{\textit{deadlock}|(} +Un caso particolare di \textit{race condition} sono poi i cosiddetti +\textit{deadlock}, particolarmente gravi in quanto comportano spesso il blocco +completo di un servizio, e non il fallimento di una singola operazione. Per +definizione un \textit{deadlock} è una situazione in cui due o più processi +non sono più in grado di proseguire perché ciascuno aspetta il risultato di +una operazione che dovrebbe essere eseguita dall'altro. + + +L'esempio tipico di una situazione che può condurre ad un +\textit{deadlock} è 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}). + +In tutti questi casi è di fondamentale importanza il concetto di atomicità +visto in sez.~\ref{sec:proc_atom_oper}; questi problemi infatti possono essere +risolti soltanto assicurandosi, quando essa sia richiesta, che sia possibile +eseguire in maniera atomica le operazioni necessarie. +\index{\textit{race~condition}|)} +\index{\textit{deadlock}|)} + + +\subsection{Le funzioni rientranti} +\label{sec:proc_reentrant} + +Si dice \textsl{rientrante} una funzione che può essere interrotta in +qualunque punto della sua esecuzione ed essere chiamata una seconda volta da +un altro thread di esecuzione senza che questo comporti nessun problema +nell'esecuzione della stessa. La problematica è comune nella programmazione +multi-thread, ma si hanno gli stessi problemi quando si vogliono chiamare +delle funzioni all'interno dei 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. + +Nel caso invece la funzione operi su un oggetto allocato dinamicamente, la +cosa viene a dipendere da come avvengono le operazioni: se l'oggetto è creato +ogni volta e ritornato indietro la funzione può essere rientrante, se invece +esso viene individuato dalla funzione stessa due chiamate alla stessa funzione +potranno interferire quando entrambe faranno riferimento allo stesso oggetto. +Allo stesso modo una funzione può non essere rientrante se usa e modifica un +oggetto che le viene fornito dal chiamante: due chiamate possono interferire +se viene passato lo stesso oggetto; in tutti questi casi occorre molta cura da +parte del programmatore. + +In genere le funzioni di libreria non sono rientranti, molte di esse ad +esempio utilizzano variabili statiche, le \acr{glibc} però mettono a +disposizione due macro di compilatore, \macro{\_REENTRANT} e +\macro{\_THREAD\_SAFE}, la cui definizione attiva le versioni rientranti di +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: