X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=process.tex;h=770e2b81214373f387a2373c65b3600d1954ec63;hp=d23e54f01cecde1c041d75116a2399c8129261d8;hb=0875699cc0b47e6b543a56fa45bb509ae876f66d;hpb=0c9d95dfc21869e96f8a3e3ab8111c842e85a1f9 diff --git a/process.tex b/process.tex index d23e54f..770e2b8 100644 --- a/process.tex +++ b/process.tex @@ -24,8 +24,8 @@ ciascun processo vedr tutte le parti uguali siano condivise), avrà un suo spazio di indirizzi, variabili proprie e sarà eseguito in maniera completamente indipendente da tutti gli altri.\footnote{questo non è del tutto vero nel caso di un programma - \textit{multi-thread}, ma sulla gestione dei \textit{thread} in Linux - torneremo più avanti.} + \textit{multi-thread}, ma la gestione dei \textit{thread} in Linux sarà + trattata a parte.} \subsection{La funzione \func{main}} @@ -50,10 +50,10 @@ Lo standard ISO C specifica che la funzione \func{main} pu argomenti o prendere due argomenti che rappresentano gli argomenti passati da linea di comando, in sostanza un prototipo che va sempre bene è il seguente: \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} - int main (int argc, char *argv[]) + int main (int argc, char *argv[]) \end{lstlisting} -In realtà nei sistemi unix esiste un'altro modo per definire la funzione +In realtà nei sistemi Unix esiste un'altro modo per definire la funzione \func{main}, che prevede la presenza di un terzo parametro, \var{char *envp[]}, che fornisce l'\textsl{ambiente} (vedi \secref{sec:proc_environ}) del programma; questa forma però non è prevista dallo standard POSIX.1 per cui @@ -157,11 +157,11 @@ specificato in \param{status} che pu Un'esigenza comune che si incontra nella programmazione è quella di dover effettuare una serie di operazioni di pulizia (ad esempio salvare dei dati, -ripristinare dei settaggi, eliminare dei file temporanei, ecc.) prima della -conclusione di un programma. In genere queste operazioni vengono fatte in -un'apposita sezione del programma, ma quando si realizza una libreria diventa -antipatico dover richiedere una chiamata esplicita ad una funzione di pulizia -al programmatore che la utilizza. +ripristinare delle impostazioni, eliminare dei file temporanei, ecc.) prima +della conclusione di un programma. In genere queste operazioni vengono fatte +in un'apposita sezione del programma, ma quando si realizza una libreria +diventa antipatico dover richiedere una chiamata esplicita ad una funzione di +pulizia al programmatore che la utilizza. È invece molto meno soggetto ad errori, e completamente trasparente all'utente, avere la possibilità di effettuare automaticamente la chiamata ad @@ -175,7 +175,7 @@ pu programma. \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di - fallimento, \var{errno} non viene settata.} + fallimento, \var{errno} non viene modificata.} \end{prototype} \noindent la funzione richiede come argomento l'indirizzo della opportuna funzione di pulizia da chiamare all'uscita, che non deve prendere argomenti e @@ -192,7 +192,7 @@ definita su altri sistemi; il suo prototipo rispetto a quello di registrazione. \bodydesc{La funzione restituisce 0 in caso di successo e -1 in caso di - fallimento, \var{errno} non viene settata.} + fallimento, \var{errno} non viene modificata.} \end{prototype} In questo caso la funzione da chiamare prende due parametri, il primo dei @@ -211,7 +211,7 @@ stream aperti, infine verr \label{sec:proc_term_conclusion} Data l'importanza dell'argomento è opportuno sottolineare ancora una volta che -in un sistema unix l'unico modo in cui un programma può essere eseguito dal +in un sistema Unix l'unico modo in cui un programma può essere eseguito dal kernel è attraverso la chiamata alla system call \func{execve} (o attraverso una delle funzioni della famiglia \func{exec} che vedremo in \secref{sec:proc_exec}). @@ -253,11 +253,12 @@ esecuzione, e le varie funzioni utilizzabili per la sua gestione. Ci sono vari modi in cui i vari sistemi organizzano la memoria (ed i dettagli di basso livello dipendono spesso in maniera diretta dall'architettura dell'hardware), ma quello più tipico, usato dai sistemi unix-like come Linux è -la cosiddetta \textsl{memoria virtuale} che consiste nell'assegnare ad ogni -processo uno spazio virtuale di indirizzamento lineare, in cui gli indirizzi -vanno da zero ad un qualche valore massimo.\footnote{nel caso di Linux fino al - kernel 2.2 detto massimo era, per macchine a 32bit, di 2Gb, con il kernel - 2.4 ed il supporto per la \textit{high-memory} il limite è stato esteso.} +la cosiddetta \textsl{memoria virtuale}\index{memoria virtuale} che consiste +nell'assegnare ad ogni processo uno spazio virtuale di indirizzamento lineare, +in cui gli indirizzi vanno da zero ad un qualche valore massimo.\footnote{nel + caso di Linux fino al kernel 2.2 detto massimo era, per macchine a 32bit, di + 2Gb, con il kernel 2.4 ed il supporto per la \textit{high-memory} il limite + è stato esteso.} Come accennato in \capref{cha:intro_unix} questo spazio di indirizzi è virtuale e non corrisponde all'effettiva posizione dei dati nella RAM del @@ -286,15 +287,16 @@ gestione della memoria (la \textit{Memory Management Unit} del processore). Poiché in genere la memoria fisica è solo una piccola frazione della memoria virtuale, è necessario un meccanismo che permetta di trasferire le pagine che servono dal supporto su cui si trovano in memoria, eliminando quelle che non -servono. Questo meccanismo è detto \textit{paging}, ed è uno dei compiti -principali del kernel. +servono. Questo meccanismo è detto \textsl{paginazione}\index{paginazione} (o +\textit{paging}), ed è uno dei compiti principali del kernel. Quando un processo cerca di accedere ad una pagina che non è nella memoria -reale, avviene quello che viene chiamato un \textit{page fault}; l'hardware di -gestione della memoria genera un'interruzione e passa il controllo al kernel -il quale sospende il processo e si incarica di mettere in RAM la pagina -richiesta (effettuando tutte le operazioni necessarie per reperire lo spazio -necessario), per poi restituire il controllo al processo. +reale, avviene quello che viene chiamato un +\textit{page fault}\index{page fault}; +l'hardware di gestione della memoria genera un'interruzione e passa +il controllo al kernel il quale sospende il processo e si incarica di mettere +in RAM la pagina richiesta (effettuando tutte le operazioni necessarie per +reperire lo spazio necessario), per poi restituire il controllo al processo. Dal punto di vista di un processo questo meccanismo è completamente trasparente, e tutto avviene come se tutte le pagine fossero sempre @@ -305,8 +307,8 @@ a tempi molto pi Normalmente questo è il prezzo da pagare per avere un multitasking reale, ed in genere il sistema è molto efficiente in questo lavoro; quando però ci siano esigenze specifiche di prestazioni è possibile usare delle funzioni che -permettono di bloccare il meccanismo del paging e mantenere fisse delle pagine -in memoria (vedi \ref{sec:proc_mem_lock}). +permettono di bloccare il meccanismo della paginazione e mantenere fisse delle +pagine in memoria (vedi \ref{sec:proc_mem_lock}). \subsection{La struttura della memoria di un processo} @@ -318,14 +320,15 @@ tentativo di accedere ad un indirizzo non allocato commette quando si è manipolato male un puntatore e genera quello che viene chiamato un \textit{segmentation fault}. Se si tenta cioè di leggere o scrivere da un indirizzo per il quale non esiste un'associazione della pagina -virtuale, il kernel risponde al relativo \textit{page fault} mandando un -segnale \macro{SIGSEGV} al processo, che normalmente ne causa la terminazione -immediata. +virtuale, il kernel risponde al relativo \textit{page fault}\index{page fault} +mandando un segnale \macro{SIGSEGV} al processo, che normalmente ne causa la +terminazione immediata. -È pertanto importante capire come viene strutturata la memoria virtuale di un -processo. Essa viene divisa in \textsl{segmenti}, cioè un insieme contiguo di -indirizzi virtuali ai quali il processo può accedere. Solitamente un -programma C viene suddiviso nei seguenti segmenti: +È pertanto importante capire come viene strutturata \textsl{la memoria + virtuale}\index{page fault} di un processo. Essa viene divisa in +\textsl{segmenti}, cioè un insieme contiguo di indirizzi virtuali ai quali il +processo può accedere. Solitamente un programma C viene suddiviso nei +seguenti segmenti: \begin{enumerate} \item Il segmento di testo o \textit{text segment}. Contiene il codice del @@ -392,7 +395,7 @@ programma C viene suddiviso nei seguenti segmenti: \begin{figure}[htb] \centering \includegraphics[width=5cm]{img/memory_layout} - \caption{Disposizione tipica dei segmenti di memoria di un processo} + \caption{Disposizione tipica dei segmenti di memoria di un processo.} \label{fig:proc_mem_layout} \end{figure} @@ -453,20 +456,20 @@ prototipi sono i seguenti: La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e \macro{NULL} in caso di fallimento, nel qual caso - \var{errno} viene settata a \macro{ENOMEM}. + \var{errno} assumerà il valore \macro{ENOMEM}. \funcdecl{void *malloc(size\_t size)} Alloca \var{size} byte nello heap. La memoria non viene inizializzata. La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e \macro{NULL} in caso di fallimento, nel qual caso - \var{errno} viene settata a \macro{ENOMEM}. + \var{errno} assumerà il valore \macro{ENOMEM}. \funcdecl{void *realloc(void *ptr, size\_t size)} Cambia la dimensione del blocco allocato all'indirizzo \var{ptr} portandola a \var{size}. La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e \macro{NULL} in caso di fallimento, nel qual caso - \var{errno} viene settata a \macro{ENOMEM}. + \var{errno} assumerà il valore \macro{ENOMEM}. \funcdecl{void free(void *ptr)} Disalloca lo spazio di memoria puntato da \var{ptr}. @@ -555,12 +558,11 @@ che contiene l'errore. Per questo motivo \textit{memory leak}. Per ovviare a questi problemi l'implementazione delle routine di allocazione -delle \acr{glibc} mette a disposizione una serie di funzionalità (su cui -torneremo in \secref{sec:xxx_advanced}) che permettono di tracciare le -allocazioni e le disallocazione, e definisce anche una serie di possibili -\textit{hook} (\textsl{ganci}) che permettono di sostituire alle funzioni di -libreria una propria versione (che può essere più o meno specializzata per il -debugging). +delle \acr{glibc} mette a disposizione una serie di funzionalità che +permettono di tracciare le allocazioni e le disallocazione, e definisce anche +una serie di possibili \textit{hook} (\textsl{ganci}) che permettono di +sostituire alle funzioni di libreria una propria versione (che può essere più +o meno specializzata per il debugging). \subsection{La funzione \func{alloca}} @@ -577,7 +579,7 @@ prototipo La funzione restituisce il puntatore alla zona di memoria allocata in caso di successo e \macro{NULL} in caso di fallimento, nel qual caso - \var{errno} viene settata a \macro{ENOMEM}. + \var{errno} assumerà il valore \macro{ENOMEM}. \end{prototype} \noindent ma in questo caso non è più necessario liberare la memoria (e quindi non esiste un analogo della \func{free}) in quanto essa viene rilasciata @@ -601,7 +603,7 @@ suo utilizzo quindi limita la portabilit non può essere usata nella lista degli argomenti di una funzione, perché lo spazio verrebbe allocato nel mezzo degli stessi. -% Questo è riportato solo dal manuale delle glibc, nelle man page non c'è +% Questo è riportato solo dal manuale delle glibc, nelle pagine di manuale non c'è % traccia di tutto ciò % %Inoltre se si @@ -629,7 +631,7 @@ analoghe system call a cui fanno da interfaccia. I loro prototipi sono: \var{end\_data\_segment}. La funzione restituisce 0 in caso di successo e -1 in caso di - fallimento, nel qual caso \var{errno} viene settata a \macro{ENOMEM}. + fallimento, nel qual caso \var{errno} assumerà il valore \macro{ENOMEM}. \funcdecl{void *sbrk(ptrdiff\_t increment)} Incrementa lo spazio dati di un programma di \var{increment}. Un valore zero restituisce l'attuale posizione @@ -637,7 +639,7 @@ analoghe system call a cui fanno da interfaccia. I loro prototipi sono: La funzione restituisce il puntatore all'inizio della nuova zona di memoria allocata in caso di successo e \macro{NULL} in caso di fallimento, nel qual - caso \macro{errno} viene settata a \macro{ENOMEM}. + caso \macro{errno} assumerà il valore \macro{ENOMEM}. \end{functions} \noindent in genere si usa \func{sbrk} con un valore zero per ottenere l'attuale posizione della fine del segmento dati. @@ -653,19 +655,19 @@ implementare una sua versione delle routine di allocazione. % \label{sec:proc_mem_malloc_custom} -\subsection{Il controllo della memoria virtuale} +\subsection{Il controllo della memoria virtuale\index{memoria virtuale}} \label{sec:proc_mem_lock} -Come spiegato in \secref{sec:proc_mem_gen} il kernel gestisce la memoria in -maniera trasparente ai processi, decidendo quando rimuovere pagine dalla -memoria per metterle nello swap, sulla base dell'utilizzo corrente da parte -dei vari processi. +Come spiegato in \secref{sec:proc_mem_gen} il kernel gestisce la memoria +virtuale in maniera trasparente ai processi, decidendo quando rimuovere pagine +dalla memoria per metterle nello swap, sulla base dell'utilizzo corrente da +parte dei vari processi. Nell'uso comune un processo non deve preoccuparsi di tutto ciò, in quanto il -meccanismo della paginazione riporta in RAM, ed in maniera trasparente, tutte -le pagine che gli occorrono; esistono però esigenze particolari in cui non si -vuole che questo meccanismo si attivi. In generale i motivi per cui si possono -avere di queste necessità sono due: +meccanismo della paginazione\index{paginazione} riporta in RAM, ed in maniera +trasparente, tutte le pagine che gli occorrono; esistono però esigenze +particolari in cui non si vuole che questo meccanismo si attivi. In generale i +motivi per cui si possono avere di queste necessità sono due: \begin{itemize} \item \textsl{La velocità}. Il processo della paginazione è trasparente solo se il programma in esecuzione non è sensibile al tempo che occorre a @@ -707,14 +709,14 @@ comporta anche la fine dell'uso della sua memoria virtuale, e quindi anche di tutti i suoi \textit{memory lock}. I \textit{memory lock} non sono ereditati dai processi figli.\footnote{ma - siccome Linux usa il \textit{copy on write} (vedi \secref{sec:proc_fork}) - gli indirizzi virtuali del figlio sono mantenuti sullo stesso segmento di - RAM del padre, quindi fintanto che un figlio non scrive su un segmento, può - usufruire del memory lock del padre.} Siccome la presenza di un -\textit{memory lock} riduce la memoria disponibile al sistema, con un impatto -su tutti gli altri processi, solo l'amministratore ha la capacità di bloccare -una pagina. Ogni processo può però sbloccare le pagine relative alla propria -memoria. + siccome Linux usa il \textit{copy on write}\index{copy on write} (vedi + \secref{sec:proc_fork}) gli indirizzi virtuali del figlio sono mantenuti + sullo stesso segmento di RAM del padre, quindi fintanto che un figlio non + scrive su un segmento, può usufruire del memory lock del padre.} Siccome la +presenza di un \textit{memory lock} riduce la memoria disponibile al sistema, +con un impatto su tutti gli altri processi, solo l'amministratore ha la +capacità di bloccare una pagina. Ogni processo può però sbloccare le pagine +relative alla propria memoria. Il sistema pone dei limiti all'ammontare di memoria di un processo che può essere bloccata e al totale di memoria fisica che può dedicare a questo, lo @@ -738,7 +740,7 @@ Le funzioni per bloccare e sbloccare singole sezioni di memoria sono \bodydesc{Entrambe le funzioni ritornano 0 in caso di successo e -1 in - caso di errore, nel qual caso \var{errno} è settata ad uno dei + caso di errore, nel qual caso \var{errno} assumerà uno dei valori seguenti: \begin{errlist} \item[\macro{ENOMEM}] alcuni indirizzi dell'intervallo specificato non @@ -786,11 +788,12 @@ esempio limitandosi a tutte le pagine allocate a partire da un certo momento. In ogni caso un processo real-time che deve entrare in una sezione critica deve provvedere a riservare memoria sufficiente prima dell'ingresso, per -scongiurare in partenza un eventuale page fault causato dal meccanismo di -\textit{copy on write}. Infatti se nella sezione critica si va ad utilizzare -memoria che non è ancora stata riportata in RAM si potrebbe avere un page -fault durante l'esecuzione della stessa, con conseguente rallentamento -(probabilmente inaccettabile) dei tempi di esecuzione. +scongiurare in partenza un eventuale page fault\index{page fault} causato dal +meccanismo di \textit{copy on write}\index{copy on write}. Infatti se nella +sezione critica si va ad utilizzare memoria che non è ancora stata riportata +in RAM si potrebbe avere un page fault durante l'esecuzione della stessa, con +conseguente rallentamento (probabilmente inaccettabile) dei tempi di +esecuzione. In genere si ovvia a questa problematica chiamando una funzione che ha allocato una quantità sufficientemente ampia di variabili automatiche, in modo @@ -824,8 +827,8 @@ che si incarica di leggere la linea di comando e di effettuarne la scansione (il cosiddetto \textit{parsing}) per individuare le parole che la compongono, ciascuna delle quali viene considerata un parametro. Di norma per individuare le parole viene usato come carattere di separazione lo spazio o il tabulatore, -ma il comportamento è modificabile attraverso il settaggio della variabile di -ambiente \cmd{IFS}. +ma il comportamento è modificabile attraverso l'impostazione della variabile +di ambiente \cmd{IFS}. \begin{figure}[htb] \centering @@ -845,7 +848,7 @@ questo meccanismo \subsection{La gestione delle opzioni} \label{sec:proc_opt_handling} -In generale un programma unix riceve da linea di comando sia gli argomenti che +In generale un programma Unix riceve da linea di comando sia gli argomenti che le opzioni, queste ultime sono standardizzate per essere riconosciute come tali: un elemento di \var{argv} che inizia con \texttt{-} e che non sia un singolo \texttt{-} o un \texttt{--} viene considerato un'opzione. In genere @@ -959,7 +962,7 @@ Normalmente \func{getopt} compie una permutazione degli elementi di \var{argv} così che alla fine della scansione gli elementi che non sono opzioni sono spostati in coda al vettore. Oltre a questa esistono altre due modalità di gestire gli elementi di \var{argv}; se \var{optstring} inizia con il carattere -\texttt{'+'} (o è settata la variabile di ambiente \macro{POSIXLY\_CORRECT}) +\texttt{'+'} (o è impostata la variabile di ambiente \macro{POSIXLY\_CORRECT}) la scansione viene fermata non appena si incontra un elemento che non è un'opzione. L'ultima modalità, usata quando un programma può gestire la mescolanza fra opzioni e argomenti, ma se li aspetta in un ordine definito, si @@ -1043,7 +1046,7 @@ anche altre: per una lista pi \hline \hline \macro{USER} & $\bullet$ & $\bullet$ & $\bullet$ & Nome utente\\ - \macro{LOGNAME} & $\bullet$ & $\bullet$ & $\bullet$ & Nome utente\\ + \macro{LOGNAME} & $\bullet$ & $\bullet$ & $\bullet$ & Nome di login\\ \macro{HOME} & $\bullet$ & $\bullet$ & $\bullet$ & Directory base dell'utente\\ \macro{LANG} & $\bullet$ & $\bullet$ & $\bullet$ & Localizzazione\\ @@ -1054,8 +1057,8 @@ anche altre: per una lista pi \macro{TERM} & $\bullet$ & $\bullet$ & $\bullet$ & Tipo di terminale\\ \macro{PAGER} & $\bullet$ & $\bullet$ & $\bullet$ & Programma per vedere i testi\\ - \macro{EDITOR} & $\bullet$ & $\bullet$ & $\bullet$ & Editor di default\\ - \macro{BROWSER} & $\bullet$ & $\bullet$ & $\bullet$ & Browser di default\\ + \macro{EDITOR} & $\bullet$ & $\bullet$ & $\bullet$ & Editor preferito\\ + \macro{BROWSER} & $\bullet$ & $\bullet$ & $\bullet$ & Browser preferito\\ \hline \end{tabular} \caption{Variabili di ambiente più comuni definite da vari standard.} @@ -1077,7 +1080,7 @@ il cui prototipo Oltre a questa funzione di lettura, che è l'unica definita dallo standard ANSI C, nell'evoluzione dei sistemi Unix ne sono state proposte altre, da -utilizzare per settare e per cancellare le variabili di ambiente. Uno schema +utilizzare per impostare e per cancellare le variabili di ambiente. Uno schema delle funzioni previste nei vari standard e disponibili in Linux è riportato in \ntab. @@ -1113,7 +1116,7 @@ ambiente, i loro prototipi sono i seguenti: \headdecl{stdlib.h} \funcdecl{int setenv(const char *name, const char *value, int overwrite)} - Setta la variabile di ambiente \param{name} al valore \param{value}. + Imposta la variabile di ambiente \param{name} al valore \param{value}. \funcdecl{int putenv(char *string)} Aggiunge la stringa \param{string} all'ambiente. @@ -1143,7 +1146,7 @@ immutata se uguale a zero. La seconda funzione prende come parametro una stringa analoga quella restituita da \func{getenv}, e sempre nella forma \var{NOME=valore}. Se la variabile specificata non esiste la stringa sarà aggiunta all'ambiente, se -invece esiste il suo valore sarà settato a quello specificato da +invece esiste il suo valore sarà impostato a quello specificato da \param{string}. Si tenga presente che, seguendo lo standard SUSv2, le \acr{glibc} successive alla versione 2.1.2 aggiungono\footnote{il comportamento è lo stesso delle vecchie \acr{libc4} e \acr{libc5}; nelle @@ -1213,7 +1216,6 @@ informazioni a riguardo dei risultati vengono passate alla routine chiamante attraverso il valore di ritorno. È buona norma seguire questa pratica anche nella programmazione normale. - Talvolta però è necessario che la funzione possa restituire indietro alla funzione chiamante un valore relativo ad uno dei suoi parametri. Per far questo si usa il cosiddetto \textit{value result argument}, si passa cioè, @@ -1336,12 +1338,12 @@ stack all'indirizzo dove sono stati salvati i parametri, normale pensare di poter effettuare questa operazione. In generale però possono esistere anche realizzazioni diverse, per questo -motivo \macro{va\_list} è definito come tipo opaco e non può essere assegnato -direttamente ad un altra variabile dello stesso tipo. Per risolvere questo -problema lo standard ISO C99\footnote{alcuni sistemi che non hanno questa - macro provvedono al suo posto \macro{\_\_va\_copy} che era il nome proposto - in una bozza dello standard.} ha previsto una macro ulteriore che permette -di eseguire la copia di un puntatore alla lista degli argomenti: +motivo \macro{va\_list} è definito come \textsl{tipo opaco} e non può essere +assegnato direttamente ad un altra variabile dello stesso tipo. Per risolvere +questo problema lo standard ISO C99\footnote{alcuni sistemi che non hanno + questa macro provvedono al suo posto \macro{\_\_va\_copy} che era il nome + proposto in una bozza dello standard.} ha previsto una macro ulteriore che +permette di eseguire la copia di un puntatore alla lista degli argomenti: \begin{prototype}{stdarg.h}{void va\_copy(va\_list dest, va\_list src)} Copia l'attuale valore \param{src} del puntatore alla lista degli argomenti su \param{dest}. @@ -1363,7 +1365,6 @@ argomenti opzionali, questi verranno sempre promossi, pertanto nella ricezione dei medesimi occorrerà tenerne conto (ad esempio un \ctyp{char} verrà visto da \macro{va\_arg} come \ctyp{int}). - Uno dei problemi che si devono affrontare con le funzioni con un numero variabile di argomenti è che non esiste un modo generico che permetta di stabilire quanti sono i parametri passati effettivamente in una chiamata. @@ -1397,6 +1398,7 @@ anche dalla funzione chiamante queste devono essere allocate esplicitamente, o in maniera statica (usando variabili di tipo \ctyp{static} o \ctyp{extern}), o dinamicamente con una delle funzioni della famiglia \func{malloc}. + \subsection{Il controllo di flusso non locale} \label{sec:proc_longjmp} @@ -1405,46 +1407,119 @@ varie istruzioni del linguaggio C; fra queste la pi \code{goto}, che viene deprecato in favore dei costrutti della programmazione strutturata, che rendono il codice più leggibile e mantenibile . Esiste però un caso in cui l'uso di questa istruzione porta all'implementazione più -efficiente e chiara anche dal punto di vista della struttura del programma, +efficiente e chiara anche dal punto di vista della struttura del programma: quello dell'uscita in caso di errore. Il C però non consente di effettuare un salto ad una label definita in -un'altra funzione, per cui se l'errore avviene in funzioni profondamente -annidate occorre usare quello che viene chiamato un salto \textsl{non-locale}; -questo viene fatto usando salvando il contesto dello stack nel punto in cui si -vuole tornare in caso di errore, e ripristinandolo quando l'occorrenza capita. - -La funzione che permette di salvare il contesto dello stack è \func{setjmp}, -il cui prototipo è: - +un'altra funzione, per cui se l'errore avviene in una funzione e la sua +gestione ordinaria è in un'altra occorre usare quello che viene chiamato un +\textsl{salto non-locale}. Il caso classico in cui si ha questa necessità, +citato sia da \cite{APUE} che da da \cite{glibc}, è quello di un programma nel +cui corpo principale in cui viene letto un input del quale viene eseguita, +attraverso una serie di funzioni di analisi, una scansione dei contenuti da cui +ottenere le indicazioni per l'esecuzione di opportune operazioni. + +Dato che l'analisi può risultare molto complessa, ed opportunamente suddivisa +in fasi diverse, la rilevazione di un errore nell'input può accadere +all'interno di funzioni profondamente annidate l'una nell'altra. In questo +caso si dovrebbe per ciascuna fase dover gestire tutta la casistica del +passaggio all'indietro di tutti gli errori rilevabili dalle funzioni usate +nelle fasi successive, mentre sarebbe molto più comodo poter tornare +direttamente al ciclo di lettura principale, scartando l'input come +errato.\footnote{a meno che, come precisa \cite{glibc}, alla chiusura di + ciascuna fase non siano associate operazioni di pulizia specifiche (come + deallocazioni, chiusure di file, ecc.), che non potrebbero essere eseguite + con un salto non-locale.} + +Tutto ciò può essere realizzato salvando il contesto dello stack nel punto in +cui si vuole tornare in caso di errore, e ripristinandolo quando l'occorrenza +capita. La funzione che permette di salvare il contesto dello stack è +\func{setjmp}, il cui prototipo è: \begin{functions} \headdecl{setjmp.h} \funcdecl{void setjmp(jmp\_buf env)} Salva il contesto dello stack in \param{env} per un successivo uso da parte - di \func{longjmp}. Il contesto viene invalidato se la routine che ha - chiamato \func{setjmp} ritorna. - + di \func{longjmp}. + \bodydesc{La funzione ritorna zero quando è chiamata direttamente e un valore diverso da zero quando ritorna da una chiamata di \func{longjmp} che usa il contesto salvato in precedenza.} \end{functions} - -Per poter effettuare un salto non locale si usa la funzione \func{longjmp}; il -suo prototipo è: +Quando si esegue la funzione il contesto viene salvato in appositi oggetti (di +tipo \type{jmp\_buf}), passati come primo argomento alla funzione, in genere +questi vengono definiti come variabili globali in modo da poter essere visti +in tutte le funzioni del programma. + +Quando viene eseguita direttamente la funzione ritorna sempre zero, un valore +diverso da zero viene restituito solo quando il ritorno è dovuto ad una +chiamata di \func{longjmp} in un'altra parte del programma. Si tenga conto che +il contesto salvato in \param{env} viene invalidato se la routine che ha +chiamato \func{setjmp} ritorna, nel qual caso l'uso di \func{longjmp} può +comportare conseguenze imprevedibili (e di norma fatali per il processo). + +Come accennato per effettuare un salto non-locale ad un punto precedentemente +stabilito con \func{setjmp} si usa la funzione \func{longjmp}; il suo +prototipo è: \begin{functions} \headdecl{setjmp.h} \funcdecl{void longjmp(jmp\_buf env, int val)} - Ripristina il contesto dello stack salvato dall'ultima chiamata di - \func{setjmp} con l'argomento \param{env}. Il programma prosegue dal ritorno - di \func{setjmp} con un valore \param{val}. Il valore di \param{val} deve - essere diverso da zero, se viene specificato 0 sarà usato 1 al suo posto. - + Ripristina il contesto dello stack salvato nell'ultima chiamata di + \func{setjmp} con l'argomento \param{env}. + \bodydesc{La funzione non ritorna.} \end{functions} +Dopo l'esecuzione della funzione programma prosegue dal codice successivo al +ritorno della \func{setjmp} con cui si era salvato \param{env}, che restituirà +il valore \param{val} invece di zero. Il valore di \param{val} specificato +nella chiamata deve essere diverso da zero, se si è specificato 0 sarà +comunque restituito 1 al suo posto. + +In sostanza un \func{longjmp} è analogo ad un \code{return}, solo che invece +di ritornare alla riga successiva della funzione chiamante, il programma +ritorna alla posizione della relativa \func{setjmp}, ed il ritorno può essere +effettuato anche attraverso diversi livelli di funzioni annidate. + +L'implementazione di queste funzioni comporta alcune restrizioni dato che esse +interagiscono direttamente con la gestione dello stack ed il funzionamento del +compilatore stesso. In particolare \func{setjmp} è implementata con una macro, +pertanto non si può cercare di ottenerne l'indirizzo, ed inoltre delle +chiamate a questa funzione sono sicure solo in uno dei seguenti casi: +\begin{itemize} +\item come espressione di controllo in un comando condizionale, di selezione + o di iterazione (come \code{if}, \code{switch} o \code{while}). +\item come operando per un operatore di uguaglianza o confronto in una + espressione di controllo di un comando condizionale, di selezione o di + iterazione. +\item come operando per l'operatore di negazione (\code{!}) in una espressione + di controllo di un comando condizionale, di selezione o di iterazione. +\item come espressione a sé stante. +\end{itemize} + +In generale, dato che l'unica differenza fra la chiamata diretta e quella +ottenuta da un \func{longjmp}, è il valore di ritorno di \func{setjmp}, essa è +usualmente chiamata all'interno di un comando \code{if}. + +Uno dei punti critici dei salti non-locali è quello del valore delle +variabili, ed in particolare quello delle variabili automatiche della funzione +a cui si ritorna. In generale le variabili globali e statiche mantengono i +valori che avevano al momento della chiamata di \func{longjmp}, ma quelli +delle variabili automatiche (o di quelle dichiarate \code{register}) sono in +genere indeterminati. + +Quello che succede infatti è che i valori delle variabili che sono tenute in +memoria manterranno il valore avuto al momento della chiamata di +\func{longjmp}, mentre quelli tenuti nei registri del processore (che nella +chiamata ad un'altra funzioni vengono salvati nel contesto nello stack) +torneranno al valore avuto al momento della chiamata di \func{setjmp}; per +questo quando si vuole avere un comportamento coerente si può bloccare +l'ottimizzazione che porta le variabili nei registri dichiarandole tutte come +\code{volatile}. + + %%% Local Variables: %%% mode: latex