X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=cc64ab80a9210683e22b5e6b6bf61cdf0a1d6356;hp=38a9b2f18799685c4e5c23a94ce5279a33f37521;hb=6a94b0f6f36bad6daf7b8d3f52d211661a135836;hpb=9aeb46d93d970f26f1939d3853e4a9e62b2eb5b9 diff --git a/prochand.tex b/prochand.tex index 38a9b2f..cc64ab8 100644 --- a/prochand.tex +++ b/prochand.tex @@ -103,23 +103,22 @@ 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 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. - + 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 \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. Il kernel mantiene una tabella dei processi attivi, la cosiddetta \textit{process table}; per ciascun processo viene mantenuta una voce nella tabella dei processi costituita da una struttura \type{task\_struct}, che contiene tutte le informazioni rilevanti per quel processo. Tutte le strutture usate a questo scopo sono dichiarate nell'header file \file{linux/sched.h}, ed -uno schema semplificato che riporta la struttura delle principali informazioni +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. @@ -137,9 +136,10 @@ decide quale processo mettere in esecuzione; esso viene eseguito ad ogni system call ed ad ogni interrupt, (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} Il -valore usuale è 100 (è espresso in Hertz), si ha cioè un interrupt dal timer -ogni centesimo di secondo. +specificata dalla costante \macro{HZ}, definita in \file{asm/param.h}. Il +valore usuale è 100\footnote{è così per tutte le architetture eccetto l'alpha, + per la quale è 1000} ed è espresso in Hertz. 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 @@ -213,11 +213,15 @@ intero con segno (nel caso di Linux e delle \acr{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\footnote{FIXME: verificare, non sono - sicuro}. Per questo motivo, come visto in \secref{sec:proc_hierarchy}, il -processo di avvio (\cmd{init}) ha sempre il \acr{pid} uguale a uno. +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. Tutti i processi inoltre memorizzano anche il \acr{pid} del genitore da cui sono stati creati, questo viene chiamato in genere \acr{ppid} (da @@ -449,7 +453,7 @@ si pu 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 + 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 @@ -678,7 +682,7 @@ eseguite alla chiusura di un processo \item ad ogni processo figlio viene assegnato un nuovo padre (in genere \cmd{init}). \item viene inviato il segnale \macro{SIGCHLD} al processo padre (vedi - \secref{sec:sig_xxx}). + \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}). @@ -795,7 +799,7 @@ 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 +\func{wait}, vedi \secref{sec:sig_sigchld} 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. @@ -846,7 +850,7 @@ segnale termina il processo o chiama una funzione di gestione. processo figlio termina. Se un figlio è già terminato la funzione ritorna immediatamente. -Al ritorno lo stato di termininazione del processo viene salvato nella +Al ritorno lo stato di terminazione 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 @@ -890,18 +894,18 @@ specchietto riportato in \ntab: \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{Macro} &\textbf{Significato}\\ \hline \hline - $<-1$& attende per un figlio il cui \textit{process group} è uguale al + $<-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 + $-1$ & \macro{WAIT\_ANY} & attende per un figlio qualsiasi, usata in + questa maniera è equivalente a \func{wait}.\\ + $0$ & \macro{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 \var{pid}.\\ \hline \end{tabular} @@ -960,9 +964,9 @@ avremo la certezza che la chiamata a \func{wait} non si bloccher \macro{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{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}.\\ + sia in Linux che in altri Unix.}\\ \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}. \\ @@ -1204,7 +1208,7 @@ la lista completa \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 (vedi \secref{sec:sig_xxx}). +\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 @@ -1282,7 +1286,7 @@ 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 + o il \textit{Mandatory Access Control} di SELinux.} 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 @@ -1343,7 +1347,7 @@ rispettivamente \textit{real} ed \textit{effective}. \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} @@ -1818,7 +1822,7 @@ quando si definisce \macro{\_POSIX\_SOURCE} o si compila con il flag In questa sezione tratteremo più approfonditamente i meccanismi con il quale lo \textit{scheduler}\footnote{che è la parte del kernel che si occupa di stabilire quale processo dovrà essere posto in esecuzione.} assegna la CPU -ai vari processi attivi. In particolare prendremo in esame i vari meccanismi +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. @@ -1831,70 +1835,139 @@ il tempo di CPU per l'esecuzione dei processi ed oggetto di numerose ricerche; in ogni caso essa dipende in maniera essenziale anche dal tipo di utilizzo che deve essere fatto del sistema. -La cosa è resa ancora più complicata dal fatto che con le architetture -multi-processore si introduce anche la problematica dovuta alla scelta di -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 occorrono - meccanismi per determininare quale è la migliore scelta fra le diverse CPU.} -Tutto questo comunque appartiene alle sottigliezze dell'implementazione del -kernel, e dal punto di vista dei programmi che girano in user space anche -quando si hanno più processori, e quindi potenzialmente anche dei processi che -sono eseguiti davvero in contemporanea, si può pensare alle politiche di -scheduling come concernenti la risorsa \textsl{tempo di esecuzione}, la cui -assegnazione sarà governata dagli stessi meccanismi di scelta di priorità, -solo che nel caso di più processori sarà a disposizione di più di un processo -alla volta. - -Si tenga presente inoltre che l'utilizzo della CPU è soltanto una delle +Si tenga presente comunque che l'utilizzo della CPU è soltanto una delle risorse (insieme alla memoria e all'accesso alle periferiche) che sono necessarie per l'esecuzione di un programma, e spesso non è neanche la più importante. Per questo non è affatto detto che dare ad un programma la massima priorità di esecuzione abbia risultati significativi in termini di prestazioni. -La politica tradizionale di scheduling di Unix (che tratteremo in -\secref{sec:proc_sched_stand}) è sempre stata basata su delle priorità -dinamiche, che assicurassero che tutti i processi, anche i meno importanti, -potessero ricevere un po' di tempo di CPU. - -Lo standard POSIX però per tenere conto dei sistemi real-time,\footnote{per - sistema real-time si intende un sistema in grado di eseguire operazioni in - tempo reale; 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 processi che non hanno questa necessità, ha introdotto il concetto di -\textsl{priorità assoluta}, chimata anche \textsl{priorità statica}, in -contrapposizione con la normale priorità dinamica. - -Il concetto di prorità assoluta dice che quando due processi si contendono -l'esecuzione, vince sempre quello con la priorità assoluta più alta, anche, -grazie al \textit{prehemptive scheduling}, se l'altro è in esecuzione. -Ovviamente questo avviene solo per i processi che sono pronti per essere -eseguiti (cioè nello stato \textit{runnable}\footnote{lo stato di un processo +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 dallo +\textit{scheduler} il cui scopo è quello di distribuire al meglio il tempo di +CPU fra i vari processi. + +Il Linux un processo può trovarsi in uno degli stati riportati in + + + +\begin{table}[htb] + \centering + \begin{tabular}[c]{|l|c|p{8cm}|} + \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 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 + \macro{SIGSTOP}, o è tracciato.\\ + \textbf{Zombie} & \texttt{Z} & Il processo è terminato ma il suo stato di + terminazione non è ancora stato letto dal padre. \\ + \hline + \end{tabular} + \caption{Tabella degli stati possibili per processo in Linux, si sono + riportati nella colonna \texttt{STAT} i valori dello stato ottenibili + dall'output del comando \cmd{ps}.} + \label{tab:proc_proc_states} +\end{table} + + + + +Una delle caratteristiche c + +,\footnote{lo stato di un processo è riportato nel campo \texttt{STAT} dell'output del comando \cmd{ps}, abbiamo già visto che lo stato di \textit{zombie} è indicato con \texttt{Z}, gli stati \textit{runnable}, \textit{sleep} e di I/O (\textit{uninteruttible - sleep}) sono invece indicati con \texttt{R}, \texttt{S} e \texttt{D}.}), + sleep}) sono invece indicati con \texttt{R}, \texttt{S} e \texttt{D}.}) la priorità assoluta viene invece ignorata per quelli che sono bloccati su una -richiesta di I/O o in stato di \textit{sleep}. - -Questa viene in genere indicata con un numero - +richiesta di I/O o in stato di \textit{sleep} +La cosa è resa ancora più complicata dal fatto che con le architetture +multi-processore si introduce anche la problematica dovuta alla scelta di +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 occorrono + meccanismi per determinare quale è la migliore scelta fra le diverse CPU.} +Tutto questo comunque appartiene alle sottigliezze dell'implementazione del +kernel, e 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), si può pensare alle politiche di scheduling come concernenti +la risorsa \textsl{tempo di esecuzione}, la cui assegnazione sarà governata +dagli stessi meccanismi di scelta di priorità, solo che nel caso di più +processori sarà a disposizione di più di un processo alla volta. + + + + +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, anche +quando l'altro è in esecuzione (grazie al \textit{prehemptive scheduling}). +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 in base ad una priorità dinamica che è +calcolata indipendentemente. È 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} -In Linux tutti i processi hanno sostanzialmente la stessa priorità; benché sia -possibile specificare una priorità assoluta secondo lo standard POSIX -(argomento che tratteremo più avanti) l'uso comune segue quello che è il -meccanismo tradizionale con cui i sistemi +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. + + + \subsection{Il meccanismo di \textit{scheduling real-time}} \label{sec:proc_real_time} @@ -1902,6 +1975,17 @@ meccanismo tradizionale con cui i sistemi Per settare le +\footnote{a meno che non si siano installate le patch di RTLinux o RTAI, con i + quali è possibile ottenere un sistema effettivamente hard real-time.} + +in realtà non si tratta di un vero hard real-time, in quanto + la presenza di eventuali interrupt o di page fault può sempre interrompere + l'esecuzione di un processo, a meno di non installare le estensioni di + RTLinux o RTAI, il normale kernel non è real-time. + + + + \section{Problematiche di programmazione multitasking} \label{sec:proc_multi_prog} @@ -1949,7 +2033,8 @@ stesso processo, e pure alcune system call, possono essere interrotti in qualunque momento, e le operazioni di un eventuale \textit{signal handler} sono compiute nello stesso spazio di indirizzi del processo. Per questo, anche il solo accesso o l'assegnazione di una variabile possono non essere più -operazioni atomiche (torneremo su questi aspetti in \secref{sec:sign_xxx}). +operazioni atomiche (torneremo su questi aspetti in +\secref{sec:sign_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ò