X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=4bb15c65003e9fa563cdb18c427f77ad092e6efa;hp=f3298c0faab606ec18a27d234db73eb9305b5f91;hb=e99034c22f44153d9260bd9075319a3804ab91a9;hpb=96e11f1878552c4c4f245448e9dc6d66e1658542 diff --git a/prochand.tex b/prochand.tex index f3298c0..4bb15c6 100644 --- a/prochand.tex +++ b/prochand.tex @@ -109,9 +109,10 @@ Dato che tutti i processi attivi nel sistema sono comunque generati da possono classificare i processi con la relazione padre/figlio in un'organizzazione gerarchica ad albero, in maniera analoga a come i file sono organizzati in un albero di directory (si veda -\secref{sec:file_organization}); in \curfig\ si è mostrato il risultato del -comando \cmd{pstree} che permette di visualizzare questa struttura, alla cui -base c'è \cmd{init} che è progenitore di tutti gli altri processi. +\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 nella @@ -120,7 +121,7 @@ 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 \type{task\_struct} (che in seguito incontreremo a più -riprese), è mostrato in \nfig. +riprese), è mostrato in \figref{fig:proc_task_struct}. \begin{figure}[htb] \centering @@ -131,21 +132,22 @@ riprese), \end{figure} -Come accennato in \secref{sec:intro_unix_struct} è lo \textit{scheduler} che -decide quale processo mettere in esecuzione; esso viene eseguito ad ogni -system call ed ad ogni interrupt,\footnote{più in una serie di altre - occasioni. NDT completare questa parte.} (ma può essere anche attivato -esplicitamente). Il timer di sistema provvede comunque a che esso sia invocato -periodicamente, generando un interrupt periodico secondo la frequenza -specificata dalla costante \macro{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}).} +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 +\macro{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} effettua il calcolo delle -priorità dei vari processi attivi (torneremo su questo in +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. @@ -219,12 +221,14 @@ Il \acr{pid} viene assegnato in forma progressiva 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 32767. Oltre questo valore l'assegnazione riparte dal numero più basso disponibile a -partire da un minimo di 300,\footnote{questi valori sono definiti dalla macro - \macro{PID\_MAX} in \file{threads.h} e direttamente in \file{fork.c} nei - sorgenti del kernel.} che serve a riservare i \acr{pid} più bassi ai processi -eseguiti dal direttamente dal kernel. Per questo motivo, come visto in -\secref{sec:proc_hierarchy}, il processo di avvio (\cmd{init}) ha sempre il -\acr{pid} uguale a uno. +partire da un minimo di 300,\footnote{questi valori, fino al kernel 2.4.x, + sono definiti dalla macro \macro{PID\_MAX} in \file{threads.h} e + direttamente in \file{fork.c}, con il kernel 2.5.x e la nuova interfaccia + per i thread creata da Ingo Molnar anche il meccanismo di allocazione dei + \acr{pid} è stato modificato.} che serve a riservare i \acr{pid} più bassi +ai processi eseguiti dal direttamente dal kernel. Per questo motivo, come +visto in \secref{sec:proc_hierarchy}, il processo di avvio (\cmd{init}) ha +sempre il \acr{pid} uguale a uno. Tutti i processi inoltre memorizzano anche il \acr{pid} del genitore da cui sono stati creati, questo viene chiamato in genere \acr{ppid} (da @@ -456,15 +460,15 @@ Go to next child Esaminiamo questo risultato: una prima conclusione che si può trarre è che non si può dire quale processo fra il padre ed il figlio venga eseguito per primo\footnote{a partire dal kernel 2.5.2-pre10 è stato introdotto il nuovo - scheduler di Ingo Molnar che esegue sempre per primo il figlio; per - mantenere la portabilità è opportuno non fare comunque affidamento su questo - comportamento.} dopo la chiamata a \func{fork}; dall'esempio si può notare -infatti come nei primi due cicli sia stato eseguito per primo il padre (con la -stampa del \acr{pid} del nuovo processo) per poi passare all'esecuzione del -figlio (completata con i due avvisi di esecuzione ed uscita), e tornare -all'esecuzione del padre (con la stampa del passaggio al ciclo successivo), -mentre la terza volta è stato prima eseguito il figlio (fino alla conclusione) -e poi il padre. + scheduler\index{scheduler} di Ingo Molnar che esegue sempre per primo il + figlio; per mantenere la portabilità è opportuno non fare comunque + affidamento su questo comportamento.} dopo la chiamata a \func{fork}; +dall'esempio si può notare infatti come nei primi due cicli sia stato eseguito +per primo il padre (con la stampa del \acr{pid} del nuovo processo) per poi +passare all'esecuzione del figlio (completata con i due avvisi di esecuzione +ed uscita), e tornare all'esecuzione del padre (con la stampa del passaggio al +ciclo successivo), mentre la terza volta è stato prima eseguito il figlio +(fino alla conclusione) e poi il padre. In generale l'ordine di esecuzione dipenderà, oltre che dall'algoritmo di scheduling usato dal kernel, dalla particolare situazione in si trova la @@ -689,13 +693,14 @@ eseguite alla chiusura di un processo \cmd{init}). \item viene inviato il segnale \macro{SIGCHLD} al processo padre (vedi \secref{sec:sig_sigchld}). -\item se il processo è un leader di sessione viene mandato un segnale di - \macro{SIGHUP} a tutti i processi in background e il terminale di - controllo viene disconnesso (vedi \secref{sec:sess_xxx}). +\item se il processo è un leader di sessione ed il suo terminale di controllo + è quello della sessione viene mandato un segnale di \macro{SIGHUP} a tutti i + processi del gruppo di foreground e il terminale di controllo viene + disconnesso (vedi \secref{sec:sess_ctrl_term}). \item se la conclusione di un processo rende orfano un \textit{process group} ciascun membro del gruppo viene bloccato, e poi gli vengono inviati in successione i segnali \macro{SIGHUP} e \macro{SIGCONT} - (vedi \secref{sec:sess_xxx}). + (vedi ancora \secref{sec:sess_ctrl_term}). \end{itemize*} Oltre queste operazioni è però necessario poter disporre di un meccanismo @@ -767,15 +772,15 @@ informazioni riguardo ai processi che sta terminando. Questo viene fatto mantenendo attiva la voce nella tabella dei processi, e memorizzando alcuni dati essenziali, come il \acr{pid}, i tempi di CPU usati -dal processo (vedi \secref{sec:sys_unix_time}) e lo stato di -terminazione\footnote{NdA verificare esattamente cosa c'è!}, mentre la memoria -in uso ed i file aperti vengono rilasciati immediatamente. I processi che sono -terminati, ma il cui stato di terminazione non è stato ancora ricevuto dal -padre sono chiamati \textit{zombie}, essi restano presenti nella tabella dei -processi ed in genere possono essere identificati dall'output di \cmd{ps} per -la presenza di una \texttt{Z} nella colonna che ne indica lo stato. Quando il -padre effettuerà la lettura dello stato di uscita anche questa informazione, -non più necessaria, verrà scartata e la terminazione potrà dirsi completamente +dal processo (vedi \secref{sec:sys_unix_time}) e lo stato di terminazione, +mentre la memoria in uso ed i file aperti vengono rilasciati immediatamente. I +processi che sono terminati, ma il cui stato di terminazione non è stato +ancora ricevuto dal padre sono chiamati \textit{zombie}, essi restano presenti +nella tabella dei processi ed in genere possono essere identificati +dall'output di \cmd{ps} per la presenza di una \texttt{Z} nella colonna che ne +indica lo stato (vedi \tabref{tab:proc_proc_states}). Quando il padre +effettuerà la lettura dello stato di uscita anche questa informazione, non più +necessaria, verrà scartata e la terminazione potrà dirsi completamente conclusa. Possiamo utilizzare il nostro programma di prova per analizzare anche questa @@ -907,7 +912,7 @@ specchietto riportato in \tabref{tab:proc_waidpid_pid}: \hline \hline $<-1$& -- & attende per un figlio il cui \textit{process group} (vedi - \ref{sec:sess_proc_group}) è uguale al + \secref{sec:sess_proc_group}) è uguale al valore assoluto di \var{pid}. \\ $-1$ & \macro{WAIT\_ANY} & attende per un figlio qualsiasi, usata in questa maniera è equivalente a \func{wait}.\\ @@ -925,12 +930,16 @@ specchietto riportato in \tabref{tab:proc_waidpid_pid}: 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 \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 \secref{sec:sess_job_control}) 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. +quando il processo figlio non è terminato, e \macro{WUNTRACED}. Quest'ultimo +viene generalmente usato per il controllo di sessione, (trattato in +\secref{sec:sess_job_control}) in quanto permette di identificare i processi +bloccati. La funzione infatti in tal caso ritorna, restituendone il \acr{pid}, +se c'è un processo figlio che è entrato in stato di sleep (vedi +\tabref{tab:proc_proc_states}) di cui non si è ancora letto lo stato (con +questa stessa opzione). Il valore dell'opzione deve essere specificato come +maschera binaria ottenuta con l'OR delle suddette costanti con zero. In Linux +sono previste altre opzioni non standard relative al comportamento con i +thread, che saranno trattate 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 @@ -1111,16 +1120,17 @@ 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 +\var{argv} e \var{argc} visti dalla funzione \func{main} del programma +chiamato). 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 @@ -1184,7 +1194,7 @@ indicato dal parametro \var{path}, che viene interpretato come il \begin{figure}[htb] \centering - \includegraphics[width=13cm]{img/exec_rel} + \includegraphics[width=15cm]{img/exec_rel} \caption{La interrelazione fra le sei funzioni della famiglia \func{exec}.} \label{fig:proc_exec_relat} \end{figure} @@ -1206,7 +1216,7 @@ la lista completa supplementari} (vedi \secref{sec:proc_access_id}). \item il \textit{session id} (\acr{sid}) ed il \textit{process groupid} (\acr{pgid}), vedi \secref{sec:sess_proc_group}. -\item il terminale di controllo (vedi \secref{sec:sess_xxx}). +\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}). @@ -1287,8 +1297,12 @@ problematiche connesse ad una gestione accorta dei privilegi. Come accennato in \secref{sec:intro_multiuser} il modello base\footnote{in realtà già esistono estensioni di questo modello base, che lo rendono più flessibile e controllabile, come le \textit{capabilities}, le ACL per i file - o il \textit{Mandatory Access Control} di SELinux.} di sicurezza di un -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}, ol 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. @@ -1479,10 +1493,10 @@ all'\textsl{userid salvato}. Negli altri casi viene segnalato un errore (con \macro{EPERM}). Come accennato l'uso principale di queste funzioni è quello di poter -consentire ad un programma con i bit \acr{suid} o \acr{sgid} impostati di -riportare l'\textsl{userid effettivo} a quello dell'utente che ha lanciato il -programma, effettuare il lavoro che non necessita di privilegi aggiuntivi, ed -eventualmente tornare indietro. +consentire ad un programma con i bit \acr{suid} o \acr{sgid} impostati (vedi +\secref{sec:file_suid_sgid}) di riportare l'\textsl{userid effettivo} a quello +dell'utente che ha lanciato il programma, effettuare il lavoro che non +necessita di privilegi aggiuntivi, ed eventualmente tornare indietro. Come esempio per chiarire l'uso di queste funzioni prendiamo quello con cui viene gestito l'accesso al file \file{/var/log/utmp}. In questo file viene @@ -1743,8 +1757,8 @@ questa funzione \param{size}. \bodydesc{La funzione restituisce il numero di gruppi letti in caso di - successo e -1 in caso di fallimento, nel qual caso \var{errno} viene - impostata a: + successo e -1 in caso di fallimento, nel qual caso \var{errno} assumerà + i valori: \begin{errlist} \item[\macro{EFAULT}] \param{list} non ha un indirizzo valido. \item[\macro{EINVAL}] il valore di \param{size} è diverso da zero ma @@ -1785,7 +1799,7 @@ delle due supplementari del processo ai valori specificati in \param{list}. \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di - fallimento, nel qual caso \var{errno} viene impostata a: + fallimento, nel qual caso \var{errno} assumerà i valori: \begin{errlist} \item[\macro{EFAULT}] \param{list} non ha un indirizzo valido. \item[\macro{EPERM}] il processo non ha i privilegi di amministratore. @@ -1805,7 +1819,7 @@ un utente specifico, si pu aggiungendo il gruppo addizionale \param{group}. \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di - fallimento, nel qual caso \var{errno} viene impostata agli stessi valori di + fallimento, nel qual caso \var{errno} assumerà gli stessi valori di \func{setgroups} più \macro{ENOMEM} quando non c'è memoria sufficiente per allocare lo spazio per informazioni dei gruppi.} \end{functions} @@ -1824,9 +1838,10 @@ quando si definisce \macro{\_POSIX\_SOURCE} o si compila con il flag \label{sec:proc_priority} In questa sezione tratteremo più approfonditamente i meccanismi con il quale -lo \textit{scheduler} assegna la CPU ai vari processi attivi. In particolare -prenderemo in esame i vari meccanismi con cui viene gestita l'assegnazione del -tempo di CPU, ed illustreremo le varie funzioni di gestione. +lo \textit{scheduler}\index{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}} @@ -1844,8 +1859,8 @@ 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}, il cui scopo è quello di -distribuire al meglio il tempo di CPU fra i vari processi. +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 @@ -1874,6 +1889,7 @@ sia la sua priorit 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 @@ -1972,16 +1988,17 @@ viene assegnato ad un altro campo della struttura (\var{counter}) quando il processo viene eseguito per la prima volta e diminuito progressivamente ad ogni interruzione del timer. -Quando lo scheduler viene eseguito scandisce la coda dei processi in stato -\textit{runnable} associando, sulla base del valore di \var{counter}, un peso -a ciascun processo in attesa di esecuzione,\footnote{il calcolo del peso in - realtà è un po' più complicato, ad esempio nei sistemi multiprocessore viene - favorito un processo che è eseguito sulla stessa CPU, e a parità del valore - di \var{counter} viene favorito chi ha una priorità più elevata.} chi ha il -peso più alto verrà posto in esecuzione, ed il precedente processo sarà -spostato in fondo alla coda. Dato che ad ogni interruzione del timer il -valore di \var{counter} del processo corrente viene diminuito, questo assicura -che anche i processi con priorità più bassa verranno messi in esecuzione. +Quando lo scheduler\index{scheduler} viene eseguito scandisce la coda dei +processi in stato \textit{runnable} associando, sulla base del valore di +\var{counter}, un peso a ciascun processo in attesa di esecuzione,\footnote{il + calcolo del peso in realtà è un po' più complicato, ad esempio nei sistemi + multiprocessore viene favorito un processo che è eseguito sulla stessa CPU, + e a parità del valore di \var{counter} viene favorito chi ha una priorità + più elevata.} chi ha il peso più alto verrà posto in esecuzione, ed il +precedente processo sarà spostato in fondo alla coda. Dato che ad ogni +interruzione del timer il valore di \var{counter} del processo corrente viene +diminuito, questo assicura che anche i processi con priorità più bassa +verranno messi in esecuzione. La priorità di un processo è così controllata attraverso il valore di \var{nice}, che stabilisce la durata della \textit{time-slice}; per il @@ -2103,9 +2120,10 @@ priorit 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 o RTAI, con i quali è possibile + 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, e gestiti + interrupt vengono intercettati dall'interfaccia real-time (o nel caso di + Adeos gestiti dalle code del nano-kernel), in modo da poterlo 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 @@ -2123,23 +2141,22 @@ quando si lavora con processi che usano priorit shell cui si sia assegnata la massima priorità assoluta, in modo da poter essere comunque in grado di rientrare nel sistema. -Quando c'è un processo con priorità assoluta lo scheduler lo metterà in -esecuzione prima di ogni processo normale. In caso di più processi sarà -eseguito per primo quello con priorità assoluta più alta. Quando ci sono più -processi con la stessa priorità assoluta questi vengono tenuti in una coda -tocca al kernel decidere quale deve essere eseguito. - - +Quando c'è un processo con priorità assoluta lo scheduler\index{scheduler} lo +metterà in esecuzione prima di ogni processo normale. In caso di più processi +sarà eseguito per primo quello con priorità assoluta più alta. Quando ci sono +più processi con la stessa priorità assoluta questi vengono tenuti in una coda +tocca al kernel decidere quale deve essere eseguito. Il meccanismo con cui vengono gestiti questi processi dipende dalla politica di scheduling che si è scelto; lo standard ne prevede due: -\begin{basedescript}{\desclabelwidth{3cm}\desclabelstyle{\nextlinelabel}} -\item[\textit{FIFO}] 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{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. +\begin{basedescript}{\desclabelwidth{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