X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=d38c843b8c52e2ca132a68b088ca2991c7efca67;hp=8c8a67190e98a67d4502897f1611bf83bb8e8a40;hb=1fad076b97ffee3c0507d3fcc4e845185a03483c;hpb=0c7fb286c2c1b480ed81f7ce8d506b179e5d291e diff --git a/prochand.tex b/prochand.tex index 8c8a671..d38c843 100644 --- a/prochand.tex +++ b/prochand.tex @@ -1,43 +1,55 @@ +%% prochand.tex +%% +%% Copyright (C) 2000-2002 Simone Piccardi. Permission is granted to +%% copy, distribute and/or modify this document under the terms of the GNU Free +%% Documentation License, Version 1.1 or any later version published by the +%% Free Software Foundation; with the Invariant Sections being "Prefazione", +%% 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. - -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. Infine nella sezione finale affronteremo alcune -problematiche generiche della programmazione in ambiente multitasking. +Come accennato nell'introduzione in un sistema Unix tutte le operazioni +vengono svolte tramite opportuni processi. In sostanza questi ultimi vengono +a costituire l'unità base per l'allocazione e l'uso delle risorse del sistema. +Nel precedente capitolo abbiamo esaminato il funzionamento di un processo come +unità a se stante, in questo esamineremo il funzionamento dei processi +all'interno del sistema. Saranno cioè affrontati i dettagli della creazione e +della 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 +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, e daremo una panoramica sull'uso delle principali funzioni -per la gestione dei processi. +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}. +numero univoco, il cosiddetto \textit{process identifier} o, più brevemente, +\acr{pid}, assegnato in forma progressiva (vedi \secref{sec:proc_pid}) quando +il processo viene creato. -Una seconda caratteristica di un sistema unix è che la generazione di un -processo è una operazione separata rispetto al lancio di un programma. In +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 voluto: questo è ad esempio +eseguirà, in un passo successivo, il programma desiderato: questo è ad esempio quello che fa la shell quando mette in esecuzione il programma che gli indichiamo nella linea di comando. @@ -102,33 +114,72 @@ 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 \curfig\ si è mostrato il -risultato del comando \cmd{pstree} che permette di mostrare questa struttura, -alla cui base c'è \cmd{init} che è progenitore di tutti gli altri processi. + vero, in Linux ci sono alcuni processi speciali che pur comparendo come + figli di \cmd{init}, o con \acr{pid} successivi, sono in realtà generati + direttamente dal kernel, (come \cmd{keventd}, \cmd{kswapd}, etc.).} si +possono classificare i processi con la relazione padre/figlio in +un'organizzazione gerarchica ad albero, in maniera analoga a come i file sono +organizzati in un albero di directory (si veda +\secref{sec:file_organization}); in \figref{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 +\figref{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 \secref{sec:intro_unix_struct} è lo +\textit{scheduler}\index{scheduler} che decide quale processo mettere in +esecuzione; esso viene eseguito ad ogni system call ed ad ogni +interrupt,\footnote{più in una serie di altre occasioni. NDT completare questa + parte.} (ma può essere anche attivato esplicitamente). Il timer di sistema +provvede comunque a che esso sia invocato periodicamente, generando un +interrupt periodico secondo la frequenza specificata dalla costante +\const{HZ}, definita in \file{asm/param.h}, ed il cui valore è espresso in +Hertz.\footnote{Il valore usuale di questa costante è 100, per tutte le + architetture eccetto l'alpha, per la quale è 1000. Occorre fare attenzione a + non confondere questo valore con quello dei clock tick (vedi + \secref{sec:sys_unix_time}).} +%Si ha cioè un interrupt dal timer ogni centesimo di secondo. + +Ogni volta che viene eseguito, lo \textit{scheduler}\index{scheduler} effettua +il calcolo delle priorità dei vari processi attivi (torneremo su questo in +\secref{sec:proc_priority}) e stabilisce quale di essi debba essere posto in +esecuzione fino alla successiva invocazione. + + +\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 -eseguito in maniera indipendente (le differenze fra padre e figlio sono -affrontate in dettaglio in \secref{sec:proc_fork}). +una system call, Linux però usa un'altra nomenclatura, e la funzione +\func{fork} è basata a sua volta sulla system call \func{\_\_clone}, che viene +usata anche per generare i \textit{thread}. Il processo figlio creato dalla +\func{fork} è una copia identica del processo processo padre, ma ha un nuovo +\acr{pid} e viene eseguito in maniera indipendente (le differenze fra padre e +figlio sono affrontate in dettaglio in \secref{sec:proc_fork}). 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. +\secref{sec:proc_wait}); queste funzioni restituiscono anche un'informazione +abbastanza limitata sulle cause della terminazione del processo figlio. Quando un processo ha concluso il suo compito o ha incontrato un errore non risolvibile esso può essere terminato con la funzione \func{exit} (si veda @@ -146,9 +197,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 @@ -158,53 +209,69 @@ 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 \secref{sec:sess_proc_group}).} ogni volta che +un nuovo processo viene creato, fino ad un limite che, essendo il \acr{pid} un +numero positivo memorizzato in un intero a 16 bit, arriva ad un massimo di +32768. Oltre questo valore l'assegnazione riparte dal numero più basso +disponibile a partire da un minimo di 300,\footnote{questi valori, fino al + kernel 2.4.x, sono definiti dalla macro \const{PID\_MAX} in \file{threads.h} + e direttamente in \file{fork.c}, con il kernel 2.5.x e la nuova interfaccia + per i thread creata da Ingo Molnar anche il meccanismo di allocazione dei + \acr{pid} è stato modificato.} che serve a riservare i \acr{pid} più bassi +ai processi eseguiti direttamente dal kernel. Per questo motivo, come visto +in \secref{sec:proc_hierarchy}, il processo di avvio (\cmd{init}) ha sempre il +\acr{pid} uguale a uno. 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. + \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} \noindent esempi dell'uso di queste funzioni sono riportati in -\figref{fig:proc_fork_code}, nel programma di esempio \file{ForkTest.c}. +\figref{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{tmpname} (si veda \secref{sec:file_temp_file}) usa il \acr{pid} +per generare un pathname univoco, che non potrà essere replicato da un altro +processo che usi la stessa funzione. Tutti i processi figli dello stesso processo padre sono detti \textit{sibling}, questa è una delle relazioni usate nel \textsl{controllo di @@ -214,18 +281,19 @@ o relativi allo stesso login. Torneremo su questo argomento in dettaglio in un processo e le varie relazioni fra processi utilizzate per definire una sessione. -Oltre al \acr{pid} e al \acr{ppid}, e a quelli usati per il controllo di -sessione, ad ogni processo sono associati altri identificatori, usati per il -controllo di accesso, che servono per determinare se il processo può o meno -eseguire le operazioni richieste, a seconda dei privilegi e dell'identità di -chi lo ha posto in esecuzione; su questi torneremo in dettagli più avanti in -\secref{sec:proc_perms}. +Oltre al \acr{pid} e al \acr{ppid}, (e a quelli che vedremo in +\secref{sec:sess_proc_group}, relativi al controllo di sessione), ad ogni +processo vengono associati degli altri identificatori che vengono usati per il +controllo di accesso. Questi servono per determinare se un processo può +eseguire o meno le operazioni richieste, a seconda dei privilegi e +dell'identità di chi lo ha posto in esecuzione; l'argomento è complesso e sarà +affrontato in dettaglio in \secref{sec:proc_perms}. \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 @@ -236,94 +304,57 @@ prototipo della funzione \funcdecl{pid\_t fork(void)} Crea un nuovo processo. - \bodydesc{Restituisce zero al padre e il \acr{pid} al figlio in caso di - successo, ritorna -1 al padre (senza creare il figlio) in caso di errore; - \var{errno} può assumere i valori: + \bodydesc{In caso di successo restituisce il \acr{pid} del figlio al padre e + zero al figlio; ritorna -1 al padre (senza creare il figlio) in caso di + errore; \var{errno} può assumere i valori: \begin{errlist} - \item[\macro{EAGAIN}] non ci sono risorse sufficienti per creare un'altro + \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{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 +il processo figlio continuano ad essere eseguiti normalmente a partire +dall'istruzione successiva alla \func{fork}; il processo figlio è però una +copia del padre, e riceve una copia dei segmenti di testo, stack e dati (vedi \secref{sec:proc_mem_layout}), ed esegue esattamente lo stesso codice del -padre, 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. +padre. Si tenga presente però che la memoria è copiata, non condivisa, +pertanto padre e figlio vedono variabili diverse. + +Per quanto riguarda la gestione della memoria, in generale il segmento di +testo, che è identico per i due processi, è condiviso e tenuto in read-only +per il padre e per i figli. Per gli altri segmenti Linux utilizza la tecnica +del \textit{copy on write}\index{copy on write}; questa tecnica comporta che +una pagina di memoria viene effettivamente copiata per il nuovo processo solo +quando ci viene effettuata sopra una scrittura (e si ha quindi una reale +differenza fra padre e figlio). In questo modo si rende molto più efficiente +il meccanismo della creazione di un nuovo processo, non essendo più necessaria +la copia di tutto lo spazio degli indirizzi virtuali del padre, ma solo delle +pagine di memoria che sono state modificate, e solo al momento della modifica +stessa. 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 \secref{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] 572 pts/0 R 0:00 ps T \end{verbatim} %$ -\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 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 \secref{sec:sig_sigchld} e \secref{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. +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 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. +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. -\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: +\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} \noindent -è presente fin dalle prime versioni di unix; la funzione ritorna alla -conclusione del primo figlio (o immediatamente se un figlio è già -uscito). 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. Nel -caso un processo abbia più figli il valore di ritorno permette di -identificare qual'è quello che è uscito. - -Questa funzione 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 +è 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 \secref{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 -provveda 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 \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 è: +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 +\secref{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. \bodydesc{La funzione restituisce il \acr{pid} del processo che è uscito, 0 se - è stata specificata l'opzione \macro{WNOHANG} e il processo non è uscito e + è 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. + \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 \tabref{tab:proc_waidpid_pid}. + \begin{table}[!htb] \centering \footnotesize - \begin{tabular}[c]{|c|p{10cm}|} + \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 + \secref{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 -maschera binaria ottenuta con l'OR delle suddette costanti con zero. +Il comportamento di \func{waitpid} può inoltre essere modificato passando +delle opportune opzioni tramite l'argomento \param{option}. I valori possibili +sono il già citato \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 \secref{sec:sess_job_control}). In tal caso infatti +la funzione ritorna, restituendone il \acr{pid}, quando c'è un processo figlio +che è entrato in stato di sleep (vedi \tabref{tab:proc_proc_states}) e del +quale non si è ancora letto lo stato (con questa stessa opzione). In Linux +sono previste altre opzioni non standard relative al comportamento con i +thread, che riprenderemo in \secref{sec:thread_xxx}. 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 +\secref{sec:sig_base}) per questo segnale è di essere ignorato, ma la sua +generazione costituisce il meccanismo di comunicazione asincrona con cui il +kernel avverte il processo padre che uno dei suoi figli è terminato. In genere in un programma non si vuole essere forzati ad attendere la conclusione di un processo per proseguire, specie se tutto questo serve solo -per leggerne lo stato di chiusura (ed evitare la presenza di \textit{zombie}), -per questo la modalità più usata per chiamare queste funzioni è quella di -utilizzarle all'interno di un \textit{signal handler} (torneremo sui segnali e -su come gestire \macro{SIGCHLD} in \secref{sec:sig_sigwait_xxx}) nel qual -caso, dato che il segnale è generato dalla terminazione un figlio, avremo la -certezza che la chiamata a \func{wait} non si bloccherà. +per leggerne lo stato di chiusura (ed evitare la presenza di +\textit{zombie}\index{zombie}), per questo la modalità più usata per chiamare +queste funzioni è quella di utilizzarle all'interno di un \textit{signal + handler} (vedremo un esempio di come gestire \const{SIGCHLD} con i segnali +in \secref{sec:sig_example}). In questo caso infatti, dato che il segnale è +generato dalla terminazione di un figlio, avremo la certezza che la chiamata a +\func{wait} non si bloccherà. \begin{table}[!htb] \centering @@ -900,23 +960,21 @@ certezza che la chiamata a \func{wait} non si bloccher \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 restituito un valore non nullo.\\ + \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 restituito un valore non nullo.\\ + \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 restituito un valore non nullo\footnote{questa - macro non è definita dallo standard POSIX.1, ma è presente come estensione - sia in Linux che in altri unix}.\\ + \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}. \\ + l'opzione \const{WUNTRACED}. \\ \macro{WSTOPSIG(s)} & restituisce il numero del segnale che ha bloccato - il processo, Può essere valutata solo se \macro{WIFSTOPPED} ha + il processo, Può essere valutata solo se \val{WIFSTOPPED} ha restituito un valore non nullo. \\ \hline \end{tabular} @@ -925,144 +983,116 @@ 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 \tabref{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}. +\val{WTERMSIG} può essere confrontato con le costanti definite in +\file{signal.h} ed elencate in \tabref{tab:sig_signal_list}, e stampato usando +le apposite funzioni trattate in \secref{sec:sig_strsignal}. \subsection{Le funzioni \func{wait3} e \func{wait4}} \label{sec:proc_wait4} -Linux, seguendo una estensione di BSD, supporta altre due funzioni per la -lettura dello stato di terminazione di un processo, analoghe a \func{wait} e -\func{waitpid}, ma che prevedono un ulteriore parametro attraverso il quale il -kernel può restituire al processo padre ulteriori informazioni sulle risorse -usate dal processo terminato e dai vari figli. Queste funzioni, che diventano -accessibili definendo la costante \macro{\_USE\_BSD}, sono: +Linux, seguendo un'estensione di BSD, supporta altre due funzioni per la +lettura dello stato di terminazione di un processo, analoghe alle precedenti +ma che prevedono un ulteriore parametro attraverso il quale il kernel può +restituire al padre informazioni sulle risorse usate dal processo terminato e +dai vari figli. 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} + \headdecl{sys/times.h} \headdecl{sys/types.h} \headdecl{sys/wait.h} + \headdecl{sys/resource.h} + \funcdecl{pid\_t wait4(pid\_t pid, int * status, int options, struct rusage - * rusage)} - È identica a \func{waitpid} sia per comportamento che per i - valori dei parametri, ma restituisce in \var{rusage} un sommario delle - risorse usate dal processo (per i dettagli vedi \secref{sec:sys_xxx}) + * rusage)} + È identica a \func{waitpid} sia per comportamento che per i valori dei + parametri, 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 \func{wait4(-1, \&status, opt, rusage)} è + Prima versione, equivalente a \code{wait4(-1, \&status, opt, rusage)} è ormai deprecata in favore di \func{wait4}. \end{functions} \noindent -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; la sua definizione è riportata in \nfig. -\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 \var{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 portabilità, e serve in caso si debba accedere -ai campi di \var{rusage} definiti come \type{struct timeval}. La -struttura è ripresa da BSD 4.3, 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}. +la struttura \struct{rusage} è definita in \file{sys/resource.h}, e viene +utilizzata anche dalla funzione \func{getrusage} (vedi +\secref{sec:sys_resource_use}) per ottenere le risorse di sistema usate da un +processo; la sua definizione è riportata in \figref{fig:sys_rusage_struct}. \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 \figref{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}. - \bodydesc{La funzione ritorna -1 solo in caso di errore, nel qual caso - caso la \var{errno} può assumere i valori: + \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'interprete + \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 + \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[\macro{ELIBBAD}] Un interprete ELF non è in un formato + \item[\errcode{ELIBBAD}] Un interprete ELF non è in un formato riconoscibile. + \item[\errcode{E2BIG}] La lista degli argomenti è troppo grande. \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} La funzione \func{exec} 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 +\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 -\func{main(int argc, char *argv[], char *envp[])}. +\code{main(int argc, char *argv[], char *envp[])}. Le altre funzioni della famiglia servono per fornire all'utente una serie possibile di diverse interfacce per la creazione di un nuovo processo. I loro @@ -1080,18 +1110,19 @@ Sostituiscono l'immagine corrente del processo con quella indicata nel primo argomento. I parametri successivi consentono di specificare gli argomenti a linea di comando e l'ambiente ricevuti dal nuovo processo. -\bodydesc{Queste funzioni ritornano solo in caso di errore, restituendo - -1; 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 \tabref{tab:proc_exec_scheme}. La +prima differenza riguarda le modalità di passaggio dei parametri che poi +andranno a costituire gli argomenti a linea di comando (cioè i valori di +\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, @@ -1099,9 +1130,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. @@ -1114,8 +1143,8 @@ 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$&&& \\ @@ -1134,33 +1163,34 @@ 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 +specifica il programma che si vuole eseguire. Con lo mnemonico \code{p} si indicano le due funzioni che replicano il comportamento della shell nello -specificare il comando da eseguire; quando il parametro \var{file} non -contiene una \file{/} esso viene considerato come un nome di programma, e +specificare il comando da eseguire; quando il parametro \param{file} non +contiene una ``\file{/}'' esso viene considerato come un nome di programma, e viene eseguita automaticamente una ricerca fra i file presenti nella lista di directory specificate dalla variabile di ambiente \var{PATH}. Il file che -viene posto in esecuzione è il primo che viene trovato. Se si ha un errore di -permessi negati (cioè l'esecuzione della sottostante \func{execve} ritorna un -\macro{EACCESS}), la ricerca viene proseguita nelle eventuali ulteriori -directory indicate nel \var{PATH}, solo se non viene trovato nessun altro file -viene finalmente restituito \macro{EACCESS}. +viene posto in esecuzione è il primo che viene trovato. Se si ha un errore +relativo a permessi di accesso insufficienti (cioè l'esecuzione della +sottostante \func{execve} ritorna un \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 dal parametro \var{path}, che viene interpretato come il +indicato dall'argomento \param{path}, che viene interpretato come il \textit{pathname} del programma. \begin{figure}[htb] \centering - \includegraphics[width=13cm]{img/exec_rel} - \caption{La interrelazione fra le sei funzioni della famiglia \func{exec}} + \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 +Con lo mnemonico \code{e} vengono indicate quelle funzioni che necessitano di un vettore di parametri \var{envp[]} analogo a quello usato per gli argomenti -a riga di comando (terminato quindi da un \macro{NULL}), le altre usano il +a riga di comando (terminato quindi da un \val{NULL}), le altre usano il valore della variabile \var{environ} (vedi \secref{sec:proc_environ}) del processo di partenza per costruire l'ambiente. @@ -1168,74 +1198,75 @@ 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} +\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. +\item l'\textsl{user-ID reale}, il \textit{group-ID reale} ed i + \textsl{group-ID supplementari} (vedi \secref{sec:proc_access_id}). +\item il \textit{session id} (\acr{sid}) ed il \textit{process group-ID} + (\acr{pgid}), vedi \secref{sec:sess_proc_group}. +\item il terminale di controllo (vedi \secref{sec:sess_ctrl_term}). +\item il tempo restante ad un allarme (vedi \secref{sec:sig_alarm_abort}). \item la directory radice e la directory di lavoro corrente (vedi \secref{sec:file_work_dir}). \item la maschera di creazione dei file (\var{umask}, vedi \secref{sec:file_umask}) ed i \textit{lock} sui file (vedi \secref{sec:file_locking}). \item i segnali sospesi (\textit{pending}) e la maschera dei segnali (si veda - \secref{sec:sig_xxx}). -\item i limiti sulle risorse (vedi \secref{sec:sys_limits}).. + \secref{sec:sig_sigmask}). +\item i limiti sulle risorse (vedi \secref{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}). + \var{tms\_cutime}, \var{tms\_ustime} (vedi \secref{sec:sys_cpu_times}). \end{itemize*} -Oltre a questo i segnali che sono stati settati per essere ignorati nel -processo chiamante mantengono lo stesso settaggio pure nel nuovo programma, -tutti gli altri segnali vengono settati alla loro azione di default. Un caso -speciale è il segnale \macro{SIGCHLD} che, quando settato a \macro{SIG\_IGN}, -può anche non essere resettato a \macro{SIG\_DFL} (si veda -\secref{sec:sig_xxx}). - -La gestione dei file aperti dipende dal valore del flag di -\textit{close-on-exec} per ciascun file descriptor (si veda -\secref{sec:file_fcntl}); i file per cui è settato vengono chiusi, tutti gli -altri file restano aperti. Questo significa che il comportamento di default è -che i file restano aperti attraverso una \func{exec}, a meno di una chiamata -esplicita a \func{fcntl} che setti il suddetto flag. - -Per le directory lo standard POSIX.1 richiede che esse vengano chiuse +Inoltre i segnali che sono stati 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 +\secref{sec:sig_gen_beha}). + +La gestione dei file aperti dipende dal valore che ha il flag di +\textit{close-on-exec}\index{close-on-exec} (vedi anche +\secref{sec:file_fcntl}) per ciascun file descriptor. I file per cui è +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} che effettua da sola il settaggio del flag di -\textit{close-on-exec} sulle directory che apre, in maniera trasparente -all'utente. +\func{opendir} (vedi \secref{sec:file_dir_read}) che effettua da sola +l'impostazione del flag di \textit{close-on-exec}\index{close-on-exec} sulle +directory che apre, in maniera trasparente all'utente. -Abbiamo detto che il \textit{real user ID} ed il \textit{real group ID} +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'\textit{effective user ID} ed l'\textit{effective group ID}, tranne il caso -in cui il file che si va ad eseguire ha o il \acr{suid} bit o lo \acr{sgid} -bit settato, nel qual caso \textit{effective user ID} e \textit{effective - group ID} vengono settati rispettivamente all'utente o al gruppo cui il file -appartiene (per i dettagli vedi \secref{sec:proc_perms}). +l'\textsl{user-ID effettivo} ed il \textsl{group-ID effettivo} (il significato +di questi identificatori è trattato in \secref{sec:proc_access_id}), tranne +quando il file che si va ad eseguire abbia o il \acr{suid} bit o lo \acr{sgid} +bit impostato, in questo caso l'\textsl{user-ID effettivo} ed il +\textsl{group-ID effettivo} vengono impostati rispettivamente all'utente o al +gruppo cui il file appartiene (per i dettagli vedi \secref{sec:proc_perms}). Se il file da eseguire è in formato \emph{a.out} e necessita di librerie condivise, viene lanciato il \textit{linker} dinamico \cmd{ld.so} prima del programma per caricare le librerie necessarie ed effettuare il link dell'eseguibile. Se il programma è in formato ELF per caricare le librerie -dinamiche viene usato l'interprete indicato nel segmento \macro{PT\_INTERP}, +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 -\emph{libc5}, e \file{/lib/ld-linux.so.2} per programmi linkati con le -\emph{glibc}. Infine nel caso il file sia uno script esso deve iniziare con +\acr{libc5}, e \file{/lib/ld-linux.so.2} per programmi linkati con le +\acr{glibc}. Infine nel caso il file sia uno script esso deve iniziare con una linea nella forma \cmd{\#!/path/to/interpreter} dove l'interprete indicato -deve esse un valido programma (binario, non un altro script) che verrà -chiamato come se si fosse eseguito il comando \cmd{interpreter [arg] +deve esse un programma valido (binario, non un altro script) che verrà +chiamato come se si fosse eseguito il comando \cmd{interpreter [argomenti] filename}. Con la famiglia delle \func{exec} si chiude il novero delle funzioni su cui è -basata la gestione dei processi in unix: con \func{fork} si crea un nuovo -processo, con \func{exec} si avvia un nuovo programma, con \func{exit} e -\func{wait} si effettua e verifica 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. @@ -1245,18 +1276,22 @@ parametri connessi ai processi. In questa sezione esamineremo le problematiche relative al controllo di accesso dal punto di vista del processi; vedremo quali sono gli identificatori usati, come questi possono essere modificati nella creazione e nel lancio di -nuovi processi, e le varie funzioni per la loro manipolazione diretta e tutte -le problematiche connesse alla gestione accorta dei privilegi. +nuovi processi, le varie funzioni per la loro manipolazione diretta e tutte le +problematiche connesse ad una gestione accorta dei privilegi. -\subsection{Utente e gruppo di un processo} -\label{sec:proc_user_group} +\subsection{Gli identificatori del controllo di accesso} +\label{sec:proc_access_id} Come accennato in \secref{sec:intro_multiuser} il modello base\footnote{in realtà già esistono estensioni di questo modello base, che lo rendono più flessibile e controllabile, come le \textit{capabilities}, le ACL per i file - o il \textit{Mandatory Access Control} di SELinux} di sicurezza di un -sistema unix-like è fondato sui concetti di utente e gruppo, e sulla + 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. @@ -1266,8 +1301,8 @@ utenti, per i quali invece vengono effettuati i vari controlli di accesso. %notevole flessibilità, Abbiamo già accennato come il sistema associ ad ogni utente e gruppo due -identificatori univoci, lo \acr{uid} e il \acr{gid}; questi servono al kernel -per identificare uno specifico utente o un gruppo di utenti, per poi poter +identificatori univoci, lo user-ID ed il group-ID; questi servono al kernel per +identificare uno specifico utente o un gruppo di utenti, per poi poter controllare che essi siano autorizzati a compiere le operazioni richieste. Ad esempio in \secref{sec:file_access_control} vedremo come ad ogni file vengano associati un utente ed un gruppo (i suoi \textsl{proprietari}, indicati @@ -1276,91 +1311,103 @@ 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 processo, e pertanto -anche a ciascuno di essi è associato un utente e a un gruppo. - -Un semplice controllo di una corrispondenza fra identificativi però non -garantisce però sufficiente flessibilità per tutti quei casi in cui è -necessario poter disporre di privilegi diversi, o dover impersonare un altro -utente per un limitato insieme di operazioni. Per questo motivo in generale -tutti gli unix prevedono che i processi abbiano almeno due gruppi di -identificatori, chiamati rispettivamente \textit{real} ed \textit{effective}. +anche poter identificare chi è che ha lanciato un certo programma, e pertanto +anche a ciascun processo dovrà essere associato ad un utente e ad un gruppo. + +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 \tabref{tab:proc_uid_gid}. \begin{table}[htb] \footnotesize \centering - \begin{tabular}[c]{|c|l|p{6.5cm}|} + \begin{tabular}[c]{|c|c|l|p{7.3cm}|} + \hline + \textbf{Suffisso} & \textbf{Gruppo} & \textbf{Denominazione} + & \textbf{Significato} \\ + \hline \hline - \textbf{Suffisso} & \textbf{Significato} & \textbf{Utilizzo} \\ + \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 - \acr{uid} & \textit{real user id} & indica l'utente che ha lanciato - il programma\\ - \acr{gid} & \textit{real group id} & indica il gruppo dell'utente - che ha lanciato il programma \\ - \acr{euid} & \textit{effective user id} & indica l'utente usato - dal programma nel controllo di accesso \\ - \acr{egid} & \textit{effective group id} & indica il gruppo - usato dal programma nel controllo di accesso \\ - -- & \textit{supplementary group id} & indica i gruppi cui - l'utente appartiene \\ - -- & \textit{saved user id} & copia dell'\acr{euid} iniziale\\ - -- & \textit{saved group id} & copia dell'\acr{egid} iniziale \\ - \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 \\ + -- & \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 usate dalle varie funzioni di manipolazione.} + indicazione dei suffissi usati dalle varie funzioni di manipolazione.} \label{tab:proc_uid_gid} \end{table} -Al primo gruppo appartengono il \textit{real user ID} e il \textit{real group - ID}: questi vengono settati al login ai valori corrispondenti all'utente con -cui si accede al sistema (e relativo gruppo di default). Servono per -l'identificazione dell'utente e normalmente non vengono mai cambiati. In -realtà vedremo (in \secref{sec:proc_setuid}) che è possibile modificarli, ma -solo ad un processo che abbia i privilegi di amministratore; questa -possibilità è usata ad esempio da \cmd{login} che una volta completata la -procedura di autenticazione lancia una shell per la quale setta questi -identificatori ai valori corrispondenti all'utente che entra nel sistema. - -Al secondo gruppo appartengono l'\textit{effective user ID} e -l'\textit{effective group ID} (a cui si aggiungono gli eventuali -\textit{supplementary group id} dei gruppi dei quale l'utente fa parte). -Questi sono invece gli identificatori usati nella verifiche dei permessi del -processo e per il controllo di accesso ai file (argomento affrontato in -dettaglio in \secref{sec:file_perm_overview}). +Al primo gruppo appartengono l'\textsl{user-ID reale} ed il \textsl{group-ID + reale}: questi vengono impostati al login ai valori corrispondenti +all'utente con cui si accede al sistema (e relativo gruppo principale). +Servono per l'identificazione dell'utente e normalmente non vengono mai +cambiati. In realtà vedremo (in \secref{sec:proc_setuid}) che è possibile +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 nella verifiche dei permessi del processo e per il +controllo di accesso ai file (argomento affrontato in dettaglio in +\secref{sec:file_perm_overview}). Questi identificatori normalmente sono identici ai corrispondenti del gruppo -\textsl{reale} tranne nel caso in cui, come visto in \secref{sec:proc_exec}, -il programma che si è posto in esecuzione abbia i bit \acr{suid} o \acr{sgid} -settati (il significato di questi bit è affrontato in dettaglio in -\secref{sec:file_suid_sgid}). In questo caso essi saranno settati all'utente e -al gruppo proprietari del file; questo consente, per programmi in 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} tutti questi identificatori -possono essere letti dal processo attraverso delle opportune funzioni, i cui -prototipi sono i seguenti: +\textit{real} tranne nel caso in cui, come accennato in +\secref{sec:proc_exec}, il programma che si è posto in esecuzione abbia i bit +\acr{suid} o \acr{sgid} impostati (il significato di questi bit è affrontato +in dettaglio in \secref{sec:file_suid_sgid}). In questo caso essi saranno +impostati all'utente e al gruppo proprietari del file. Questo consente, per +programmi in cui ci sia necessità, di dare a qualunque utente normale +privilegi o permessi di un altro (o dell'amministratore). + +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 il \textit{real user ID} del - processo corrente. - - \funcdecl{uid\_t geteuid(void)} Restituisce l'\textit{effective user ID} del + \funcdecl{uid\_t getuid(void)} Restituisce l'\textsl{user-ID reale} del processo corrente. - \funcdecl{gid\_t getgid(void)} Restituisce il \textit{real group ID} del + \funcdecl{uid\_t geteuid(void)} Restituisce l'\textsl{user-ID effettivo} del processo corrente. - \funcdecl{gid\_t getegid(void)} Restituisce l'\textit{effective group ID} del + \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} @@ -1372,318 +1419,965 @@ 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}, analoghi ai precedenti. Il primo -gruppo è lo stesso usato in SVr4, e previsto dallo standard POSIX quando è -definita la costante \macro{\_POSIX\_SAVED\_IDS}\footnote{in caso si abbia a - 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 +\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. -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 dalla funzione \func{exec} all'avvio del -processo, come copie dell'\textit{effective user id} e dell'\textit{effective - group id} dopo che questo sono stati settati 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. - -Il \textit{filesystem user id} e il \textit{filesystem group id} sono una -estensione introdotta in Linux per rendere più sicuro l'uso di NFS (torneremo -sull'argomento in \secref{sec:proc_setfsuid}). Essi sono una replica dei -corrispondenti \textit{effective id}, ai quali si sostituiscono per tutte le -operazioni di verifica dei permessi relativi ai file (trattate in -\secref{sec:file_perm_overview}). Ogni cambiamento effettuato sugli -\textit{effective id} viene automaticamente riportato su di essi, per cui in -condizioni normali se ne può tranquillamente ignorare l'esistenza, in quanto -saranno del tutto equivalenti ai precedenti. - -Uno specchietto riassuntivo, contenente l'elenco completo degli identificatori -di utente e gruppo associati dal kernel ad ogni processo, è riportato in -\tabref{tab:proc_uid_gid}. +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 \secref{sec:proc_setfsuid}). Essi sono una +replica dei corrispondenti identificatori del gruppo \textit{effective}, ai +quali si sostituiscono per tutte le operazioni di verifica dei permessi +relativi ai file (trattate in \secref{sec:file_perm_overview}). Ogni +cambiamento effettuato sugli identificatori effettivi viene automaticamente +riportato su di essi, per cui in condizioni normali si può tranquillamente +ignorarne l'esistenza, in quanto saranno del tutto equivalenti ai precedenti. \subsection{Le funzioni \func{setuid} e \func{setgid}} \label{sec:proc_setuid} Le due funzioni che vengono usate per cambiare identità (cioè utente e gruppo -di appartenenza) ad un processo sono rispettivamente \func{setuid} e -\func{setgid}; come accennato in \secref{sec:proc_user_group} in Linux esse -seguono la semantica POSIX che prevede l'esistenza di \textit{saved user id} e -\textit{saved group id}; i loro prototipi sono: +di appartenenza) ad un processo sono rispettivamente \funcd{setuid} e +\funcd{setgid}; come accennato in \secref{sec:proc_access_id} in Linux esse +seguono la semantica POSIX che prevede l'esistenza dell'\textit{user-ID + salvato} e del \textit{group-ID salvato}; i loro prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} -\funcdecl{int setuid(uid\_t uid)} Setta l'\textit{user ID} del processo +\funcdecl{int setuid(uid\_t uid)} Imposta l'\textsl{user-ID} del processo corrente. -\funcdecl{int setgid(gid\_t gid)} Setta il \textit{group ID} del processo +\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 è \macro{EPERM}.} + 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 \textit{group id} invece che all'\textit{user id}. Gli -eventuali \textit{supplementary group id} non vengono modificati da nessuna -delle funzioni che tratteremo in questa sezione. - +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'\textit{effective user id} è zero (cioè è quello dell'amministratore di -sistema) allora tutti gli identificatori (\textit{real}, \textit{effective} -e \textit{saved}) vengono settati al valore specificato da \var{uid}, -altrimenti viene settato solo l'\textit{effective user id}, e soltanto se il -valore specificato corrisponde o al \textit{real user id} o al \textit{saved - user id}. Negli altri casi viene segnalato un errore (con \macro{EPERM}). +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} settati di -riportare l'\textit{effective user id} a quello dell'utente che ha lanciato il -programma, effettuare il lavoro che non necessita di privilegi aggiuntivi, ed -eventualmente tornare indietro. +consentire ad un programma con i bit \acr{suid} o \acr{sgid} impostati (vedi +\secref{sec:file_suid_sgid}) di riportare l'\textsl{user-ID effettivo} a quello +dell'utente che ha lanciato il programma, effettuare il lavoro che non +necessita di privilegi aggiuntivi, ed eventualmente tornare indietro. -Come esempio per chiarire dell'uso di queste funzioni prediamo quello con cui +Come esempio per chiarire l'uso di queste funzioni prendiamo quello con cui viene gestito l'accesso al file \file{/var/log/utmp}. In questo file viene registrato chi sta usando il sistema al momento corrente; chiaramente non può essere lasciato aperto in scrittura a qualunque utente, che potrebbe falsificare la registrazione. Per questo motivo questo file (e l'analogo \file{/var/log/wtmp} su cui vengono registrati login e logout) appartengono ad un gruppo dedicato (\acr{utmp}) ed i programmi che devono accedervi (ad -esempio tutti i programmi di terminale in X, o il programma \cmd{screen} -che crea terminali multipli su una console) appartengono a questo gruppo ed -hanno il bit \acr{sgid} settato. +esempio tutti i programmi di terminale in X, o il programma \cmd{screen} che +crea terminali multipli su una console) appartengono a questo gruppo ed hanno +il bit \acr{sgid} impostato. -Quando uno di questi programmi (ad esempio \cmd{xterm}) viene lanciato la +Quando uno di questi programmi (ad esempio \cmd{xterm}) viene lanciato, la situazione degli identificatori è la seguente: \begin{eqnarray*} \label{eq:1} - \textit{real group id} &=& \textrm{\acr{gid} (del chiamante)} \\ - \textit{effective group id} &=& \textrm{\acr{utmp}} \\ - \textit{saved group id} &=& \textrm{\acr{utmp}} + \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 l'\textit{effective group id} è quello giusto, il -programma può accedere a \file{/var/log/utmp} in scrittura ed aggiornarlo, a -questo punto il programma può eseguire una \func{setgid(getgid())} per settare -l'\textit{effective group id} a quello dell'utente (e dato che il \textit{real - group id} corrisponde la funzione avrà successo), in questo modo non sarà -possibile lanciare dal terminale programmi che modificano detto file, in tal -caso infatti la situazione degli identificatori sarebbe: +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} - \textit{real group id} &=& \textrm{\acr{gid} (invariato)} \\ - \textit{effective group id} &=& \textrm{\acr{gid}} \\ - \textit{saved group id} &=& \textrm{\acr{utmp} (invariato)} + \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 -\textit{effective group id}. All'uscita dal terminale, per poter di nuovo +\textsl{group-ID effettivo}. All'uscita dal terminale, per poter di nuovo aggiornare lo stato di \file{/var/log/utmp} il programma eseguirà una -\func{setgid(utmp)} (dove \var{utmp} è il valore numerico associato al gruppo -\acr{utmp}, ottenuto ad esempio con una \func{getegid}), dato che in questo -caso il valore richiesto corrisponde al \textit{saved group id} la funzione -avrà successo e riporterà la situazione a: +\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} - \textit{real group id} &=& \textrm{\acr{gid} (invariato)} \\ - \textit{effective group id} &=& \textrm{\acr{utmp}} \\ - \textit{saved group id} &=& \textrm{\acr{utmp} (invariato)} + \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 root, in tal caso infatti l'esecuzione una \func{setuid} -comporta il cambiamento di tutti gli identificatori associati al processo, -rendendo impossibile riguadagnare i privilegi di amministratore. Questo -comportamento è corretto per l'uso che ne fa \cmd{login} una volta che crea -una nuova shell per l'utente; ma quando si vuole cambiare soltanto -l'\textit{effective user id} del processo per cedere i privilegi occorre +i privilegi di amministratore, in tal caso infatti l'esecuzione di una +\func{setuid} comporta il cambiamento di tutti gli identificatori associati al +processo, rendendo impossibile riguadagnare i privilegi di amministratore. +Questo comportamento è corretto per l'uso che ne fa \cmd{login} una volta che +crea una nuova shell per l'utente; ma quando si vuole cambiare soltanto +l'\textsl{user-ID effettivo} del processo per cedere i privilegi occorre ricorrere ad altre funzioni (si veda ad esempio \secref{sec:proc_seteuid}). -\subsection{Le funzioni \func{setreuid} e \func{setresuid}} +\subsection{Le funzioni \func{setreuid} e \func{setregid}} \label{sec:proc_setreuid} -Queste due funzioni derivano da BSD che non supportando\footnote{almeno fino - alla versione 4.3+BSD TODO, verificare e aggiornare la nota} i \textit{saved - id} le usava per poter scambiare fra di loro effective e real id. I -prototipi sono: +Le due funzioni \funcd{setreuid} e \funcd{setregid} derivano da BSD che, non +supportando\footnote{almeno fino alla versione 4.3+BSD TODO, FIXME verificare + e aggiornare la nota.} gli identificatori del gruppo \textit{saved}, le usa +per poter scambiare fra di loro \textit{effective} e \textit{real}. I +rispettivi prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} -\funcdecl{int setreuid(uid\_t ruid, uid\_t euid)} Setta il \textit{real user - ID} e l'\textit{effective user ID} del processo corrente ai valori -specificati da \var{ruid} e \var{euid}. +\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)} Setta il \textit{real group - ID} e l'\textit{effective group ID} del processo corrente ai valori -specificati da \var{rgid} e \var{egid}. +\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 è \macro{EPERM}.} + di fallimento: l'unico errore possibile è \errval{EPERM}.} \end{functions} -I processi non privilegiati possono settare i \textit{real id} soltanto ai -valori dei loro \textit{effective id} o \textit{real id} e gli -\textit{effective id} ai valori dei loro \textit{real id}, \textit{effective - id} o \textit{saved id}; valori diversi comportano il fallimento della -chiamata; l'amministratore invece può specificare un valore qualunque. -Specificando un valore di -1 l'identificatore corrispondente viene lasciato -inalterato. +La due funzioni sono analoghe ed il loro comportamento è identico; quanto +detto per la prima prima riguardo l'user-ID, si applica immediatamente alla +seconda per il group-ID. I processi non privilegiati possono impostare solo i +valori del loro user-ID effettivo o reale; valori diversi comportano il +fallimento della chiamata; l'amministratore invece può specificare un valore +qualunque. Specificando un argomento di valore -1 l'identificatore +corrispondente verrà lasciato inalterato. -Con queste funzione si possono scambiare fra loro \textit{real id} e -\textit{effective id}, 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. +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 \textit{real id} privilegiato, che dovrà +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 i \textit{real id} agli -\textit{effective id}) in caso contrario quest'ultimo potrebbe a sua volta -effettuare uno scambio e riottenere privilegi non previsti. +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 porrebbe per i \textit{saved id}. Queste funzioni derivano da -un'implementazione che non ne prevede la presenza, e quindi non è possibile -usarle per correggere la situazione come nel caso precedente, per questo -motivo tutte le volte che uno degli identificatori viene modificato ad un -valore diverso dal precedente \textit{real id}, il \textit{saved id} viene -sempre settato al valore dell'\textit{effective id}. +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. -\subsection{Le funzioni \func{setresuid} e \func{setresgid}} -\label{sec:proc_setresuid} +\subsection{Le funzioni \func{seteuid} e \func{setegid}} +\label{sec:proc_seteuid} -Queste due funzioni sono una estensione introdotta in Linux dal kernel 2.1.44, -e permettono un completo controllo su tutti gli identificatori (\textit{real}, -\textit{effective} e \textit{saved}), i prototipi sono: +Le due funzioni \funcd{seteuid} e \funcd{setegid} sono un'estensione allo +standard POSIX.1 (ma sono comunque supportate dalla maggior parte degli Unix) +e vengono usate per cambiare gli identificatori del gruppo \textit{effective}; +i loro prototipi sono: \begin{functions} \headdecl{unistd.h} \headdecl{sys/types.h} -\funcdecl{int setresuid(uid\_t ruid, uid\_t euid, uid\_t suid)} Setta il -\textit{real user ID}, l'\textit{effective user ID} e il \textit{saved user - ID} del processo corrente ai valori specificati rispettivamente da -\var{ruid}, \var{euid} e \var{suid}. - -\funcdecl{int setresgid(gid\_t rgid, gid\_t egid, gid\_t sgid)} Setta il -\textit{real group ID}, l'\textit{effective group ID} e il \textit{saved group - ID} del processo corrente ai valori specificati rispettivamente da -\var{rgid}, \var{egid} e \var{sgid}. +\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 possibile è \macro{EPERM}.} + di fallimento: l'unico errore è \errval{EPERM}.} \end{functions} -I processi non privilegiati possono cambiare uno qualunque degli -identificatori usando uno qualunque dei valori correnti di \textit{real id}, -\textit{effective id} o \textit{saved id}, l'amministratore può specificare i -valori che vuole; un valore di -1 per un qualunque parametro lascia inalterato -l'identificatore corrispondente. +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. + +\subsection{Le funzioni \func{setresuid} e \func{setresgid}} +\label{sec:proc_setresuid} +Le due funzioni \funcd{setresuid} e \funcd{setresgid} sono un'estensione +introdotta in Linux,\footnote{a partire dal kernel 2.1.44.} e permettono un +completo controllo su tutti e tre i gruppi di identificatori (\textit{real}, +\textit{effective} e \textit{saved}), i loro prototipi sono: +\begin{functions} +\headdecl{unistd.h} +\headdecl{sys/types.h} -\subsection{Le funzioni \func{seteuid} e \func{setegid}} -\label{sec:proc_seteuid} +\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}. -Queste funzioni sono un'estensione allo standard POSIX.1 (ma sono comunque -supportate dalla maggior parte degli unix) e usate per cambiare gli -\textit{effective id}; i loro prototipi sono: +\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 parametro +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 seteuid(uid\_t uid)} Setta l'\textit{effective user ID} del -processo corrente a \var{uid}. - -\funcdecl{int setegid(gid\_t gid)} Setta l'\textit{effective group ID} del -processo corrente a \var{gid}. +\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 è \macro{EPERM}.} +\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} -Gli utenti normali possono settare l'\textit{effective id} solo al valore del -\textit{real id} o del \textit{saved id}, l'amministratore può specificare -qualunque valore. Queste funzioni sono usate per permettere a root di settare -solo l'\textit{effective id}, dato che l'uso normale di \func{setuid} comporta -il settaggio di tutti gli identificatori. - +Anche queste funzioni sono un'estensione specifica di Linux, e non richiedono +nessun privilegio. I valori sono restituiti negli argomenti, che vanno +specificati come puntatori (è un altro esempio di \textit{value result + argument}). Si noti che queste funzioni sono le uniche in grado di leggere +gli identificatori del gruppo \textit{saved}. + \subsection{Le funzioni \func{setfsuid} e \func{setfsgid}} \label{sec:proc_setfsuid} -Queste funzioni sono usate per settare gli identificatori usati da Linux per -il controllo dell'accesso ai file. Come già accennato in -\secref{sec:proc_user_group} in Linux è definito questo ulteriore gruppo di -identificatori, che di norma sono assolutamente equivalenti agli -\textit{effective id}, dato che ogni cambiamento di questi ultimi viene -immediatamente riportato sui \textit{filesystem id}. - -C'è un solo caso in cui si ha necessità di introdurre una differenza fra -\textit{effective id} e \textit{filesystem id}, 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'\textit{effective id} o il \textit{real id} il server si -espone alla ricezione di eventuali segnali ostili da parte dell'utente di cui -ha temporaneamente assunto l'identità. Cambiando solo il \textit{filesystem - id} si ottengono i privilegi necessari per accedere ai file, mantenendo -quelli originari per quanto riguarda tutti gli altri controlli di accesso. - -Le due funzioni usate per cambiare questi identificatori sono \func{setfsuid} -e \func{setfsgid}, ovviamente sono specifiche di Linux e non devono essere +Queste funzioni servono per impostare gli identificatori del gruppo +\textit{filesystem} che sono usati da Linux per il controllo dell'accesso ai +file. Come già accennato in \secref{sec:proc_access_id} Linux definisce +questo ulteriore gruppo di identificatori, che in circostanze normali sono +assolutamente equivalenti a quelli del gruppo \textit{effective}, dato che +ogni cambiamento di questi ultimi viene immediatamente riportato su di essi. + +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)} Setta il \textit{filesystem user ID} del -processo corrente a \var{fsuid}. +\funcdecl{int setfsuid(uid\_t fsuid)} Imposta l'user-ID di filesystem del +processo corrente a \param{fsuid}. -\funcdecl{int setfsgid(gid\_t fsgid)} Setta l'\textit{filesystem group ID} del -processo corrente a \var{fsgid}. +\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 è \macro{EPERM}.} + 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 \func{setgroups} e \func{getgroups}} +\label{sec:proc_setgroups} + +Le ultime funzioni che esamineremo sono quelle che permettono di operare sui +gruppi supplementari. Ogni processo può avere fino a \const{NGROUPS\_MAX} +gruppi supplementari in aggiunta al gruppo primario, questi vengono ereditati +dal processo padre e possono essere cambiati con queste funzioni. + +La funzione che permette di leggere i gruppi supplementari è +\funcd{getgroups}; questa funzione è definita nello standard POSIX ed il suo +prototipo è: +\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 +\secref{sec:sys_user_group}). Ritorna poi in \param{groups} la lista di quelli +a cui l'utente appartiene. Si noti che \param{ngroups} è passato come +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} + +La funzione imposta i gruppi supplementari del processo corrente ai valori +specificati nel vettore passato con l'argomento \param{list}, di dimensioni +date dall'argomento \param{size}. Il numero massimo di gruppi supplementari è +un parametro di sistema, che può essere ricavato con le modalità spiegate in +\secref{sec:sys_characteristics}. + +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. + + +\section{La gestione della priorità di esecuzione} +\label{sec:proc_priority} + +In questa sezione tratteremo più approfonditamente i meccanismi con il quale +lo \textit{scheduler}\index{scheduler} assegna la CPU ai vari processi attivi. +In particolare prenderemo in esame i vari meccanismi con cui viene gestita +l'assegnazione del tempo di CPU, ed illustreremo le varie funzioni di +gestione. + + +\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 +\secref{sec:proc_hierarchy} questa scelta viene eseguita da una sezione +apposita del kernel, lo \textit{scheduler}\index{scheduler}, il cui scopo è +quello di distribuire al meglio il tempo di CPU fra i vari processi. + +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 +\tabref{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. + +\begin{table}[htb] + \footnotesize + \centering + \begin{tabular}[c]{|p{2.8cm}|c|p{10cm}|} + \hline + \textbf{Stato} & \texttt{STAT} & \textbf{Descrizione} \\ + \hline + \hline + \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{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} + +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 +\secref{sec:proc_sched_stand}) è sempre stato basato su delle \textsl{priorità + dinamiche}, in modo da assicurare che tutti i processi, anche i meno +importanti, possano ricevere un po' di tempo di CPU. In sostanza quando un +processo ottiene la CPU la sua priorità viene diminuita. In questo modo alla +fine, anche un processo con priorità iniziale molto bassa, finisce per avere +una priorità sufficiente per essere eseguito. + +Lo standard POSIX.1b però ha introdotto il concetto di \textsl{priorità + assoluta}, (chiamata anche \textsl{priorità statica}, in contrapposizione +alla normale priorità dinamica), per tenere conto dei sistemi +real-time,\footnote{per sistema real-time si intende un sistema in grado di + eseguire operazioni in 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 +\secref{sec:proc_real_time}. + +In generale quello che succede in tutti gli Unix moderni è che ai processi +normali viene sempre data una priorità assoluta pari a zero, e la decisione di +assegnazione della CPU è fatta solo 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, 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{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. + + \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 +\secref{sec:sess_proc_group}) o di un utente, specificando un corrispondente +valore per \param{who}; un valore nullo di quest'ultimo indica il processo, il +gruppo di processi o l'utente correnti. + +\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. + +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 \secref{sec:proc_sched} lo standard POSIX.1b ha introdotto le +priorità assolute per permettere la gestione di processi real-time. In realtà +nel caso di Linux non si tratta di un vero hard real-time, in quanto in +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{page fault} si possono avere ritardi non previsti. Se +l'ultimo problema può essere aggirato attraverso l'uso delle funzioni di +controllo della memoria virtuale (vedi \secref{sec:proc_mem_lock}), il primo +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{scheduler} lo +metterà in esecuzione prima di ogni processo normale. In caso di più processi +sarà eseguito per primo quello con priorità assoluta più alta. Quando ci sono +più processi con la stessa priorità assoluta questi vengono tenuti in una coda +e tocca al kernel decidere quale deve essere eseguito. +Il meccanismo con cui vengono gestiti questi processi dipende dalla politica +di scheduling che si è scelto; lo standard ne prevede due: +\begin{basedescript}{\desclabelwidth{1.2cm}\desclabelstyle{\nextlinelabel}} +\item[\textit{FIFO}] \textit{First In First Out}. Il processo viene eseguito + fintanto che non cede volontariamente la CPU, si blocca, finisce o viene + interrotto da un processo a priorità più alta. +\item[\textit{RR}] \textit{Round Robin}. Ciascun processo viene eseguito a + turno per un certo periodo di tempo (una \textit{time slice}). Solo i + processi con la stessa priorità ed in stato \textit{runnable} entrano nel + circolo. +\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 (vale solo per \const{SCHED\_FIFO} e + \const{SCHED\_RR}). + \end{errlist}} +\end{prototype} + +La funzione esegue l'impostazione per il processo specificato dall'argomento +\param{pid}; un valore nullo esegue l'impostazione per il processo corrente. +Solo un processo con i privilegi di amministratore può impostare delle +priorità assolute diverse da zero. La politica di scheduling è specificata +dall'argomento \param{policy} i cui possibili valori sono riportati in +\tabref{tab:proc_sched_policy}; un valore negativo per \param{policy} mantiene +la politica di scheduling corrente. + +\begin{table}[htb] + \centering + \footnotesize + \begin{tabular}[c]{|c|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 \figref{fig:sig_sched_param}), il cui solo +campo attualmente definito è \var{sched\_priority}, che nel caso delle +priorità assolute deve essere specificato nell'intervallo fra un valore +massimo ed uno minimo, che nel caso sono rispettivamente 1 e 99 (il valore +zero è legale, ma indica i processi normali). + +\begin{figure}[!htb] + \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} + +Lo standard POSIX.1b prevede comunque che i due valori della massima e minima +priorità statica possano essere ottenuti, per ciascuna delle politiche di +scheduling realtime, tramite le due funzioni \funcd{sched\_get\_priority\_max} +e \funcd{sched\_get\_priority\_min}, i cui prototipi sono: +\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} è invalido. + \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. + +La priorità assoluta può essere riletta indietro dalla funzione +\funcd{sched\_getscheduler}, il cui prototipo è: +\begin{prototype}{sched.h} +{int sched\_getscheduler(pid\_t pid)} + Legge la politica di scheduling per il processo \param{pid}. + + \bodydesc{La funzione ritorna la politica di scheduling in caso di successo + e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EINVAL}] il valore di \param{pid} è negativo. + \end{errlist}} +\end{prototype} + +La funzione restituisce il valore (secondo quanto elencato in +\tabref{tab:proc_sched_policy}) della politica di scheduling per il processo +specificato; se \param{pid} è nullo viene restituito quello del processo +chiamante. + +Se si intende operare solo sulla priorità assoluta di un processo si possono +usare le funzioni \funcd{sched\_setparam} e \funcd{sched\_getparam}, i cui +prototipi sono: +\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{pid} è negativo. + \end{errlist}} \end{functions} -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 \textit{real}, \textit{effective} o \textit{saved id}. +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}. + +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 0in 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 \figref{fig:sys_timeval_struct}). + + +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. \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 multiutente -occorre tenere conto di tutta una serie di problematiche che normalmente non +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. +programma alla volta. -Pur non essendo tutto questo direttamente legato alla modalità specifica in -cui il multitasking è implementato in un sistema unix-like, né al solo -concetto di multitasking (le stesse problematiche si presentano ad esempio -nella gestione degli interrupt hardware), in questa sezione conclusiva del -capitolo in cui abbiamo affrontato la gestione dei processi, introdurremo -sinteticamente queste problematiche, che ritroveremo a più riprese in capitoli -successivi, con una breve definizione della terminologia e delle loro -caratteristiche di fondo. +Pur essendo questo argomento di carattere generale, ci è parso opportuno +introdurre sinteticamente queste problematiche, che ritroveremo a più riprese +in capitoli successivi, in questa sezione conclusiva del capitolo in cui +abbiamo affrontato la gestione dei processi. \subsection{Le operazioni atomiche} \label{sec:proc_atom_oper} La nozione di \textsl{operazione atomica} deriva dal significato greco della -parola atomo, cioè indivisibile; si dice infatti che una operazione è atomica +parola atomo, cioè indivisibile; si dice infatti che un'operazione è atomica quando si ha la certezza che, qualora essa venga effettuata, tutti i passaggi che devono essere compiuti per realizzarla verranno eseguiti senza possibilità di interruzione in una fase intermedia. @@ -1691,14 +2385,15 @@ 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} (vedi +accorti nei confronti delle possibili +\textit{race condition}\index{race condition} (vedi \secref{sec:proc_race_cond}) derivanti da operazioni interrotte in una fase in cui non erano ancora state completate. Nel caso dell'interazione fra processi la situazione è molto più semplice, ed occorre preoccuparsi della atomicità delle operazioni solo quando si ha a che fare con meccanismi di intercomunicazione (che esamineremo in dettaglio in -\capref{cha:IPC}) o nella operazioni con i file (vedremo alcuni esempi in +\capref{cha:IPC}) o nelle operazioni con i file (vedremo alcuni esempi in \secref{sec:file_atomic}). In questi casi in genere l'uso delle appropriate funzioni di libreria per compiere le operazioni necessarie è garanzia sufficiente di atomicità in quanto le system call con cui esse sono realizzate @@ -1708,65 +2403,115 @@ 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 -solo il solo accesso o l'assegnazione di una variabile possono non essere più -operazioni atomiche (torneremo su questi aspetti in \secref{sec:sign_xxx}). +sono compiute nello stesso spazio di indirizzi del processo. Per questo, anche +il solo accesso o l'assegnazione di una variabile possono non essere più +operazioni atomiche (torneremo su questi aspetti in +\secref{sec:sig_control}). In questo caso il sistema provvede un tipo di dato, il \type{sig\_atomic\_t}, il cui accesso è assicurato essere atomico. In pratica comunque si può -assumere che in ogni piattaforma su cui è implementato Linux il tipo -\type{int} (e gli altri interi di dimensione inferiore) ed i puntatori sono +assumere che, in ogni piattaforma su cui è implementato Linux, il tipo +\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 questi casi è anche opportuno marcare come \type{volatile} le -variabili che possono essere interessate ad accesso condiviso, onde evitare -problemi con le ottimizzazioni del codice. +le strutture. In tutti questi casi è anche opportuno marcare come +\direct{volatile} le variabili che possono essere interessate ad accesso +condiviso, onde evitare problemi con le ottimizzazioni del codice. -\subsection{Le \textit{race condition} e i \textit{deadlock}} -\label{sec:proc_race_cond} -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 rispetto agli altri, 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. +\subsection{Le \textit{race condition}\index{race condition} e i + \textit{deadlock}\index{deadlock}} +\label{sec:proc_race_cond} -Casi tipici di \textit{race condition} si hanno quando diversi processi +Si definiscono \textit{race condition}\index{race condition} tutte quelle +situazioni in cui processi diversi operano su una risorsa comune, ed in cui il +risultato viene a dipendere dall'ordine in cui essi effettuano le loro +operazioni. Il caso tipico è quello di un'operazione che viene eseguita da un +processo in più passi, e può essere compromessa dall'intervento di un altro +processo che accede alla stessa risorsa quando ancora non tutti i passi sono +stati completati. + +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}\index{race condition} si hanno quando diversi processi accedono allo stesso file, o nell'accesso a meccanismi di intercomunicazione come la memoria condivisa. In questi casi, se non si dispone della possibilità -di eseguire atomicamente le operazioni necessarie, occorre che le risorse -condivise siano opportunamente protette da meccanismi di sincronizzazione -(torneremo su queste problematiche di questo tipo in \secref{sec:ipc_semaph}). - -Un caso particolare di \textit{race condition} sono poi i cosiddetti -\textit{deadlock}; l'esempio tipico è quello di un flag di ``occupazione'' che -viene rilasciato da un evento asincrono fra il controllo (in cui viene trovato -occupato) e la successiva messa in attesa, che a questo punto diventerà -perpetua (da cui il nome di \textit{deadlock}) in quanto l'evento di sblocco -del flag è stato perso fra il controllo e la messa in attesa. +di eseguire atomicamente le operazioni necessarie, occorre che quelle parti di +codice in cui si compiono le operazioni sulle risorse condivise (le cosiddette +\textsl{sezioni critiche}\index{sezioni critiche}) del programma, siano +opportunamente protette da meccanismi di sincronizzazione (torneremo su queste +problematiche di questo tipo in \capref{cha:IPC}). + +Un caso particolare di \textit{race condition}\index{race condition} sono poi +i cosiddetti \textit{deadlock}\index{deadlock}, particolarmente gravi in +quanto comportano spesso il blocco completo di un servizio, e non il +fallimento di una singola operazione. Per definizione un +\textit{deadlock}\index{deadlock} è una situazione in cui due o più processi +non sono più in grado di proseguire perché ciascuno aspetta il risultato di +una operazione che dovrebbe essere eseguita dall'altro. + + +L'esempio tipico di una situazione che può condurre ad un +\textit{deadlock}\index{deadlock} è quello in cui un flag di +``\textsl{occupazione}'' viene rilasciato da un evento asincrono (come un +segnale o un altro processo) fra il momento in cui lo si è controllato +(trovandolo occupato) e la successiva operazione di attesa per lo sblocco. In +questo caso, dato che l'evento di sblocco del flag è avvenuto senza che ce ne +accorgessimo proprio fra il controllo e la messa in attesa, quest'ultima +diventerà perpetua (da cui il nome di \textit{deadlock}\index{deadlock}). + +In tutti questi casi è di fondamentale importanza il concetto di atomicità +visto in \secref{sec:proc_atom_oper}; questi problemi infatti possono essere +risolti soltanto assicurandosi, quando essa sia richiesta, che sia possibile +eseguire in maniera atomica le operazioni necessarie. \subsection{Le funzioni rientranti} \label{sec:proc_reentrant} -Si dice rientrante una funzione che può essere interrotta in qualunque momento -ed essere chiamata da capo (da questo il nome) da un altro filone di -esecuzione (thread e manipolatori di segnali sono i casi in cui occorre -prestare attenzione a questa problematica) senza che questo comporti nessun -problema. - -In genere una funzione non è rientrante se opera direttamente su memoria che -non è nello stack. Ad esempio una funzione non è rientrante se usa una -variabile globale o statica od un oggetto allocato dinamicamente che trova da -sola: due chiamate alla stessa funzione interferiranno. 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. - -Le glibc mettono a disposizione due macro di compilatore \macro{\_REENTRANT} e -\macro{\_THREAD\_SAFE} per assicurare che siano usate delle versioni rientranti -delle funzioni di libreria. - +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: