From: Simone Piccardi Date: Wed, 15 Aug 2018 22:43:29 +0000 (+0200) Subject: Ancora revisione X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=commitdiff_plain;h=c6998e1daad18f2bf10b0dbff8c7441139a27795;p=gapil.git Ancora revisione --- diff --git a/img/argv_argc.dia b/img/argv_argc.dia index 7889ffe..2252f80 100644 Binary files a/img/argv_argc.dia and b/img/argv_argc.dia differ diff --git a/img/endianness.dia b/img/endianness.dia index 99ba5b9..22f1737 100644 Binary files a/img/endianness.dia and b/img/endianness.dia differ diff --git a/img/environ_var.dia b/img/environ_var.dia index 318629b..2e44875 100644 Binary files a/img/environ_var.dia and b/img/environ_var.dia differ diff --git a/img/memory_layout.dia b/img/memory_layout.dia index 87189ae..a3e99d1 100644 Binary files a/img/memory_layout.dia and b/img/memory_layout.dia differ diff --git a/img/proc_beginend.dia b/img/proc_beginend.dia index 692e3a2..db2e000 100644 Binary files a/img/proc_beginend.dia and b/img/proc_beginend.dia differ diff --git a/img/task_struct.dia b/img/task_struct.dia index 39d2138..250d93a 100644 Binary files a/img/task_struct.dia and b/img/task_struct.dia differ diff --git a/process.tex b/process.tex index f14150a..9a67fb5 100644 --- a/process.tex +++ b/process.tex @@ -381,7 +381,7 @@ linguaggio C all'interno della stessa, o se si richiede esplicitamente la chiusura invocando direttamente la funzione \func{exit}. Queste due modalità sono assolutamente equivalenti, dato che \func{exit} viene chiamata in maniera trasparente anche quando \code{main} ritorna, passandogli come argomento il -valore di ritorno. +valore indicato da \instruction{return}. La funzione \funcd{exit}, che è completamente generale, essendo definita dallo standard ANSI C, è quella che deve essere invocata per una terminazione @@ -406,7 +406,7 @@ vedremo a breve) che completa la terminazione del processo. \itindbeg{exit~status} -Il valore dell'argomento \param{status} o il valore di ritorno di \code{main}, +Il valore dell'argomento \param{status} o il valore di ritorno di \code{main} costituisce quello che viene chiamato lo \textsl{stato di uscita} (l'\textit{exit status}) del processo. In generale si usa questo valore per fornire al processo padre (come vedremo in sez.~\ref{sec:proc_wait}) delle @@ -415,22 +415,22 @@ terminato. Anche se l'argomento \param{status} (ed il valore di ritorno di \code{main}) sono numeri interi di tipo \ctyp{int}, si deve tener presente che il valore -dello stato di uscita viene comunque troncato ad 8 bit, -per cui deve essere sempre compreso fra 0 e 255. Si tenga presente che se si -raggiunge la fine della funzione \code{main} senza ritornare esplicitamente si -ha un valore di uscita indefinito, è pertanto consigliabile di concludere -sempre in maniera esplicita detta funzione. - -Non esiste un valore significato intrinseco della stato di uscita, ma una -convenzione in uso pressoché universale è quella di restituire 0 in caso di -successo e 1 in caso di fallimento. Una eccezione a questa convenzione è per i -programmi che effettuano dei confronti (come \cmd{diff}), che usano 0 per -indicare la corrispondenza, 1 per indicare la non corrispondenza e 2 per -indicare l'incapacità di effettuare il confronto. Un'altra convenzione riserva -i valori da 128 a 256 per usi speciali: ad esempio 128 viene usato per -indicare l'incapacità di eseguire un altro programma in un -sottoprocesso. Benché le convenzioni citate non siano seguite universalmente è -una buona idea tenerle presenti ed adottarle a seconda dei casi. +dello stato di uscita viene comunque troncato ad 8 bit, per cui deve essere +sempre compreso fra 0 e 255. Si tenga presente che se si raggiunge la fine +della funzione \code{main} senza ritornare esplicitamente si ha un valore di +uscita indefinito, è pertanto consigliabile di concludere sempre in maniera +esplicita detta funzione. + +Non esiste un significato intrinseco della stato di uscita, ma una convenzione +in uso pressoché universale è quella di restituire 0 in caso di successo e 1 +in caso di fallimento. Una eccezione a questa convenzione è per i programmi +che effettuano dei confronti (come \cmd{diff}), che usano 0 per indicare la +corrispondenza, 1 per indicare la non corrispondenza e 2 per indicare +l'incapacità di effettuare il confronto. Un'altra convenzione riserva i valori +da 128 a 256 per usi speciali: ad esempio 128 viene usato per indicare +l'incapacità di eseguire un altro programma in un sottoprocesso. Benché le +convenzioni citate non siano seguite universalmente è una buona idea tenerle +presenti ed adottarle a seconda dei casi. Si tenga presente inoltre che non è una buona idea usare eventuali codici di errore restituiti nella variabile \var{errno} (vedi sez.~\ref{sec:sys_errors}) @@ -473,8 +473,8 @@ sez.~\ref{sec:file_unix_interface} e sez.~\ref{sec:files_std_interface}). Infine fa sì che ogni figlio del processo sia adottato da \cmd{init} (vedi sez.~\ref{sec:proc_termination}), manda un segnale \signal{SIGCHLD} al processo padre (vedi -sez.~\ref{sec:sig_job_control}) e ritorna lo stato di uscita specificato -in \param{status} che può essere raccolto usando la funzione \func{wait} (vedi +sez.~\ref{sec:sig_job_control}) e salva lo stato di uscita specificato in +\param{status} che può essere raccolto usando la funzione \func{wait} (vedi sez.~\ref{sec:proc_wait}). Si tenga presente infine che oltre alla conclusione ``\textsl{normale}'' @@ -488,12 +488,12 @@ funzione \func{abort}; torneremo su questo in sez.~\ref{sec:proc_termination}. \label{sec:proc_atexit} Un'esigenza comune che si incontra è quella di dover effettuare una serie di -operazioni di pulizia (ad esempio salvare dei dati, 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. +operazioni di pulizia prima della conclusione di un programma, ad esempio +salvare dei dati, ripristinare delle impostazioni, eliminare dei file +temporanei, ecc. 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 fare effettuare automaticamente la @@ -524,8 +524,11 @@ funzione di pulizia dovrà essere definita come \code{void function(void)}. Un'estensione di \func{atexit} è la funzione \funcd{on\_exit}, che le \acr{glibc} includono per compatibilità con SunOS ma che non è detto sia -definita su altri sistemi,\footnote{non essendo prevista dallo standard POSIX - è in genere preferibile evitarne l'uso.} il suo prototipo è: +definita su altri sistemi,\footnote{la funzione è disponibile dalle + \acr{glibc} 2.19 definendo la macro \macro{\_DEFAULT\_SOURCE}, mentre in + precedenza erano necessarie \macro{\_BSD\_SOURCE} o \macro{\_SVID\_SOURCE}; + non essendo prevista dallo standard POSIX è in generale preferibile evitarne + l'uso.} il suo prototipo è: \begin{funcproto}{ \fhead{stdlib.h} @@ -595,44 +598,44 @@ normalmente un programma è riportato in fig.~\ref{fig:proc_prog_start_stop}. \begin{figure}[htb] \centering -% \includegraphics[width=9cm]{img/proc_beginend} - \begin{tikzpicture}[>=stealth] - \filldraw[fill=black!35] (-0.3,0) rectangle (12,1); - \draw(5.5,0.5) node {\large{\textsf{kernel}}}; + \includegraphics[width=9cm]{img/proc_beginend} + % \begin{tikzpicture}[>=stealth] + % \filldraw[fill=black!35] (-0.3,0) rectangle (12,1); + % \draw(5.5,0.5) node {\large{\textsf{kernel}}}; - \filldraw[fill=black!15] (1.5,2) rectangle (4,3); - \draw (2.75,2.5) node {\texttt{ld-linux.so}}; - \draw [->] (2.75,1) -- (2.75,2); - \draw (2.75,1.5) node [anchor=west]{\texttt{execve}}; + % \filldraw[fill=black!15] (1.5,2) rectangle (4,3); + % \draw (2.75,2.5) node {\texttt{ld-linux.so}}; + % \draw [->] (2.75,1) -- (2.75,2); + % \draw (2.75,1.5) node [anchor=west]{\texttt{execve}}; - \filldraw[fill=black!15,rounded corners] (1.5,4) rectangle (4,5); - \draw (2.75,4.5) node {\texttt{main}}; + % \filldraw[fill=black!15,rounded corners] (1.5,4) rectangle (4,5); + % \draw (2.75,4.5) node {\texttt{main}}; - \draw [<->, dashed] (2.75,3) -- (2.75,4); - \draw [->] (1.5,4.5) -- (0.3,4.5) -- (0.3,1); - \draw (0.9,4.5) node [anchor=south] {\texttt{\_exit}}; + % \draw [<->, dashed] (2.75,3) -- (2.75,4); + % \draw [->] (1.5,4.5) -- (0.3,4.5) -- (0.3,1); + % \draw (0.9,4.5) node [anchor=south] {\texttt{\_exit}}; - \filldraw[fill=black!15,rounded corners] (1.5,6) rectangle (4,7); - \draw (2.75,6.5) node {\texttt{funzione}}; + % \filldraw[fill=black!15,rounded corners] (1.5,6) rectangle (4,7); + % \draw (2.75,6.5) node {\texttt{funzione}}; - \draw [<->, dashed] (2.75,5) -- (2.75,6); - \draw [->] (1.5,6.5) -- (0.05,6.5) -- (0.05,1); - \draw (0.9,6.5) node [anchor=south] {\texttt{\_exit}}; + % \draw [<->, dashed] (2.75,5) -- (2.75,6); + % \draw [->] (1.5,6.5) -- (0.05,6.5) -- (0.05,1); + % \draw (0.9,6.5) node [anchor=south] {\texttt{\_exit}}; - \draw (6.75,4.5) node (exit) [rectangle,fill=black!15,minimum width=2.5cm,minimum height=1cm,rounded corners, draw]{\texttt{exit}}; + % \draw (6.75,4.5) node (exit) [rectangle,fill=black!15,minimum width=2.5cm,minimum height=1cm,rounded corners, draw]{\texttt{exit}}; - \draw[->] (4,6.5) -- node[anchor=south west]{\texttt{exit}} (exit); - \draw[->] (4,4.5) -- node[anchor=south]{\texttt{exit}} (exit); - \draw[->] (exit) -- node[anchor=east]{\texttt{\_exit}}(6.75,1); + % \draw[->] (4,6.5) -- node[anchor=south west]{\texttt{exit}} (exit); + % \draw[->] (4,4.5) -- node[anchor=south]{\texttt{exit}} (exit); + % \draw[->] (exit) -- node[anchor=east]{\texttt{\_exit}}(6.75,1); - \draw (10,4.5) node (exithandler1) [rectangle,fill=black!15,rounded corners, draw]{\textsf{exit handler}}; - \draw (10,5.5) node (exithandler2) [rectangle,fill=black!15,rounded corners, draw]{\textsf{exit handler}}; - \draw (10,3.5) node (stream) [rectangle,fill=black!15,rounded corners, draw]{\textsf{chiusura stream}}; + % \draw (10,4.5) node (exithandler1) [rectangle,fill=black!15,rounded corners, draw]{\textsf{exit handler}}; + % \draw (10,5.5) node (exithandler2) [rectangle,fill=black!15,rounded corners, draw]{\textsf{exit handler}}; + % \draw (10,3.5) node (stream) [rectangle,fill=black!15,rounded corners, draw]{\textsf{chiusura stream}}; - \draw[<->, dashed] (exithandler1) -- (exit); - \draw[<->, dashed] (exithandler2) -- (exit); - \draw[<->, dashed] (stream) -- (exit); - \end{tikzpicture} + % \draw[<->, dashed] (exithandler1) -- (exit); + % \draw[<->, dashed] (exithandler2) -- (exit); + % \draw[<->, dashed] (stream) -- (exit); + % \end{tikzpicture} \caption{Schema dell'avvio e della conclusione di un programma.} \label{fig:proc_prog_start_stop} \end{figure} @@ -857,27 +860,27 @@ programma C viene suddiviso nei seguenti segmenti: \begin{figure}[htb] \centering -% \includegraphics[height=12cm]{img/memory_layout} - \begin{tikzpicture} - \draw (0,0) rectangle (4,1); - \draw (2,0.5) node {\textit{text}}; - \draw (0,1) rectangle (4,2.5); - \draw (2,1.75) node {dati inizializzati}; - \draw (0,2.5) rectangle (4,5); - \draw (2,3.75) node {dati non inizializzati}; - \draw (0,5) rectangle (4,9); - \draw[dashed] (0,6) -- (4,6); - \draw[dashed] (0,8) -- (4,8); - \draw (2,5.5) node {\textit{heap}}; - \draw (2,8.5) node {\textit{stack}}; - \draw [->] (2,6) -- (2,6.5); - \draw [->] (2,8) -- (2,7.5); - \draw (0,9) rectangle (4,10); - \draw (2,9.5) node {\textit{environment}}; - \draw (4,0) node [anchor=west] {\texttt{0x08000000}}; - \draw (4,5) node [anchor=west] {\texttt{0x08xxxxxx}}; - \draw (4,9) node [anchor=west] {\texttt{0xC0000000}}; - \end{tikzpicture} + \includegraphics[height=10cm]{img/memory_layout} + % \begin{tikzpicture} + % \draw (0,0) rectangle (4,1); + % \draw (2,0.5) node {\textit{text}}; + % \draw (0,1) rectangle (4,2.5); + % \draw (2,1.75) node {dati inizializzati}; + % \draw (0,2.5) rectangle (4,5); + % \draw (2,3.75) node {dati non inizializzati}; + % \draw (0,5) rectangle (4,9); + % \draw[dashed] (0,6) -- (4,6); + % \draw[dashed] (0,8) -- (4,8); + % \draw (2,5.5) node {\textit{heap}}; + % \draw (2,8.5) node {\textit{stack}}; + % \draw [->] (2,6) -- (2,6.5); + % \draw [->] (2,8) -- (2,7.5); + % \draw (0,9) rectangle (4,10); + % \draw (2,9.5) node {\textit{environment}}; + % \draw (4,0) node [anchor=west] {\texttt{0x08000000}}; + % \draw (4,5) node [anchor=west] {\texttt{0x08xxxxxx}}; + % \draw (4,9) node [anchor=west] {\texttt{0xC0000000}}; + % \end{tikzpicture} \caption{Disposizione tipica dei segmenti di memoria di un processo.} \label{fig:proc_mem_layout} \end{figure} @@ -995,9 +998,9 @@ essere esplicitamente rilasciata usando la funzione \funcd{free},\footnote{le Questa funzione vuole come argomento \var{ptr} il puntatore restituito da una precedente chiamata ad una qualunque delle funzioni di allocazione che non sia -già stato liberato da un'altra chiamata a \func{free}. Se il valore -di \param{ptr} è \val{NULL} la funzione non fa niente, mentre se l'area di -memoria era già stata liberata da un precedente chiamata il comportamento +già stato liberato da un'altra chiamata a \func{free}. Se il valore di +\param{ptr} è \val{NULL} la funzione non fa niente, mentre se l'area di +memoria era già stata liberata da una precedente chiamata il comportamento della funzione è dichiarato indefinito, ma in genere comporta la corruzione dei dati di gestione dell'allocazione, che può dar luogo a problemi gravi, ad esempio un \textit{segmentation fault} in una successiva chiamata di una di @@ -1188,7 +1191,7 @@ programma\footnote{questo comporta anche il fatto che non è possibile modificare il puntatore nello \textit{stack} e non c'è modo di sapere se se ne sono superate le dimensioni, per cui in caso di fallimento nell'allocazione il comportamento del programma può risultare indefinito, dando luogo ad una -\textit{segment violation} la prima volta che cercherà di accedere alla +\textit{segment violation} la prima volta che si cerchi di accedere alla memoria non effettivamente disponibile. \index{segmento!dati|(} @@ -1305,7 +1308,7 @@ memoria virtuale è disponibile una apposita funzione di sistema, \funcd{mincore}, che però non è standardizzata da POSIX e pertanto non è disponibile su tutte le versioni di kernel unix-like;\footnote{nel caso di Linux devono essere comunque definite le macro \macro{\_BSD\_SOURCE} e - \macro{\_SVID\_SOURCE}.} il suo prototipo è: + \macro{\_SVID\_SOURCE} o \macro{\_DEFAULT\_SOURCE}.} il suo prototipo è: \begin{funcproto}{ \fhead{unistd.h} @@ -1351,7 +1354,7 @@ la pagina di memoria corrispondente è al momento residente in memoria, o cancellato altrimenti. Il comportamento sugli altri bit è indefinito, essendo questi al momento riservati per usi futuri. Per questo motivo in genere è comunque opportuno inizializzare a zero il contenuto del vettore, così che le -pagine attualmente residenti in memoria saranno indicata da un valore non +pagine attualmente residenti in memoria saranno indicate da un valore non nullo del byte corrispondente. Dato che lo stato della memoria di un processo può cambiare continuamente, il @@ -1422,11 +1425,15 @@ loro prototipi sono: {Entrambe le funzioni ritornano $0$ in caso di successo e $-1$ in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: \begin{errlist} - \item[\errcode{EINVAL}] \param{len} non è un valore positivo. + \item[\errcode{EAGAIN}] una parte o tutto l'intervallo richiesto non può + essere bloccato per una mancanza temporanea di risorse. + \item[\errcode{EINVAL}] \param{len} non è un valore positivo o la somma con + \param{addr} causa un overflow. \item[\errcode{ENOMEM}] alcuni indirizzi dell’intervallo specificato non - corrispondono allo spazio di indirizzi del processo o si è superato il - limite di \const{RLIMIT\_MEMLOCK} per un processo non privilegiato (solo - per kernel a partire dal 2.6.9). + corrispondono allo spazio di indirizzi del processo o con \func{mlock} si + è superato il limite di \const{RLIMIT\_MEMLOCK} per un processo non + privilegiato (solo per kernel a partire dal 2.6.9) o si è superato il + limite di regioni di memoria con attributi diversi. \item[\errcode{EPERM}] il processo non è privilegiato (per kernel precedenti il 2.6.9) o si ha un limite nullo per \const{RLIMIT\_MEMLOCK} e il processo non è privilegiato (per kernel a partire dal 2.6.9). @@ -1435,15 +1442,13 @@ loro prototipi sono: Le due funzioni permettono rispettivamente di bloccare e sbloccare la paginazione per l'intervallo di memoria iniziante all'indirizzo \param{addr} e -lungo \param{len} byte. Tutte le pagine che contengono una parte -dell'intervallo bloccato sono mantenute in RAM per tutta la durata del -blocco. Con kernel diversi da Linux si può ottenere un errore di -\errcode{EINVAL} se \param{addr} non è un multiplo della dimensione delle -pagine di memoria, pertanto se si ha a cuore la portabilità si deve avere cura -di allinearne correttamente il valore. - -% TODO trattare mlock2, introdotta con il kernel 4.4 (vedi -% http://lwn.net/Articles/650538/) +lungo \param{len} byte. Al ritorno di \func{mlock} tutte le pagine che +contengono una parte dell'intervallo bloccato sono garantite essere in RAM e +vi verranno mantenute per tutta la durata del blocco. Con kernel diversi da +Linux si può ottenere un errore di \errcode{EINVAL} se \param{addr} non è un +multiplo della dimensione delle pagine di memoria, pertanto se si ha a cuore +la portabilità si deve avere cura di allinearne correttamente il valore. Il +blocco viene rimosso chiamando \func{munlock}. Altre due funzioni di sistema, \funcd{mlockall} e \funcd{munlockall}, consentono di bloccare genericamente la paginazione per l'intero spazio di @@ -1463,7 +1468,7 @@ indirizzi di un processo. I prototipi di queste funzioni sono: L'argomento \param{flags} di \func{mlockall} permette di controllarne il comportamento; esso deve essere specificato come maschera binaria dei valori -espressi dalle costanti riportate in tab.~\ref{tab:mlockall_flags}. +espressi dalle costanti riportate in tab.~\ref{tab:mlockall_flags}. \begin{table}[htb] \footnotesize @@ -1477,6 +1482,8 @@ espressi dalle costanti riportate in tab.~\ref{tab:mlockall_flags}. spazio di indirizzi del processo.\\ \constd{MCL\_FUTURE} & blocca tutte le pagine che verranno mappate nello spazio di indirizzi del processo.\\ + \constd{MCL\_ONFAULT}& esegue il blocco delle pagine selezionate solo + quando vengono utilizzate (dal kernel 4.4).\\ \hline \end{tabular} \caption{Valori e significato dell'argomento \param{flags} della funzione @@ -1491,22 +1498,68 @@ file mappati in memoria, i dati del kernel mappati in user space, la memoria condivisa. L'uso dell'argomento \param{flags} permette di selezionare con maggior finezza le pagine da bloccare, ad esempio usando \const{MCL\_FUTURE} ci si può limitare a tutte le pagine allocate a partire dalla chiamata della -funzione. - -In ogni caso un processo \textit{real-time} che deve entrare in una sezione -critica (vedi sez.~\ref{sec:proc_race_cond}) deve provvedere a riservare -memoria sufficiente prima dell'ingresso, per scongiurare l'occorrenza di un -eventuale \textit{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 \textit{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 -che esse vengano mappate in RAM dallo \textit{stack}, dopo di che, per essere -sicuri che esse siano state effettivamente portate in memoria, ci si scrive -sopra. +funzione, mentre \const{MCL\_CURRENT} blocca tutte quelle correntemente +mappate. L'uso di \func{munlockall} invece sblocca sempre tutte le pagine di +memoria correntemente mappate nello spazio di indirizzi del programma. + +A partire dal kernel 4.4 alla funzione \func{mlockall} è stato aggiunto un +altro flag, \const{MCL\_ONFAULT}, che può essere abbinato a entrambi gli altri +due flag, e consente di modificare il comportamento della funzione per +ottenere migliori prestazioni. + +Il problema che si presenta infatti è che eseguire un \textit{memory lock} per +un intervallo ampio di memoria richiede che questa venga comunque allocata in +RAM, con altrettanti \textit{page fault} che ne assicurino la presenza; questo +vale per tutto l'intervallo e può avere un notevole costo in termini di +prestazioni, anche quando poi, nell'esecuzione del programma, venisse usata +solo una piccola parte dello stesso. L'uso di \const{MCL\_ONFAULT} previene il +\textit{page faulting} immediato di tutto l'intervallo, le pagine +dell'intervallo verranno bloccate, ma solo quando un \textit{page fault} +dovuto all'accesso ne richiede l'allocazione effettiva in RAM. + +Questo stesso comportamento non è ottenibile con \func{mlock}, che non dispone +di un argomento \param{flag} che consenta di richiederlo, per questo sempre +con il kernel 4.4 è stata aggiunta una ulteriore funzione di sistema, +\funcd{mlock2}, il cui prototipo è: + +\begin{funcproto}{ + \fhead{sys/mman.h} + \fdecl{int mlock2(const void *addr, size\_t len, int flags)} + \fdesc{Blocca la paginazione su un intervallo di memoria.} +} +{Le funzione ritornano $0$ in caso di successo e $-1$ in caso di errore, nel + qual caso \var{errno} assume gli stessi valori di \func{mlock} con + l'aggiunta id un possibile \errcode{EINVAL} anche se si è indicato un valore + errato di \param{flags}.} +\end{funcproto} + +% NOTA: per mlock2, introdotta con il kernel 4.4 (vedi +% http://lwn.net/Articles/650538/) + +Indicando un valore nullo per \param{flags} il comportamento della funzione è +identico a quello di \func{mlock}, l'unico altro valore possibile è +\constd{MLOCK\_ONFAULT} che ha lo stesso effetto sull'allocazione delle pagine +in RAM già descritto per \const{MCL\_ONFAULT}. + +Si tenga presente che un processo \textit{real-time} che intende usare il +\textit{memory locking} con \func{mlockall} per prevenire l'avvenire di un +eventuale \textit{page fault} ed il conseguente rallentamento (probabilmente +inaccettabile) dei tempi di esecuzione, deve comunque avere delle accortezze. +In particolare si deve assicurare di aver preventivamente bloccato una +quantità di spazio nello \textit{stack} sufficiente a garantire l'esecuzione +di tutte le funzioni che hanno i requisiti di criticità sui tempi. Infatti, +anche usando \const{MCL\_FUTURE}, in caso di allocazione di una nuova pagina +nello \textit{stack} durante l'esecuzione di una funzione (precedentemente non +usata e quindi non bloccata) si potrebbe avere un \textit{page fault}. + +In genere si ovvia a questa problematica chiamando inizialmente una funzione +che definisca una quantità sufficientemente ampia di variabili automatiche +(che si ricordi vengono allocate nello \textit{stack}) e ci scriva, in modo da +esser sicuri che le corrispondenti pagine vengano mappate nello spazio di +indirizzi del processo, per poi bloccarle. La scrittura è necessaria perché il +kernel usa il meccanismo di \textit{copy on write} (vedi +sez.~\ref{sec:proc_fork}) e le pagine potrebbero non essere allocate +immediatamente. \itindend{memory~locking} \index{memoria~virtuale|)} @@ -1534,8 +1587,9 @@ tal caso l'uso di \func{malloc} non è sufficiente, ed occorre utilizzare una funzione specifica. Tradizionalmente per rispondere a questa esigenza sono state create due -funzioni diverse, \funcd{memalign} e \funcd{valloc}, oggi obsolete; i -rispettivi prototipi sono: +funzioni diverse, \funcd{memalign} e \funcd{valloc}, oggi obsolete, cui si +aggiunge \funcd{pvalloc} come estensione GNU, anch'essa obsoleta; i rispettivi +prototipi sono: \begin{funcproto}{ \fhead{malloc.h} @@ -1545,6 +1599,9 @@ rispettivi prototipi sono: \fdecl{void *memalign(size\_t boundary, size\_t size)} \fdesc{Alloca un blocco di memoria allineato ad un multiplo di \param{boundary}.} +\fdecl{void *pvalloc(size\_t size)} +\fdesc{Alloca un blocco di memoria allineato alla dimensione di una pagina di + memoria.} } {Entrambe le funzioni ritornano un puntatore al blocco di memoria allocato in caso di successo e \val{NULL} in caso di errore, nel qual caso \var{errno} @@ -1556,11 +1613,13 @@ rispettivi prototipi sono: \end{funcproto} Le funzioni restituiscono il puntatore al buffer di memoria allocata di -dimensioni pari a \param{size}, che per \func{memalign} sarà un multiplo -di \param{boundary} mentre per \func{valloc} un multiplo della dimensione di -una pagina di memoria. Nel caso della versione fornita dalla \acr{glibc} la -memoria allocata con queste funzioni deve essere liberata con \func{free}, -cosa che non è detto accada con altre implementazioni. +dimensioni pari a \param{size}, che per \func{memalign} sarà un multiplo di +\param{boundary} mentre per \func{valloc} un multiplo della dimensione di una +pagina di memoria; lo stesso vale per \func{pvalloc} che però arrotonda +automaticamente la dimensione dell'allocazione al primo multiplo di una +pagina. Nel caso della versione fornita dalla \acr{glibc} la memoria allocata +con queste funzioni deve essere liberata con \func{free}, cosa che non è detto +accada con altre implementazioni. Nessuna delle due funzioni ha una chiara standardizzazione e nessuna delle due compare in POSIX.1, inoltre ci sono indicazioni discordi sui file che ne @@ -1569,8 +1628,8 @@ contengono la definizione;\footnote{secondo SUSv2 \func{valloc} è definita in e \acr{libc5} la dichiarano in \headfile{malloc.h}, lo stesso vale per \func{memalign} che in alcuni sistemi è dichiarata in \headfile{stdlib.h}.} per questo motivo il loro uso è sconsigliato, essendo state sostituite dalla -nuova \funcd{posix\_memalign}, che è stata standardizzata in POSIX.1d; il suo -prototipo è: +nuova \funcd{posix\_memalign}, che è stata standardizzata in POSIX.1d e +disponibile dalle \acr{glibc} 2.1.91; il suo prototipo è: \begin{funcproto}{ \fhead{stdlib.h} @@ -1582,7 +1641,7 @@ prototipo è: caso di successo e \val{NULL} in caso di errore, nel qual caso \var{errno} assumerà uno dei valori: \begin{errlist} - \item[\errcode{EINVAL}] \param{alignment} non è potenza di due e multiplo + \item[\errcode{EINVAL}] \param{alignment} non è potenza di due o un multiplo di \code{sizeof(void *)}. \item[\errcode{ENOMEM}] non c'è memoria sufficiente per l'allocazione. \end{errlist}} @@ -1594,10 +1653,26 @@ indicato da \param{memptr}. La funzione fallisce nelle stesse condizioni delle due funzioni precedenti, ma a loro differenza restituisce direttamente come valore di ritorno il codice di errore. Come per le precedenti la memoria allocata con \func{posix\_memalign} deve essere disallocata con \func{free}, -che in questo caso però è quanto richiesto dallo standard. Si tenga presente -infine che nessuna di queste funzioni inizializza il buffer di memoria -allocato, il loro comportamento cioè è analogo, allineamento a parte, a quello -di \func{malloc}. +che in questo caso però è quanto richiesto dallo standard. + +Dalla versione 2.16 delle \acr{glibc} è stata aggiunta anche la funzione +\funcd{aligned\_alloc}, prevista dallo standard C11 (e disponibile definendo +\const{\_ISOC11\_SOURCE}), il cui prototipo è: + +\begin{funcproto}{ +\fhead{malloc.h} +\fdecl{void *aligned\_alloc(size\_t alignment, size\_t size)} +\fdesc{Alloca un blocco di memoria allineato ad un multiplo + di \param{alignment}.} +} +{La funzione ha gli stessi valori di ritorno e codici di errore di + \func{memalign}.} +\end{funcproto} + +La funzione è identica a \func{memalign} ma richiede che \param{size} sia un +multiplo di \param{alignment}. Infine si tenga presente infine che nessuna di +queste funzioni inizializza il buffer di memoria allocato, il loro +comportamento cioè è analogo, allineamento a parte, a quello di \func{malloc}. Un secondo caso in cui risulta estremamente utile poter avere un maggior controllo delle modalità di allocazione della memoria è quello in cui cercano @@ -1623,7 +1698,7 @@ suo prototipo è: \fdecl{int mcheck(void (*abortfn) (enum mcheck\_status status))} \fdesc{Attiva i controlli di consistenza delle allocazioni di memoria.} } -{La funzione ritorna $0$ in caso di successo e $-1$ per un errorre; +{La funzione ritorna $0$ in caso di successo e $-1$ per un errore; \var{errno} non viene impostata.} \end{funcproto} @@ -1738,34 +1813,32 @@ contengono degli spazi evitando di spezzarli in parole diverse. \begin{figure}[htb] \centering -% \includegraphics[width=13cm]{img/argv_argc} -% \includegraphics[width=13cm]{img/argv_argc} - \begin{tikzpicture}[>=stealth] - \draw (0.5,2.5) rectangle (3.5,3); - \draw (2,2.75) node {\texttt{argc = 5}}; - \draw (5,2.5) rectangle (8,3); - \draw (6.5,2.75) node {\texttt{argv[0]}}; - \draw [->] (8,2.75) -- (9,2.75); - \draw (9,2.75) node [anchor=west] {\texttt{"touch"}}; - \draw (5,2) rectangle (8,2.5); - \draw (6.5,2.25) node {\texttt{argv[1]}}; - \draw [->] (8,2.25) -- (9,2.25); - \draw (9,2.25) node [anchor=west] {\texttt{"-r"}}; - \draw (5,1.5) rectangle (8,2); - \draw (6.5,1.75) node {\texttt{argv[2]}}; - \draw [->] (8,1.75) -- (9,1.75); - \draw (9,1.75) node [anchor=west] {\texttt{"riferimento.txt"}}; - \draw (5,1.0) rectangle (8,1.5); - \draw (6.5,1.25) node {\texttt{argv[3]}}; - \draw [->] (8,1.25) -- (9,1.25); - \draw (9,1.25) node [anchor=west] {\texttt{"-m"}}; - \draw (5,0.5) rectangle (8,1.0); - \draw (6.5,0.75) node {\texttt{argv[4]}}; - \draw [->] (8,0.75) -- (9,0.75); - \draw (9,0.75) node [anchor=west] {\texttt{"questofile.txt"}}; - \draw (4.25,3.5) node{\texttt{"touch -r riferimento.txt -m questofile.txt"}}; - - \end{tikzpicture} + \includegraphics[width=13cm]{img/argv_argc} + % \begin{tikzpicture}[>=stealth] + % \draw (0.5,2.5) rectangle (3.5,3); + % \draw (2,2.75) node {\texttt{argc = 5}}; + % \draw (5,2.5) rectangle (8,3); + % \draw (6.5,2.75) node {\texttt{argv[0]}}; + % \draw [->] (8,2.75) -- (9,2.75); + % \draw (9,2.75) node [anchor=west] {\texttt{"touch"}}; + % \draw (5,2) rectangle (8,2.5); + % \draw (6.5,2.25) node {\texttt{argv[1]}}; + % \draw [->] (8,2.25) -- (9,2.25); + % \draw (9,2.25) node [anchor=west] {\texttt{"-r"}}; + % \draw (5,1.5) rectangle (8,2); + % \draw (6.5,1.75) node {\texttt{argv[2]}}; + % \draw [->] (8,1.75) -- (9,1.75); + % \draw (9,1.75) node [anchor=west] {\texttt{"riferimento.txt"}}; + % \draw (5,1.0) rectangle (8,1.5); + % \draw (6.5,1.25) node {\texttt{argv[3]}}; + % \draw [->] (8,1.25) -- (9,1.25); + % \draw (9,1.25) node [anchor=west] {\texttt{"-m"}}; + % \draw (5,0.5) rectangle (8,1.0); + % \draw (6.5,0.75) node {\texttt{argv[4]}}; + % \draw [->] (8,0.75) -- (9,0.75); + % \draw (9,0.75) node [anchor=west] {\texttt{"questofile.txt"}}; + % \draw (4.25,3.5) node{\texttt{"touch -r riferimento.txt -m questofile.txt"}}; + % \end{tikzpicture} \caption{Esempio dei valori di \param{argv} e \param{argc} generati nella scansione di una riga di comando.} \label{fig:proc_argv_argc} @@ -1791,9 +1864,11 @@ tali: un elemento di \param{argv} successivo al primo che inizia con il carattere ``\texttt{-}'' e che non sia un singolo ``\texttt{-}'' o un ``\texttt{-{}-}'' viene considerato un'opzione. In genere le opzioni sono costituite da una lettera singola (preceduta dal carattere ``\texttt{-}'') e -possono avere o no un parametro associato. Un esempio tipico può essere quello -mostrato in fig.~\ref{fig:proc_argv_argc}. In quel caso le opzioni sono -\cmd{-r} e \cmd{-m} e la prima vuole un parametro mentre la seconda no +possono avere o no un parametro associato. + +Un esempio tipico può essere quello mostrato in +fig.~\ref{fig:proc_argv_argc}. In quel caso le opzioni sono \cmd{-r} e +\cmd{-m} e la prima vuole un parametro mentre la seconda no (\cmd{questofile.txt} è un argomento del programma, non un parametro di \cmd{-m}). @@ -1834,16 +1909,6 @@ ritornato il carattere ``\texttt{:}'', infine se viene incontrato il valore ``\texttt{-{}-}'' la scansione viene considerata conclusa, anche se vi sono altri elementi di \param{argv} che cominciano con il carattere ``\texttt{-}''. -\begin{figure}[!htb] - \footnotesize \centering - \begin{minipage}[c]{\codesamplewidth} - \includecodesample{listati/option_code.c} - \end{minipage} - \normalsize - \caption{Esempio di codice per la gestione delle opzioni.} - \label{fig:proc_options_code} -\end{figure} - Quando \func{getopt} trova un'opzione fra quelle indicate in \param{optstring} essa ritorna il valore numerico del carattere, in questo modo si possono eseguire azioni specifiche usando uno \instruction{switch}; la funzione @@ -1858,6 +1923,16 @@ inoltre inizializza alcune variabili globali: \item \var{int optopt} contiene il carattere dell'opzione non riconosciuta. \end{itemize*} +\begin{figure}[!htb] + \footnotesize \centering + \begin{minipage}[c]{\codesamplewidth} + \includecodesample{listati/option_code.c} + \end{minipage} + \normalsize + \caption{Esempio di codice per la gestione delle opzioni.} + \label{fig:proc_options_code} +\end{figure} + In fig.~\ref{fig:proc_options_code} si è mostrata la sezione del programma \file{fork\_test.c}, che useremo nel prossimo capitolo per effettuare dei test sulla creazione dei processi, deputata alla decodifica delle opzioni a riga di @@ -1923,34 +1998,34 @@ più comuni che normalmente sono definite dal sistema, è riportato in fig.~\ref{fig:proc_envirno_list}. \begin{figure}[htb] \centering -% \includegraphics[width=15 cm]{img/environ_var} - \begin{tikzpicture}[>=stealth] - \draw (2,3.5) node {\textsf{Environment pointer}}; - \draw (6,3.5) node {\textsf{Environment list}}; - \draw (10.5,3.5) node {\textsf{Environment string}}; - \draw (0.5,2.5) rectangle (3.5,3); - \draw (2,2.75) node {\texttt{environ}}; - \draw [->] (3.5,2.75) -- (4.5,2.75); - \draw (4.5,2.5) rectangle (7.5,3); - \draw (6,2.75) node {\texttt{environ[0]}}; - \draw (4.5,2) rectangle (7.5,2.5); - \draw (6,2.25) node {\texttt{environ[1]}}; - \draw (4.5,1.5) rectangle (7.5,2); - \draw (4.5,1) rectangle (7.5,1.5); - \draw (4.5,0.5) rectangle (7.5,1); - \draw (4.5,0) rectangle (7.5,0.5); - \draw (6,0.25) node {\texttt{NULL}}; - \draw [->] (7.5,2.75) -- (8.5,2.75); - \draw (8.5,2.75) node[right] {\texttt{HOME=/home/piccardi}}; - \draw [->] (7.5,2.25) -- (8.5,2.25); - \draw (8.5,2.25) node[right] {\texttt{PATH=:/bin:/usr/bin}}; - \draw [->] (7.5,1.75) -- (8.5,1.75); - \draw (8.5,1.75) node[right] {\texttt{SHELL=/bin/bash}}; - \draw [->] (7.5,1.25) -- (8.5,1.25); - \draw (8.5,1.25) node[right] {\texttt{EDITOR=emacs}}; - \draw [->] (7.5,0.75) -- (8.5,0.75); - \draw (8.5,0.75) node[right] {\texttt{OSTYPE=linux-gnu}}; - \end{tikzpicture} + \includegraphics[width=13cm]{img/environ_var} + % \begin{tikzpicture}[>=stealth] + % \draw (2,3.5) node {\textsf{Environment pointer}}; + % \draw (6,3.5) node {\textsf{Environment list}}; + % \draw (10.5,3.5) node {\textsf{Environment string}}; + % \draw (0.5,2.5) rectangle (3.5,3); + % \draw (2,2.75) node {\texttt{environ}}; + % \draw [->] (3.5,2.75) -- (4.5,2.75); + % \draw (4.5,2.5) rectangle (7.5,3); + % \draw (6,2.75) node {\texttt{environ[0]}}; + % \draw (4.5,2) rectangle (7.5,2.5); + % \draw (6,2.25) node {\texttt{environ[1]}}; + % \draw (4.5,1.5) rectangle (7.5,2); + % \draw (4.5,1) rectangle (7.5,1.5); + % \draw (4.5,0.5) rectangle (7.5,1); + % \draw (4.5,0) rectangle (7.5,0.5); + % \draw (6,0.25) node {\texttt{NULL}}; + % \draw [->] (7.5,2.75) -- (8.5,2.75); + % \draw (8.5,2.75) node[right] {\texttt{HOME=/home/piccardi}}; + % \draw [->] (7.5,2.25) -- (8.5,2.25); + % \draw (8.5,2.25) node[right] {\texttt{PATH=:/bin:/usr/bin}}; + % \draw [->] (7.5,1.75) -- (8.5,1.75); + % \draw (8.5,1.75) node[right] {\texttt{SHELL=/bin/bash}}; + % \draw [->] (7.5,1.25) -- (8.5,1.25); + % \draw (8.5,1.25) node[right] {\texttt{EDITOR=emacs}}; + % \draw [->] (7.5,0.75) -- (8.5,0.75); + % \draw (8.5,0.75) node[right] {\texttt{OSTYPE=linux-gnu}}; + % \end{tikzpicture} \caption{Esempio di lista delle variabili di ambiente.} \label{fig:proc_envirno_list} \end{figure} @@ -2075,7 +2150,7 @@ stringa che ne contiene il valore, nella forma ``\texttt{NOME=valore}''. 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 impostare, modificare e per cancellare le variabili di +utilizzare per impostare, modificare e cancellare le variabili di ambiente. Uno schema delle funzioni previste nei vari standard e disponibili in Linux è riportato in tab.~\ref{tab:proc_env_func}. Tutte le funzioni sono state comunque inserite nello standard POSIX.1-2001, ad eccetto di @@ -2119,7 +2194,7 @@ sostituendo il relativo puntatore;\footnote{il comportamento è lo stesso delle dal prototipo.} pertanto ogni cambiamento alla stringa in questione si riflette automaticamente sull'ambiente, e quindi si deve evitare di passare a questa funzione una variabile automatica (per evitare i problemi esposti in -sez.~\ref{sec:proc_var_passing}). Benché non sia richiesto dallo standard +sez.~\ref{sec:proc_var_passing}). Benché non sia richiesto dallo standard, nelle versioni della \acr{glibc} a partire dalla 2.1 la funzione è rientrante (vedi sez.~\ref{sec:proc_reentrant}). @@ -2678,10 +2753,11 @@ dei seguenti casi: \item come espressione a sé stante. \end{itemize*} -In generale, dato che l'unica differenza fra la chiamata diretta e quella -ottenuta nell'uscita con un \func{longjmp} è costituita dal valore di ritorno -di \func{setjmp}, pertanto quest'ultima viene usualmente chiamata all'interno -di un una istruzione \instr{if} che permetta di distinguere i due casi. +In generale, dato che l'unica differenza fra il risultato di una chiamata +diretta di \func{setjmp} e quello ottenuto nell'uscita con un \func{longjmp} è +costituita dal valore di ritorno della funzione, quest'ultima viene usualmente +chiamata all'interno di un una istruzione \instr{if} che permetta di +distinguere i due casi. Uno dei punti critici dei salti non-locali è quello del valore delle variabili, ed in particolare quello delle variabili automatiche della funzione @@ -2719,13 +2795,13 @@ dichiarandole tutte come \direct{volatile}.\footnote{la direttiva \itindbeg{endianness} Un altro dei problemi di programmazione che può dar luogo ad effetti -imprevisti è quello relativo alla cosiddetta \textit{endianness}. Questa è una -caratteristica generale dell'architettura hardware di un computer che dipende -dal fatto che la rappresentazione di un numero binario può essere fatta in due -modi, chiamati rispettivamente \textit{big endian} e \textit{little endian} a -seconda di come i singoli bit vengono aggregati per formare le variabili -intere (ed in genere in diretta corrispondenza a come sono poi in realtà -cablati sui bus interni del computer). +imprevisti è quello relativo alla cosiddetta \textit{endianness}. Questa è +una caratteristica generale dell'architettura hardware di un computer che +dipende dal fatto che la rappresentazione di un numero binario può essere +fatta in due modi, chiamati rispettivamente \textit{big endian} e +\textit{little endian}, a seconda di come i singoli bit vengono aggregati per +formare le variabili intere (ed in genere in diretta corrispondenza a come +sono poi in realtà cablati sui bus interni del computer). \begin{figure}[!htb] \centering \includegraphics[height=3cm]{img/endianness} @@ -2824,12 +2900,11 @@ il valore del confronto delle due variabili. In generale non ci si deve preoccupare della \textit{endianness} all'interno di un programma fintanto che questo non deve generare o manipolare dei dati -che sono scambiati con altre macchine, ad esempio tramite via rete o tramite -dei file binari. Nel primo caso la scelta è già stata fatta nella -standardizzazione dei protocolli, che hanno adottato il \textit{big endian} -(che viene detto anche per questo \textit{network order} e vedremo in -sez.~\ref{sec:sock_func_ord} le funzioni di conversione che devono essere -usate. +che sono scambiati con altre macchine, ad esempio via rete o tramite dei file +binari. Nel primo caso la scelta è già stata fatta nella standardizzazione dei +protocolli, che hanno adottato il \textit{big endian} (che viene detto anche +per questo \textit{network order}); vedremo in sez.~\ref{sec:sock_func_ord} le +funzioni di conversione che devono essere usate. Nel secondo caso occorre sapere quale \textit{endianness} è stata usata nei dati memorizzati sul file e tenerne conto nella rilettura e nella @@ -2854,7 +2929,7 @@ basterà scegliere una volta per tutte quale usare e attenersi alla scelta. % LocalWords: capability MEMLOCK limits getpagesize RLIMIT munlock sys const % LocalWords: addr len EINVAL EPERM mlockall munlockall flags l'OR CURRENT IFS % LocalWords: argc argv parsing questofile txt getopt optstring switch optarg -% LocalWords: optind opterr optopt POSIXLY CORRECT long options NdA +% LocalWords: optind opterr optopt POSIXLY CORRECT long options NdA group % LocalWords: option parameter list environ PATH HOME XPG tab LOGNAME LANG PWD % LocalWords: TERM PAGER TMPDIR getenv name SVr setenv unsetenv putenv opz gcc % LocalWords: clearenv libc value overwrite string reference result argument @@ -2871,9 +2946,10 @@ basterà scegliere una volta per tutte quale usare e attenersi alla scelta. % LocalWords: is to LC SIG str mem wcs assert ctype dirent fcntl signal stdio % LocalWords: times library utmp syscall number Filesystem Hierarchy pathname % LocalWords: context assembler sysconf fork Dinamic huge segmentation program -% LocalWords: break store Using +% LocalWords: break store using intptr ssize overflow ONFAULT faulting alloc %%% Local Variables: %%% mode: latex %%% TeX-master: "gapil" %%% End: +% LocalWords: scheduler pvalloc aligned ISOC ABCDEF diff --git a/prochand.tex b/prochand.tex index fc5e477..1d8cadb 100644 --- a/prochand.tex +++ b/prochand.tex @@ -39,12 +39,12 @@ terminazione dei processi, e per la messa in esecuzione degli altri programmi. \subsection{L'architettura della gestione dei processi} \label{sec:proc_hierarchy} -A differenza di quanto avviene in altri sistemi, ad esempio nel VMS la +A differenza di quanto avviene in altri sistemi, ad esempio nel VMS, dove la generazione di nuovi processi è un'operazione privilegiata, una delle caratteristiche fondanti di Unix, che esamineremo in dettaglio più avanti, è che qualunque processo può a sua volta generarne altri. Ogni processo è identificato presso il sistema da un numero univoco, il cosiddetto -\textit{Process ID} o, più brevemente, \ids{PID}, assegnato in forma +\textit{Process ID}, o più brevemente \ids{PID}, assegnato in forma progressiva (vedi sez.~\ref{sec:proc_pid}) quando il processo viene creato. Una seconda caratteristica di un sistema unix-like è che la generazione di un @@ -57,16 +57,16 @@ indichiamo nella linea di comando. Una terza caratteristica del sistema è che ogni processo è sempre stato generato da un altro processo, il processo generato viene chiamato \textit{processo figlio} (\textit{child process}) mentre quello che lo ha -viene chiamato \textsl{processo padre} (\textit{parent process}). Questo vale -per tutti i processi, con una sola eccezione, dato che ci deve essere un punto -di partenza esiste un processo speciale (che normalmente è \cmd{/sbin/init}), -che come abbiamo accennato in sez.~\ref{sec:intro_kern_and_sys} viene lanciato -dal kernel alla conclusione della fase di avvio. Essendo questo il primo -processo lanciato dal sistema ha sempre il \ids{PID} uguale a 1 e non è figlio -di nessun altro processo. - -Ovviamente \cmd{init} è un processo speciale che in genere si occupa di far -partire tutti gli altri processi necessari al funzionamento del sistema, +generato viene chiamato \textsl{processo padre} (\textit{parent + process}). Questo vale per tutti i processi, con una sola eccezione; dato +che ci deve essere un punto di partenza esiste un processo iniziale (che +normalmente è \cmd{/sbin/init}), che come accennato in +sez.~\ref{sec:intro_kern_and_sys} viene lanciato dal kernel alla conclusione +della fase di avvio. Essendo questo il primo processo lanciato dal sistema ha +sempre \ids{PID} uguale a 1 e non è figlio di nessun altro processo. + +Ovviamente \cmd{init} è un processo particolare che in genere si occupa di +lanciare tutti gli altri processi necessari al funzionamento del sistema, inoltre \cmd{init} è essenziale per svolgere una serie di compiti amministrativi nelle operazioni ordinarie del sistema (torneremo su alcuni di essi in sez.~\ref{sec:proc_termination}) e non può mai essere terminato. La @@ -138,10 +138,10 @@ Il kernel mantiene una tabella dei processi attivi, la cosiddetta questa tabella, costituita da una struttura \kstruct{task\_struct}, che contiene tutte le informazioni rilevanti per quel processo. Tutte le strutture usate a questo scopo sono dichiarate nell'\textit{header file} -\file{linux/sched.h}, ed uno schema semplificato, che riporta la struttura -delle principali informazioni contenute nella \texttt{task\_struct} (che in -seguito incontreremo a più riprese), è mostrato in -fig.~\ref{fig:proc_task_struct}. +\file{linux/sched.h}, ed in fig.~\ref{fig:proc_task_struct} si è riportato uno +schema semplificato che mostra la struttura delle principali informazioni +contenute nella \texttt{task\_struct}, che in seguito incontreremo a più +riprese. \begin{figure}[!htb] \centering \includegraphics[width=14cm]{img/task_struct} @@ -179,7 +179,7 @@ su macchine che non stanno facendo nulla, con un forte risparmio nell'uso dell'energia da parte del processore che può essere messo in stato di sospensione anche per lunghi periodi di tempo. -Indipendentemente dalle motivazioni per cui questo avviene, ogni volta che +Ma, indipendentemente dalle motivazioni per cui questo avviene, ogni volta che viene eseguito lo \textit{scheduler} effettua il calcolo delle priorità dei vari processi attivi (torneremo su questo in sez.~\ref{sec:proc_priority}) e stabilisce quale di essi debba essere posto in esecuzione fino alla successiva @@ -194,7 +194,7 @@ invocazione. Come accennato nella sezione precedente ogni processo viene identificato dal sistema da un numero identificativo univoco, il \textit{process ID} o -\ids{PID}. Questo è un tipo di dato standard, \type{pid\_t} che in genere è un +\ids{PID}. Questo è un tipo di dato standard, \type{pid\_t}, che in genere è un intero con segno (nel caso di Linux e della \acr{glibc} il tipo usato è \ctyp{int}). @@ -272,7 +272,7 @@ sez.~\ref{sec:proc_perms}. \subsection{La funzione \func{fork} e le funzioni di creazione dei processi} \label{sec:proc_fork} -La funzione di sistema \funcd{fork} è la funzione fondamentale della gestione +La funzione di sistema \func{fork} è la funzione fondamentale della gestione dei processi: come si è detto tradizionalmente l'unico modo di creare un nuovo processo era attraverso l'uso di questa funzione,\footnote{in realtà oggi la \textit{system call} usata da Linux per creare nuovi processi è \func{clone} @@ -282,18 +282,19 @@ processo era attraverso l'uso di questa funzione,\footnote{in realtà oggi la migliore interazione coi \textit{thread}.} essa quindi riveste un ruolo centrale tutte le volte che si devono scrivere programmi che usano il multitasking.\footnote{oggi questa rilevanza, con la diffusione dell'uso dei - \textit{thread} che tratteremo al cap.~\ref{cha:threads}, è in parte minore, - ma \func{fork} resta comunque la funzione principale per la creazione di - processi.} Il prototipo della funzione è: + \textit{thread}\unavref{ che tratteremo al cap.~\ref{cha:threads}}, è in + parte minore, ma \func{fork} resta comunque la funzione principale per la + creazione di processi.} Il prototipo di \funcd{fork} è: \begin{funcproto}{ \fhead{unistd.h} \fdecl{pid\_t fork(void)} \fdesc{Crea un nuovo processo.} } -{La funzione ritorna il \ids{PID} del figlio al padre e $0$ al figlio in caso - di successo e $-1$ al padre senza creare il figlio per un errore, - nel qual caso \var{errno} assumerà uno dei valori: + +{La funzione ritorna in caso di successo il \ids{PID} del figlio nel padre e + $0$ nel figlio mentre ritorna $-1$ nel padre, senza creare il figlio, per un + errore, al caso \var{errno} assumerà uno dei valori: \begin{errlist} \item[\errcode{EAGAIN}] non ci sono risorse sufficienti per creare un altro processo (per allocare la tabella delle pagine e le strutture del task) o @@ -304,7 +305,7 @@ multitasking.\footnote{oggi questa rilevanza, con la diffusione dell'uso dei \end{funcproto} Dopo il successo dell'esecuzione di una \func{fork} sia il processo padre che -il processo figlio continuano ad essere eseguiti normalmente a partire +il processo figlio continuano ad essere eseguiti normalmente, a partire dall'istruzione successiva alla \func{fork}. Il processo figlio è una copia del padre, e riceve una copia dei segmenti di testo, dati e dello \textit{stack} (vedi sez.~\ref{sec:proc_mem_layout}), ed esegue esattamente lo @@ -334,11 +335,11 @@ eseguito dal padre o dal figlio. Si noti come la funzione \func{fork} ritorni due volte, una nel padre e una nel figlio. La scelta di questi valori di ritorno non è casuale, un processo infatti può -avere più figli, ed il valore di ritorno di \func{fork} è l'unico modo che gli -permette di identificare quello appena creato. Al contrario un figlio ha -sempre un solo padre, il cui \ids{PID} può sempre essere ottenuto con -\func{getppid}, come spiegato in sez.~\ref{sec:proc_pid}, per cui si usa il -valore nullo, che non è il \ids{PID} di nessun processo. +avere più figli, ed il valore di ritorno di \func{fork} è l'unico che gli +permette di identificare qual è quello appena creato. Al contrario un figlio +ha sempre un solo padre il cui \ids{PID}, come spiegato in +sez.~\ref{sec:proc_pid}, può sempre essere ottenuto con \func{getppid}; per +questo si ritorna un valore nullo, che non è il \ids{PID} di nessun processo. Normalmente la chiamata a \func{fork} può fallire solo per due ragioni: o ci sono già troppi processi nel sistema, il che di solito è sintomo che @@ -353,7 +354,7 @@ ne esegue un'altra. È il caso tipico dei programmi server (il modello \textit{client-server} è illustrato in sez.~\ref{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. +le risposte associate al 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 @@ -393,8 +394,8 @@ 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{fork\_test.c}, -distribuito insieme agli altri sorgenti degli esempi su -\url{http://gapil.truelite.it/gapil_source.tgz}. +distribuito insieme agli altri sorgenti degli esempi della guida su +\url{http://gapil.gnulinux.it}. Decifrato il numero di figli da creare, il ciclo principale del programma (\texttt{\small 24-40}) esegue in successione la creazione dei processi figli @@ -460,36 +461,36 @@ In realtà con l'introduzione dei kernel della serie 2.6 lo \textit{scheduler} risultati precedenti infatti sono stati ottenuti usando un kernel della serie 2.4.} Questa è una ottimizzazione adottata per evitare che il padre, effettuando per primo una operazione di scrittura in memoria, attivasse il -meccanismo del \textit{copy on write}, operazione inutile qualora il figlio -venga creato solo per eseguire una \func{exec} su altro programma che scarta -completamente lo spazio degli indirizzi e rende superflua la copia della -memoria modificata dal padre. Eseguendo sempre per primo il figlio la -\func{exec} verrebbe effettuata subito, con la certezza di utilizzare +meccanismo del \textit{copy on write}, operazione inutile quando il figlio +viene creato solo per eseguire una \func{exec} per lanciare un altro programma +che scarta completamente lo spazio degli indirizzi e rende superflua la copia +della memoria modificata dal padre. Eseguendo sempre per primo il figlio la +\func{exec} verrebbe effettuata subito, con la certezza di utilizzare il \textit{copy on write} solo quando necessario. Con il kernel 2.6.32 però il comportamento è stato nuovamente cambiato, stavolta facendo eseguire per primo sempre il padre. Si è realizzato infatti -che l'eventualità prospettata per la scelta precedente era comunque molto -improbabile, mentre l'esecuzione immediata del padre presenta sempre il +che l'eventualità prospettata per la scelta precedente era comunque poco +probabile, mentre l'esecuzione immediata del padre presenta sempre il vantaggio di poter utilizzare immediatamente tutti i dati che sono nella cache -della CPU e nella unità di gestione della memoria virtuale senza doverli +della CPU e nell'unità di gestione della memoria virtuale, senza doverli invalidare, cosa che per i processori moderni, che hanno linee di cache interne molto profonde, avrebbe un forte impatto sulle prestazioni. Allora anche se quanto detto in precedenza vale come comportamento effettivo dei programmi soltanto per i kernel fino alla serie 2.4, per mantenere la portabilità con altri kernel unix-like, e con i diversi comportamenti adottati -dalle Linux nelle versioni successive, è opportuno non fare affidamento su -nessun tipo comportamento predefinito e non dare per assunta l'esecuzione -preventiva del padre o del figlio. - -Si noti poi come dopo la \func{fork}, essendo i segmenti di memoria utilizzati -dai singoli processi completamente indipendenti, le modifiche delle variabili -nei processi figli, come l'incremento di \var{i} in (\texttt{\small 31}), sono -visibili solo a loro, (ogni processo vede solo la propria copia della -memoria), e non hanno alcun effetto sul valore che le stesse variabili hanno -nel processo padre ed in eventuali altri processi figli che eseguano lo stesso -codice. +dalle Linux nella sua evoluzione, è comunque opportuno non fare affidamento su +nessun tipo di comportamento predefinito e non fare assunzioni sull'ordine di +esecuzione di padre e figlio. + +Si noti infine come dopo la \func{fork}, essendo i segmenti di memoria +utilizzati dai singoli processi completamente indipendenti, le modifiche delle +variabili nei processi figli, come l'incremento di \var{i} in (\texttt{\small + 31}), sono visibili solo a loro, (ogni processo vede solo la propria copia +della memoria), e non hanno alcun effetto sul valore che le stesse variabili +hanno nel processo padre ed in eventuali altri processi figli che eseguano lo +stesso codice. Un secondo aspetto molto importante nella creazione dei processi figli è quello dell'interazione dei vari processi con i file. Ne parleremo qui anche @@ -528,15 +529,15 @@ che come si vede è completamente diverso da quanto ottenevamo sul terminale. Il comportamento delle varie funzioni di interfaccia con i file è analizzato in gran dettaglio in sez.~\ref{sec:file_unix_interface} per l'interfaccia nativa Unix ed in sez.~\ref{sec:files_std_interface} per la standardizzazione -adottata nelle librerie del linguaggio C e valida per qualunque sistema -operativo. +adottata nelle librerie del linguaggio C, valida per qualunque sistema +operativo. Qui basta accennare che si sono usate le funzioni standard della libreria del C che prevedono l'output bufferizzato. Il punto è che questa bufferizzazione (che tratteremo in dettaglio in sez.~\ref{sec:file_buffering}) varia a seconda che si tratti di un file su disco, in cui il buffer viene scaricato su disco solo quando necessario, o di un terminale, in cui il buffer viene scaricato ad -ogni carattere di a capo. +ogni carattere di ``a capo''. Nel primo esempio allora avevamo che, essendovi un a capo nella stringa stampata, ad ogni chiamata a \func{printf} il buffer veniva scaricato, per cui @@ -566,19 +567,19 @@ viene rediretto come si è fatto nell'esempio, lo stesso avviene anche per tutti i figli. La funzione \func{fork} infatti ha la caratteristica di duplicare nei processi figli tutti i \textit{file descriptor} (vedi sez.~\ref{sec:file_fd}) dei file aperti nel processo padre (allo stesso modo -in cui lo fa la funzione \func{dup}, trattata in sez.~\ref{sec:file_dup}), il -che comporta che padre e figli condividono le stesse voci della \textit{file - table} (tratteremo in dettaglio questi termini in sez.~\ref{sec:file_fd} e -sez.~\ref{sec:file_shared_access}) fra cui c'è anche la posizione corrente nel -file. - -In questo modo se un processo scrive su un file aggiornerà la posizione -corrente sulla \textit{file table}, e tutti gli altri processi, che vedono la -stessa \textit{file table}, vedranno il nuovo valore. In questo modo si evita, -in casi come quello appena mostrato in cui diversi processi scrivono sullo -stesso file, che l'output successivo di un processo vada a sovrapporsi a -quello dei precedenti: l'output potrà risultare mescolato, ma non ci saranno -parti perdute per via di una sovrascrittura. +in cui lo fa la funzione \func{dup}, trattata in sez.~\ref{sec:file_dup}). Ciò +fa si che padre e figli condividano le stesse voci della \textit{file table} +(tratteremo in dettaglio questi termini in sez.~\ref{sec:file_fd} e +sez.~\ref{sec:file_shared_access}) fra le quali c'è anche la posizione +corrente nel file. + +Quando un processo scrive su un file la posizione corrente viene aggiornata +sulla \textit{file table}, e tutti gli altri processi, che vedono la stessa +\textit{file table}, vedranno il nuovo valore. In questo modo si evita, in +casi come quello appena mostrato in cui diversi processi scrivono sullo stesso +file, che l'output di un processo successivo vada a sovrapporsi a quello dei +precedenti: l'output potrà risultare mescolato, ma non ci saranno parti +perdute per via di una sovrascrittura. Questo tipo di comportamento è essenziale in tutti quei casi in cui il padre crea un figlio e attende la sua conclusione per proseguire, ed entrambi @@ -4503,7 +4504,7 @@ varie funzioni di libreria, che sono identificate aggiungendo il suffisso % LocalWords: nell'header scheduler system interrupt timer HZ asm Hertz clock % LocalWords: l'alpha tick fork wait waitpid exit exec image glibc int pgid ps % LocalWords: sid thread Ingo Molnar ppid getpid getppid sys unistd LD threads -% LocalWords: void tempnam pathname sibling cap errno EAGAIN ENOMEM +% LocalWords: void tempnam pathname sibling cap errno EAGAIN ENOMEM context % LocalWords: stack read only copy write tab client spawn forktest sleep PATH % LocalWords: source LIBRARY scheduling race condition printf descriptor dup % LocalWords: close group session tms lock vfork execve BSD stream main abort @@ -4513,7 +4514,7 @@ varie funzioni di libreria, che sono identificate aggiungendo il suffisso % LocalWords: filesystem noexec EPERM suid sgid root nosuid ENOEXEC ENOENT ELF % LocalWords: ETXTBSY EINVAL ELIBBAD BIG EFAULT EIO ENAMETOOLONG ELOOP ENOTDIR % LocalWords: ENFILE EMFILE argc execl path execv execle execlp execvp vector -% LocalWords: list environ NULL umask utime cutime ustime fcntl linker +% LocalWords: list environ NULL umask utime cutime ustime fcntl linker Posix % LocalWords: opendir libc interpreter FreeBSD capabilities mandatory access % LocalWords: control MAC SELinux security modules LSM superuser uid gid saved % LocalWords: effective euid egid dell' fsuid fsgid getuid geteuid getgid SVr @@ -4527,7 +4528,7 @@ varie funzioni di libreria, che sono identificate aggiungendo il suffisso % LocalWords: shmctl ioperm iopl chroot ptrace accounting swap reboot hangup % LocalWords: vhangup mknod lease permitted inherited inheritable bounding AND % LocalWords: capability capget capset header ESRCH undef version obj clear PT -% LocalWords: pag ssize length proc capgetp preemptive cache runnable contest +% LocalWords: pag ssize length proc capgetp preemptive cache runnable % LocalWords: SIGSTOP soft slice nice niceness counter which SC switch side % LocalWords: getpriority who setpriority RTLinux RTAI Adeos fault FIFO COUNT % LocalWords: yield Robin setscheduler policy param OTHER priority setparam to @@ -4559,6 +4560,7 @@ varie funzioni di libreria, che sono identificate aggiungendo il suffisso % LocalWords: NEWUTS SETTLS SIGHAND SYSVSEM UNTRACED tls ctid CLEARTID panic % LocalWords: loader EISDIR SIGTRAP uninterrutible killable EQUAL sizeof XOR % LocalWords: destset srcset ALLOC num cpus setsize emacs pager getty TID +% LocalWords: reaper SUBREAPER Library futex %%% Local Variables: %%% mode: latex