X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=4348a7be88656d553fe3acd4a4215a453fb7f65d;hp=48af070bd1e6a7980b68b773fb3737c5ddfbd424;hb=1907c0d00d86100796a3b000827ac9742c4b9d81;hpb=65a5b6a82bdcfeefa6ebc270fe759f3a38560d33 diff --git a/prochand.tex b/prochand.tex index 48af070..4348a7b 100644 --- a/prochand.tex +++ b/prochand.tex @@ -32,8 +32,9 @@ generazione di nuovi processi 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}, assengnato 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 è un'operazione separata rispetto al lancio di un programma. In @@ -132,21 +133,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. @@ -158,7 +160,7 @@ I processi vengono creati dalla funzione \func{fork}; in molti unix questa una system call, Linux però usa un'altra nomenclatura, e la funzione \func{fork} è basata a sua volta sulla system call \func{\_\_clone}, che viene usata anche per generare i \textit{thread}. Il processo figlio creato dalla -\func{fork} è una copia identica del processo processo padre, ma ha nuovo +\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}). @@ -186,7 +188,7 @@ 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 sì che l'immagine precedente venga completamente -cancellata. Questo significa che quando il nuovo programma esce, anche il +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 @@ -211,18 +213,21 @@ programmi. \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}; +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 \acr{glibc} il tipo usato è \ctyp{int}). -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, 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 +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 \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 @@ -294,23 +299,24 @@ prototipo della funzione \end{functions} Dopo il successo dell'esecuzione di una \func{fork} sia il processo padre che -il processo figlio continuano ad essere eseguiti normalmente all'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 seccessiva alla \func{fork}; il processo figlio è però una +copia del padre, e riceve una copia dei segmenti di testo, stack e dati (vedi \secref{sec:proc_mem_layout}), ed esegue esattamente lo stesso codice del padre. Si tenga presente però che la memoria è copiata, non condivisa, pertanto padre e figlio vedono variabili diverse. -Per quanto riguarda la gestione della memoria in generale il segmento di -testo, che è identico, è 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. +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 \func{fork} è il \acr{pid} del processo figlio, mentre @@ -387,16 +393,16 @@ sul numero totale di processi permessi all'utente (vedi L'uso di \func{fork} avviene secondo due modalità principali; la prima è quella in cui all'interno di un programma si creano processi figli cui viene affidata l'esecuzione di una certa sezione di codice, mentre il processo padre -ne esegue un'altra. È il caso tipico dei server (il modello -\textit{client-server} è illustrato in \secref{sec:net_cliserv}) di rete in -cui il padre riceve ed accetta le richieste da parte dei client, per ciascuna -delle quali pone in esecuzione un figlio che è incaricato di fornire il -servizio. +ne esegue un'altra. È il caso tipico dei programmi server (il modello +\textit{client-server} è illustrato in \secref{sec:net_cliserv}) in cui il +padre riceve ed accetta le richieste da parte dei programmi client, per +ciascuna delle quali pone in esecuzione un figlio che è incaricato di fornire +il servizio. La seconda modalità è quella in cui il processo vuole eseguire un altro programma; questo è ad esempio il caso della shell. In questo caso il processo -crea un figlio la cui unica operazione è quella fare una \func{exec} (di cui -parleremo in \secref{sec:proc_exec}) subito dopo la \func{fork}. +crea un figlio la cui unica operazione è quella di fare una \func{exec} (di +cui parleremo in \secref{sec:proc_exec}) subito dopo la \func{fork}. Alcuni sistemi operativi (il VMS ad esempio) combinano le operazioni di questa seconda modalità (una \func{fork} seguita da una \func{exec}) in un'unica @@ -410,16 +416,16 @@ dell'output, identificatori) prima della \func{exec}, rendendo cos relativamente facile intervenire sulle le modalità di esecuzione del nuovo programma. -In \figref{fig:proc_fork_code} si è riportato il corpo del codice del -programma di esempio \cmd{forktest}, che ci permette di illustrare molte -caratteristiche dell'uso della funzione \func{fork}. Il programma permette di -creare un numero di figli specificato da linea di comando, e prende anche -alcune opzioni per indicare degli eventuali tempi di attesa in secondi -(eseguiti tramite la funzione \func{sleep}) per il padre ed il figlio (con -\cmd{forktest -h} si ottiene la descrizione delle opzioni); il codice -completo, compresa la parte che gestisce le opzioni a riga di comando, è -disponibile nel file \file{ForkTest.c}, distribuito insieme agli altri -sorgenti degli esempi su \href{http://gapil.firenze.linux.it/gapil_source.tgz} +In \figref{fig:proc_fork_code} è riportato il corpo del codice del programma +di esempio \cmd{forktest}, che permette di illustrare molte caratteristiche +dell'uso della funzione \func{fork}. Il programma crea un numero di figli +specificato da linea di comando, e prende anche alcune opzioni per indicare +degli eventuali tempi di attesa in secondi (eseguiti tramite la funzione +\func{sleep}) per il padre ed il figlio (con \cmd{forktest -h} si ottiene la +descrizione delle opzioni); il codice completo, compresa la parte che gestisce +le opzioni a riga di comando, è disponibile nel file \file{ForkTest.c}, +distribuito insieme agli altri sorgenti degli esempi su +\href{http://gapil.firenze.linux.it/gapil_source.tgz} {\texttt{http://gapil.firenze.linux.it/gapil\_source.tgz}}. Decifrato il numero di figli da creare, il ciclo principale del programma @@ -459,15 +465,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 @@ -860,7 +866,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 terminazione 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 @@ -1273,10 +1279,10 @@ chiamato come se si fosse eseguito il comando \cmd{interpreter [arg] 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 l'impostazione dei vari -parametri connessi ai processi. +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. @@ -1383,10 +1389,10 @@ 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 l'\textsl{userid effettivo} e l'\textsl{groupid - effettivo} (a cui si aggiungono gli eventuali \textsl{groupid supplementari} -dei gruppi dei quali l'utente fa parte). Questi sono invece gli -identificatori usati nella verifiche dei permessi del processo e per il +Al secondo gruppo appartengono lo \textsl{userid effettivo} ed il +\textsl{groupid effettivo} (a cui si aggiungono gli eventuali \textsl{groupid + 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}). @@ -1585,7 +1591,7 @@ fallimento della chiamata; l'amministratore invece pu qualunque. Specificando un argomento di valore -1 l'identificatore corrispondente verrà lasciato inalterato. -Con queste funzione si possono scambiare fra loro gli userid reale e +Con queste funzioni si possono scambiare fra loro gli userid 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 @@ -1837,9 +1843,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}} @@ -1857,8 +1864,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 @@ -1942,8 +1949,7 @@ 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}). +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 @@ -1986,26 +1992,27 @@ 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 meccanismo appena descritto infatti un valore più lungo infatti assicura una maggiore attribuzione di CPU. L'origine del nome di questo parametro sta nel -fatto che in genere esso viene generalmente 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 \func{nice}, il cui prototipo è: +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 \func{nice}, il cui prototipo è: \begin{prototype}{unistd.h} {int nice(int inc)} Aumenta il valore di \var{nice} per il processo corrente. @@ -2138,11 +2145,11 @@ 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: @@ -2344,7 +2351,7 @@ volontariamente la CPU; questo viene fatto attraverso la funzione nel qual caso \var{errno} viene impostata opportunamente.} \end{prototype} -La funzione fa si che il processo rilasci la CPU, in modo da essere rimesso in +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 @@ -2448,7 +2455,12 @@ problematiche di questo tipo in \capref{cha:IPC}). Un caso particolare di \textit{race condition} sono poi i cosiddetti \textit{deadlock}, particolarmente gravi in quanto comportano spesso il blocco -completo di un servizio, e non il fallimento di una singola operazione. +completo di un servizio, e non il fallimento di una singola operazione. Per +definizione un \textit{deadlock} è una situazione in cui due o più processi +non sono più in grado di proseguire perché ciascuno aspetta il risultato di +una operazione che dovrebbe essere eseguita dall'altro. + + L'esempio tipico di una situazione che può condurre ad un \textit{deadlock} è quello in cui un flag di ``occupazione'' viene rilasciato da un evento asincrono (come un segnale o un altro processo) fra il momento in cui lo si è