X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=process.tex;h=52c1f40f95228ebe236d617825bab3b6c86f1222;hp=84cd0fef33faac87260763e2aa09032b4967e4ca;hb=66765a9be9a61085dd00abd92d99a24b23dc844b;hpb=8f0266fc964bcdf2aa8d7791a24f05a0eda77556 diff --git a/process.tex b/process.tex index 84cd0fe..52c1f40 100644 --- a/process.tex +++ b/process.tex @@ -29,7 +29,7 @@ Anche quando all'interno di un programma possono essere presenti pi \textsl{filoni} di esecuzione (i cosiddetti \textit{thread}), o questo possa essere composto da moduli multipli completamente separati, quando questo sarà posto in esecuzione esso apparirà al sistema come un solo processo (il -discorso dei \textit{thread} comunque in linux necessita di una trattazione a +discorso dei \textit{thread} comunque in Linux necessita di una trattazione a parte per la peculiarità dell'implementazione). \section{La funzione \texttt{main}} @@ -40,7 +40,7 @@ avvio, usando il programma \texttt{ld-linux.so}, carica le librerie condivise che servono al programma, effettua il link dinamico del codice e poi alla fine lo esegue. Infatti, a meno di non aver specificato il flag \texttt{-static} durante la compilazione, tutti i -programmi in linux sono incompleti e necessitano di essere linkati alle +programmi in Linux sono incompleti e necessitano di essere linkati alle librerie condivise quando vengono avviati. La procedura è controllata da alcune variabili di ambiente e dal contenuto di \texttt{/etc/ld.so.conf}, i dettagli sono riportati nella man page di \texttt{ld.so}. @@ -50,7 +50,7 @@ sta al programmatore chiamare cos si suppone iniziale l'esecuzione; in ogni caso senza questa funzione lo stesso linker darebbe luogo ad errori. -Lo stadard ISO C specifica che la funzione \texttt{main} può o non avere +Lo standard ISO C specifica che la funzione \texttt{main} può o non avere 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{verbatim} @@ -96,7 +96,7 @@ la fine della funzione \texttt{main} senza ritornare esplicitamente si ha un valore di uscita indefinito, è pertanto consigliabile di concludere sempre in maniera esplicita detta funzione. -Una altra convenzione riserva i valori da 128 in sù per usi speciali, ad +Una altra convenzione riserva i valori da 128 in su per usi speciali, ad esempio 128 viene usato per indicare l'incapacità di eseguire un altro programma in un sottoprocesso. Benché anche questa convenzione non sia universalmente seguita è una buona idea tenerne conto. @@ -107,7 +107,7 @@ una shell non si cura di tutto questo e comunque il valore dello stato di uscita è sempre troncato ad 8 bit, per cui si potrebbe incorrere nel caso in cui l'errore 256, diventando zero, verrebbe interpretato come un successo. In \texttt{stdlib.h} sono definite due macro \texttt{EXIT\_SUCCESS} e -\texttt{EXIT\_FAILURE}, che in linux sono poste rispettivamente ai valori 0 e +\texttt{EXIT\_FAILURE}, che in Linux sono poste rispettivamente ai valori 0 e 1 (di tipo \texttt{int}), seguendo lo standard POSIX. Infine occorre distinguere fra lo stato di uscita di un programma @@ -224,7 +224,7 @@ attraveso una delle funzioni \texttt{exec} che vedremo in Allo stesso modo l'unico modo in cui un programma può concludere volontariamente la sua esecuzione è attraverso una chiamata alla system call -\texttt{\_exec} sia esplicitamnte o che in maniera indiretta attraverso l'uso +\texttt{\_exec} sia esplicitamente o che in maniera indiretta attraverso l'uso di \texttt{exit} o il ritorno della funzione \texttt{main}. Lo schema delle modalità con cui si avvia e conclude normalmente un programma @@ -248,7 +248,7 @@ attraverso l'uso di un segnale (modalit Una delle risorse base che ciascun processo ha a disposizione è la memoria, ed uno degli aspetti più complessi di un sistema unix (ed in particolar modo di -linux) è appunto la gestione della memoria. Qui ci occuperemo però di come la +Linux) è appunto la gestione della memoria. Qui ci occuperemo però di come la memoria viene vista dal punto di vista di un programma in esecuzione in un processo. @@ -258,14 +258,14 @@ processo. Ci sono vari modi in cui i vari sistemi organizzano la memoria (ed i dettagli di basso livello dipendono in maniera diretta dall'architettura -dell'hardware), ma quello più tipico, usato da unix (e da linux) è quello di +dell'hardware), ma quello più tipico, usato da unix (e da Linux) è quello di assegnare ad ogni processo uno spazio virtuale di indirizzamento lineare in cui gli indirizzi vanno da zero ad un qualche valore massimo (nel caso di -linux fino al kernel 2.2 detto massimo era per macchine a 32bit di 2Gb, con il +Linux fino al kernel 2.2 detto massimo era per macchine a 32bit di 2Gb, con il kernel 2.4 il limite è stato esteso). Come accennato nell'introduzione questo spazio di indirizzi è virtuale e non -corrisponde all'effettiva posizione dei dati nella ram del computer; in genere +corrisponde all'effettiva posizione dei dati nella RAM del computer; in genere detto spazio non è neanche continuo (cioè non tutti gli indirizzi sono utilizzabili e/o utilizzati). @@ -273,13 +273,13 @@ La memoria virtuale viene divisa in pagine (che ad esempio sono di 4kb su macchine a 32 bit e 8kb sulle alpha, valori strettamente connessi all'hardware di gestione della memoria) di dimensione fissa, e ciascuna pagina della memoria virtuale è associata ad un supporto che può essere una pagina di -memoria reale o ad un dipositivo di stoccaggio secondario (in genere lo spazio +memoria reale o ad un dispositivo di stoccaggio secondario (in genere lo spazio disco riservato alla swap, o i file che contengono il codice). Lo stesso pezzo di memoria reale (o di spazio disco) può fare da supporto a diverse pagine di memoria virtuale appartenenti a processi diversi (come accade in genere per le pagine che contengono il codice delle librerie -condivise). Ad esempio il codice della fuzione \texttt{printf} starà su una +condivise). Ad esempio il codice della funzione \texttt{printf} starà su una sola pagina di memoria reale che farà da supporto a tutte le pagine di memoria virtuale di tutti i processi hanno detta funzione nel loro codice. @@ -296,7 +296,7 @@ Quando un processo cerca di accedere ad una pagina che non reale avviene quello che viene chiamato un \textit{page fault}, l'hardware di gestione della memoria (la MMU del processore) genera una 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 +in RAM la pagina richiesta (effettuando tutte le operazioni necessarie per reperire lo spazio necessario), per poi restituire il controllo al processo. @@ -304,7 +304,7 @@ Dal punto di vista di un processo questo meccanismo trasparente e tutto avviene come se tutte le pagine fossero sempre disponibili in memoria. L'unica differenza avvertibile è quella dei tempi di esecuzione, che passano dai pochi nanosecondi necessari per l'accesso a tempi molto più -lunghi, dovuti all'intervento del kernel. Normalemente questo è il prezzo da +lunghi, dovuti all'intervento del kernel. 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 @@ -321,32 +321,32 @@ 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}, si tenta cioè di leggere e scrivere da un indirizzo per il quale non esiste una associazione della pagina virtuale -ed il kernel riponde al relativo \textit{page fault} mandando un segnale +ed il kernel risponde al relativo \textit{page fault} mandando un segnale \texttt{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. Storicamente un +indirizzi virtuali ai quali il processo può accedere. Solitamente un programma C viene suddiviso nei seguenti segmenti: \begin{enumerate} \item Il segmento di testo (\textit{text segment}). Contiene il codice - macchina del programma e le costanti statiche. Normalente viene condiviso + macchina del programma e le costanti statiche. Normalmente viene condiviso così che più processi (anche diversi nel caso di librerie) possano - utilizzarlo e marcato in sola lettura per evitare sovrascritture accidentali - (o maliziose) che ne modifichino le istruzioni. + utilizzarlo e viene marcato in sola lettura per evitare sovrascritture + accidentali (o maliziose) che ne modifichino le istruzioni. Viene allocato da \texttt{exec} all'avvio del programma e resta invariato per tutto il tempo dell'esecuzione. -\item Il segmento dei dati (\textit{data segment}). Contiene le varibili +\item Il segmento dei dati (\textit{data segment}). Contiene le variabili globali (cioè quelle definite al di fuori di tutte le funzioni). Di norma è diviso in due parti. - La prima è il segmento dei dati inizializzati, che contiene le variabili - globali il cui valore è stato assegnato esplicitamente. Ad esempio se si - definisce: + La prima parte è il segmento dei dati inizializzati, che contiene le + variabili globali il cui valore è stato assegnato esplicitamente. Ad esempio + se si definisce: \begin{verbatim} double pi = 3.14; \end{verbatim} @@ -354,14 +354,14 @@ programma C viene suddiviso nei seguenti segmenti: segmento viene preallocato dalla \texttt{exec} e inizializzata ai valori specificati. - La seconda è il segmento dei dati non inizializzati, che contiene le + La seconda parte è il segmento dei dati non inizializzati, che contiene le variabili globali il cui valore è stato non è assegnato esplicitamente. Ad esempio se si definisce: \begin{verbatim} int vect[100]; \end{verbatim} questo valore sarà immagazzinato in questo segmento. Anch'esso viene - allocato all'avvio, e tutte le varibili vengono inizializzate a + allocato all'avvio, e tutte le variabili vengono inizializzate a zero (ed i puntatori a \texttt{NULL}). Storicamente questo segmento viene chiamato BBS (da \textit{block started by @@ -400,17 +400,78 @@ salvato sul file, in quanto viene inizializzato a zero al caricamento del programma. -\subsection{La librerie condivise} -\label{sec:proc_mem_shlib} +\subsection{Allocazione della memoria per i programmi C} +\label{sec:proc_mem_alloc} + +Il C supporta due tipi di allocazione della memoria, l'allocazione statica è +quella in cui vanno le variabili globali e le variabili statiche (e viene +effettuata nel segmento dei dati), lo spazio per queste variabili viene +allocati all'avvio del programma (come parte delle operazioni svolte da +\texttt{exec}) e non viene liberato fino alla sua conclusione. + +L'allocazione automatica è quella che avviene per le cosiddette variabili +automatiche, cioè gli argomenti delle funzioni o le variabili locali. Lo +spazio per queste variabili viene allocato nello stack quando viene eseguito +comando di invocazione della funzione e liberato quando si esce dalla +medesima. + +Esiste però un terzo tipo di allocazione, che non è prevista dal linguaggio C, +che è l'allocazione dinamica della memoria, necessaria quando il quantitativo +di memoria che serve è determinabile solo in corso di esecuzione del +programma. +Il C non consente di usare variabili allocate dinamicamente, non è possibile +cioè definire in fase di programmazione una variabile le cui dimensioni +possano essere modificate durante l'esecuzione del programma; però le librerie +del C forniscono una serie opportuna di funzioni per permettere l'allocazione +dinamica di spazio in memoria (in genere nello heap, usando la system call +\texttt{sbrk}), solo che a questo punto sarà possibile usarlo solo in maniera +indiretta attraverso dei puntatori. + +Le funzioni previste dallo standard ANSI C per la gestione della memoria sono +quattro, i prototipi sono i seguenti: +\begin{prototype}{stdlib.h}{void *calloc(size\_t size)} + Alloca \texttt{size} bytes nello heap. La memoria viene inizializzata a 0. + + La funzione restituisce il puntatore alla zona di memoria allocata in caso + di successo e \texttt{NULL} in caso di fallimento, nel qual caso + \texttt{errno} viene settata a \texttt{ENOMEM}. +\end{prototype} +\begin{prototype}{stdlib.h}{void *malloc(size\_t size)} + Alloca \texttt{size} bytes nello heap. La memoria non viene inizializzata. + La funzione restituisce il puntatore alla zona di memoria allocata in caso + di successo e \texttt{NULL} in caso di fallimento, nel qual caso + \texttt{errno} viene settata a \texttt{ENOMEM}. +\end{prototype} +\begin{prototype}{stdlib.h}{void free(void *ptr)} + Disalloca lo spazio di memoria puntato da \texttt{ptr}. + + La funzione non ritorna nulla. +\end{prototype} +\begin{prototype}{stdlib.h}{void *realloc(void *ptr, size\_t size)} + Cambia la dimensione del blocco allocato all'indirizzo \texttt{ptr} + portandola a \texttt{size}. + + La funzione restituisce il puntatore alla zona di memoria allocata in caso + di successo e \texttt{NULL} in caso di fallimento, nel qual caso + \texttt{errno} viene settata a \texttt{ENOMEM}. +\end{prototype} + +Il puntatore che le funzioni di allocazione ritornano è garantito essere +sempre correttamente allineato per tutti i tipi di dati; ad esempio sulle +macchine a 32 bit in genere è allineato a multipli di 4 bytes e sulle macchine +a 64 bit a multipli di 8 bytes. Il puntatori ritornati sono di tipo generico +così non è necessario effettuare un cast per assegnarli a puntatori di altro +tipo. -\subsection{Allocazione e disallocazione della memoria} -\label{sec:proc_mem_alloc} + + + \section{La gestione di parametri e opzioni} @@ -430,7 +491,7 @@ ciascuna delle quali viene considerata un parametro; di default per individuare le parole viene usato come separatore lo spazio (comportamento modificabile attraverso il settaggio della variabile di ambiente IFS). -Nella scansione viene costruito l'array di puntatori \texttt{argv} inserendo +Nella scansione viene costruito il vettore di puntatori \texttt{argv} inserendo in successione il puntatore alla stringa costituente l'$n$-simo parametro; la variabile \texttt{argc} viene inizializzata al numero di parametri trovati, in questo modo il primo parametro è sempre il nome del programma (vedi \nfig). @@ -442,7 +503,7 @@ In generale un programma unix riceve da linea di comando sia i parametri che le opzioni, queste ultime sono standardizzate per essere riconosciute come tali: un elemento di \texttt{argv} che inizia con \texttt{-} e che non sia un singolo \texttt{-} o \texttt{--} viene considerato un'opzione. In in genere -le opzioni sono costituite da un lettera preceduta dal meno e possono avere o +le opzioni sono costituite da una lettera preceduta dal meno e possono avere o no un parametro associato; un comando tipico può essere cioè qualcosa del tipo: \begin{verbatim} @@ -479,7 +540,7 @@ considerata conclusa. Quando la funzione trova un'opzione essa ritorna il valore numerico del carattere, in questo modo si possono prendere le azioni relative usando un -case; la funzione inizializza inoltre alcune varibili globali: +case; la funzione inizializza inoltre alcune variabili globali: \begin{itemize} \item \texttt{char * optarg} contiene il puntatore alla stringa argomento dell'opzione. @@ -490,8 +551,7 @@ case; la funzione inizializza inoltre alcune varibili globali: \item \texttt{int optopt} contiene il carattere dell'opzione non riconosciuta. \end{itemize} -In \nfig è mostrato un programma di esempio, - +In \nfig\ è mostrato un programma di esempio, \begin{figure}[htbp] \footnotesize @@ -547,6 +607,7 @@ la gestione di queste ultime versione estesa di \texttt{getopt}. + \subsection{Le variabili di ambiente} \label{sec:proc_env_var}