From a93a0bac951085a52c4b33c42a607fc81f6868d2 Mon Sep 17 00:00:00 2001 From: Simone Piccardi Date: Tue, 22 May 2001 17:42:24 +0000 Subject: [PATCH] Completato il capitolo sui socket elementari, con tanto di spiegazioni delle funzioni base e di esempio di server concorrente. Eliminato option.tex che e` stato inserito in process.tex, che diventa il capitolo introduttivo ai processi. Creato poi il nuovo prochand.tex per le spiegazioni su fork &C --- elemtcp.tex | 348 +++++++++++++++++++++++++++++++++++++-------------- fileunix.tex | 3 + main.tex | 2 +- option.tex | 150 ---------------------- process.tex | 279 +++++++++++++++++++++-------------------- prochand.tex | 155 +++++++++++++++++++++++ socket.tex | 8 +- 7 files changed, 564 insertions(+), 381 deletions(-) delete mode 100644 option.tex create mode 100644 prochand.tex diff --git a/elemtcp.tex b/elemtcp.tex index fd2d34a..efc956f 100644 --- a/elemtcp.tex +++ b/elemtcp.tex @@ -225,9 +225,9 @@ in \figref{fig:net_cli_code}). Questo vuol dire ad esempio che se un processo viene terminato da un segnale tutte le connessioni aperte verranno chiuse. Infine è da sottolineare che, benché nella figura (e nell'esempio che vedremo -in \secref{sec:TCPel_echo_example}) sia il client ad eseguire la chiusura -attiva, nella realtà questa può essere eseguita da uno qualunque dei due capi -della comunicazione (come in fatto in precedenza da +piu avanti in \secref{sec:TCPsimp_echo_example}) sia il client ad eseguire la +chiusura attiva, nella realtà questa può essere eseguita da uno qualunque dei +due capi della comunicazione (come in fatto in precedenza da \figref{fig:net_serv_code}), e benché quello del client sia il caso più comune ci sono alcuni servizi, il principale dei quali è l'HTTP, per i quali è il server ad effettuare la chiusura attiva. @@ -244,10 +244,11 @@ che vengono riportati del comando \texttt{netstat} nel campo \textit{State}. Una descrizione completa del funzionamento del protocollo va al di là degli obiettivi di questo libro; un approfondimento sugli aspetti principali si -trova in \capref{cha:tcp_protocol}, ma per una trattazione esauriente il miglior -riferimento resta (FIXME citare lo Stevens); qui ci limiteremo a descrivere -brevemente un semplice esempio di connessione e le transizioni che avvengono -nei due casi appena citati (creazione e terminazione della connessione). +trova in \capref{cha:tcp_protocol}, ma per una trattazione esauriente il +miglior riferimento resta (FIXME citare lo Stevens); qui ci limiteremo a +descrivere brevemente un semplice esempio di connessione e le transizioni che +avvengono nei due casi appena citati (creazione e terminazione della +connessione). In assenza di connessione lo stato del TCP è \texttt{CLOSED}; quando una applicazione esegue una apertura attiva il TCP emette un SYN e lo stato @@ -291,8 +292,8 @@ caso contrario si avrebbe prima l'emissione di un ACK e poi l'invio della risposta. Infine si ha lo scambio dei quattro segmenti che terminano la connessione -secondo quanto visto in \secref{sec:TCPel_conn_term}; si noti che il capo della -connessione che esegue la chiusura attiva entra nello stato +secondo quanto visto in \secref{sec:TCPel_conn_term}; si noti che il capo +della connessione che esegue la chiusura attiva entra nello stato \texttt{TIME\_WAIT} su cui torneremo fra poco. È da notare come per effettuare uno scambio di due pacchetti (uno di richiesta @@ -358,7 +359,7 @@ durata di questo stato. Il primo dei due motivi precedenti si può capire tornando a \curfig: assumendo che l'ultimo ACK della sequenza (quello del capo che ha eseguito la chiusura -attiva) vanga perso, chi esegue la chiusura passiva non ricevndo risposta +attiva) vanga perso, chi esegue la chiusura passiva non ricevendo risposta rimanderà un ulteriore FIN, per questo motivo chi esegue la chiusura attiva deve mantenere lo stato della connessione per essere in grado di reinviare l'ACK e chiuderla correttamente. Se non fosse così la risposta sarebbe un RST @@ -379,7 +380,7 @@ restare intrappolati, per poi riemergere. Il caso più comune in cui questo avviene è quello di anomalie nell'instradamento; può accadere cioè che un router smetta di funzionare o che una connessione fra due router si interrompa. In questo caso i protocolli di -instradamento dei pacchetti possono impiegare diverso temo (anche dell'ordine +instradamento dei pacchetti possono impiegare diverso tempo (anche dell'ordine dei minuti) prima di trovare e stabilire un percorso alternativo per i pacchetti. Nel frattempo possono accadere casi in cui un router manda i pacchetti verso un'altro e quest'ultimo li rispedisce indietro, o li manda ad @@ -424,8 +425,8 @@ In un ambiente multitasking in un dato momento pi usare sia UDP che TCP, e ci devono poter essere più connessioni in contemporanea. Per poter tenere distinte le diverse connessioni entrambi i protocolli usano i \textsl{numeri di porta}, che fanno parte, come si può -vedere in \secref{sec:sock_sa_ipv4} e \secref{sec:sock_sa_ipv6} pure delle strutture -degli indirizzi del socket. +vedere in \secref{sec:sock_sa_ipv4} e \secref{sec:sock_sa_ipv6} pure delle +strutture degli indirizzi del socket. Quando un client contatta un server deve poter identificare con quale dei vari possibili server attivi intende parlare. Sia TCP che UDP definiscono un gruppo @@ -486,7 +487,8 @@ I sistemi unix hanno inoltre il concetto di \textsl{porte riservate} (che corrispondono alle porte con numero minore di 1024 e coincidono quindi con le porte conosciute). La loro caratteristica è che possono essere assegnate a un socket solo da un processo con i privilegi di root, per far si che solo -l'amministratore possa allocare queste porte per far partire relativi servizi. +l'amministratore possa allocare queste porte per far partire i relativi +servizi. Si tenga conto poi che ci sono alcuni client (in particolare \texttt{rsh} e \texttt{rlogin}) che richiedono una connessione su una porta riservata anche @@ -510,7 +512,7 @@ campi \textit{Local Address} e \textit{Foreing Address}. Per capire meglio l'uso delle porte e come vengono utilizzate quando si ha a che fare con un'applicazione client/server (come quella che scriveremo in -\secref{sec:TCPel_echo_example}) esaminaremo cosa accade con le connessioni nel +\secref{sec:TCPel_cunc_serv}) esaminaremo cosa accade con le connessioni nel caso di un server TCP che deve gestire connessioni multiple. Se esguiamo un \texttt{netstat} su una macchina di prova (che supponiamo avere @@ -535,13 +537,13 @@ valore generico, e corrisponde al valore \texttt{INADDR\_ANY} definito in Inoltre la porta e l'indirizzo di ogni eventuale connessione esterna non sono specificati; in questo caso la \textit{socket pair} associata al socket può -essere indicata come $(*:22, *.*)$, usando l'asterisco anche per gli indirizzi +essere indicata come $(*:22, *:*)$, usando l'asterisco anche per gli indirizzi come carattere di \textit{wildchard}. In genere avendo le macchine associato un solo IP ci si può chiedere che senso abbia l'utilizzo dell'indirizzo generico per l'indirizzo locale, ma esistono anche macchine che hanno più di un indirizzo IP (il cosiddetto -\textit{miltihoming}) in questo modo si possono accettare connessioni +\textit{multihoming}) in questo modo si possono accettare connessioni indirizzate verso uno qualunque di essi. Ma come si può vedere nell'esempio con il DNS in ascolto sulla porta 53 è anche possibile restringere l'accesso solo alle connessioni che provengono da uno specifico indirizzo, cosa che nel @@ -622,8 +624,8 @@ risponder \label{fig:TCPel_cliserv_func} \end{figure} -Useremo questo schema per l'esempio di implementazione del servizio -\texttt{echo} che illustreremo in \secref{sec:TCPel_echo_example}. +Useremo questo schema anche per l'esempio di reimplementazione del servizio +\texttt{daytime} che illustreremo in \secref{sec:TCPel_cunc_serv}. \subsection{La funzione \texttt{bind}} @@ -649,10 +651,10 @@ Il prototipo della funzione di errore. La variabile \texttt{errno} viene settata secondo i seguenti codici di errore: \begin{errlist} - \item \texttt{EBADF} Il file descriptor non è valido. - \item \texttt{EINVAL} Il socket ha già un indirizzo assegnato. - \item \texttt{ENOTSOCK} Il file descriptor non è associato ad un socket. - \item \texttt{EACCESS} Si è cercato di usare un indirizzo riservato senza + \item \texttt{EBADF} il file descriptor non è valido. + \item \texttt{EINVAL} il socket ha già un indirizzo assegnato. + \item \texttt{ENOTSOCK} il file descriptor non è associato ad un socket. + \item \texttt{EACCESS} si è cercato di usare un indirizzo riservato senza essere root. \end{errlist} \end{prototype} @@ -728,24 +730,24 @@ connessione con un server TCP, il prototipo della funzione di errore. La variabile \texttt{errno} viene settata secondo i seguenti codici di errore: \begin{errlist} - \item \texttt{EBADF} Il file descriptor non è valido. - \item \texttt{EFAULT} L'indirizzo della struttura di indirizzi è al di fuori + \item \texttt{EBADF} il file descriptor non è valido. + \item \texttt{EFAULT} l'indirizzo della struttura di indirizzi è al di fuori dello spazio di indirizzi dell'utente. - \item \texttt{ENOTSOCK} Il file descriptor non è associato ad un socket. - \item \texttt{EISCONN} Il socket è già connesso. - \item \texttt{ECONNREFUSED} Non c'è nessuno in ascolto sull'indirizzo remoto. - \item \texttt{ETIMEDOUT} Si è avuto timeout durante il tentativo di + \item \texttt{ENOTSOCK} il file descriptor non è associato ad un socket. + \item \texttt{EISCONN} il socket è già connesso. + \item \texttt{ECONNREFUSED} non c'è nessuno in ascolto sull'indirizzo remoto. + \item \texttt{ETIMEDOUT} si è avuto timeout durante il tentativo di connessione. - \item \texttt{ENETUNREACH} La rete non è raggiungibile. - \item \texttt{EADDRINUSE} L'indirizzo locale è in uso. - \item \texttt{EINPROGRESS} Il socket è non bloccante e la connessione non + \item \texttt{ENETUNREACH} la rete non è raggiungibile. + \item \texttt{EADDRINUSE} l'indirizzo locale è in uso. + \item \texttt{EINPROGRESS} il socket è non bloccante e la connessione non può essere conclusa immediatamente. - \item \texttt{EALREADY} Il socket è non bloccante e un tentativo precedente + \item \texttt{EALREADY} il socket è non bloccante e un tentativo precedente di connessione non si è ancora concluso. - \item \texttt{EAGAIN} Non ci sono più porte locali libere. - \item \texttt{EAFNOSUPPORT} L'indirizzo non ha una famiglia di indirizzi + \item \texttt{EAGAIN} non ci sono più porte locali libere. + \item \texttt{EAFNOSUPPORT} l'indirizzo non ha una famiglia di indirizzi corretta nel relativo campo. - \item \texttt{EACCESS, EPERM} Si è tentato di eseguire una connessione ad un + \item \texttt{EACCESS, EPERM} si è tentato di eseguire una connessione ad un indirizzo broacast senza che il socket fosse stato abilitato per il broadcast. \end{errlist} @@ -833,10 +835,10 @@ man page La funzione restituisce 0 in caso di successo e -1 in caso di errore. I codici di errore restituiti in \texttt{errno} sono i seguenti: \begin{errlist} - \item \texttt{EBADF} L'argomento \texttt{sockfd} non è un file descriptor + \item \texttt{EBADF} l'argomento \texttt{sockfd} non è un file descriptor valido. - \item \texttt{ENOTSOCK} L'argomento \texttt{sockfd} non è un socket. - \item \texttt{EOPNOTSUPP} Il socket è di un tipo che non supporta questa + \item \texttt{ENOTSOCK} l'argomento \texttt{sockfd} non è un socket. + \item \texttt{EOPNOTSUPP} il socket è di un tipo che non supporta questa operazione. \end{errlist} \end{prototype} @@ -853,12 +855,13 @@ con cui il kernel tratta le connessioni in arrivo. Per ogni socket in ascolto infatti vengono mantenute due code: \begin{enumerate} \item Una coda delle connessioni incomplete (\textit{incomplete connection - queue} che contiene una entrata per ciascun SYN arrivato per il quale si - sta attendendo la conclusione del three-way handshake. Questi socket sono - tutti nello stato \texttt{SYN\_RECV}. + queue} che contiene un ingresso per ciascun socket per il quale è arrivato + un SYN ma il three-way handshake non si è ancora concluso. Questi socket + sono tutti nello stato \texttt{SYN\_RECV}. \item Una coda delle connessioni complete (\textit{complete connection queue} - che contiene una entrata per ciascuna connessione per le quali il three-way + che contiene un ingresso per ciascun socket per il quale il three-way handshake è stato completato ma ancora \texttt{accept} non è ritornata. + Questi socket sono tutti nello stato \texttt{ESTABLISHED}. \end{enumerate} Lo schema di funzionamento è descritto in \nfig, quando arriva un SYN da un @@ -878,16 +881,16 @@ dette code. Stevens riporta che BSD ha sempre applicato un fattore di 1.5 al valore, e provvede una tabella con i risultati ottenuti con vari kernel, compreso linux 2.0, che mostrano le differenze fra diverse implementazioni. -Ma in linux il significato di questo valore è cambiato a partire dal kernel +In linux il significato di questo valore è cambiato a partire dal kernel 2.2 per prevenire l'attacco chiamato \texttt{syn flood}. Questo si basa sull'emissione da parte dell'attaccante di un grande numero di pacchetti SYN -indirizzati verso una porta forgiati con indirizzo IP fasullo \footnote{con la +indirizzati verso una porta forgiati con indirizzo IP fasullo\footnote{con la tecnica che viene detta \textit{ip spoofing}} così che i SYN$+$ACK vanno -perduti la coda delle connessioni incomplete viene saturata, impedendo di -fatto le connessioni. +perduti e la coda delle connessioni incomplete viene saturata, impedendo di +fatto ulteriori connessioni. Per ovviare a questo il significato del \texttt{backlog} è stato cambiato a -significare la lunghezza della coda delle connessioni complete. La lunghezza +indicare la lunghezza della coda delle connessioni complete. La lunghezza della coda delle connessioni incomplete può essere ancora controllata usando la \texttt{sysctl} o scrivendola direttamente in \texttt{/proc/sys/net/ipv4/tcp\_max\_syn\_backlog}. Quando si attiva la @@ -899,27 +902,31 @@ se La scelta storica per il valore di questo parametro è di 5, e alcuni vecchi kernel non supportavano neanche valori superiori, ma la situazione corrente è -molto cambiata dagli anni '80 e con server web che possono sopportare diversi -milioni di connessioni al giorno un tale valore non è più adeguato. Non esiste +molto cambiata per via della presenza di server web che devono gestire un gran +numero di connessioni per cui un tale valore non è più adeguato. Non esiste comunque una risposta univoca per la scelta del valore, per questo non -conviene specificare questo valore con una costante (il cui cambiamento -richiederebbe la ricompilazione del server) ma usare piuttosto una variabile -di ambiente (vedi \secref{sec:xxx_env_var}). Lo Stevens tratta accuratamente -questo argomento, con esempi presi da casi reali su web server, ed in -particolare evidenzia come non sia più vero che il compito principale della -coda sia quello di gestire il caso in cui il server è occupato fra chiamate -successive alla \texttt{accept} (per cui la coda più occupata sarebbe quella -delle connessioni compeltate), ma piuttosto quello di gestire la presenza di -un gran numero di SYN in attesa di completare il three-way handshake. - -Come accennato nel caso del TCP se un SYN arriva con tutte le code piene, il -pacchetto sarà ignorato. Questo viene fatto perché la condizione delle code -piene è transitoria, e se il client ristrasmette il SYN è probabile che -passato un po' di tempo possa trovare lo spazio per una nuova connessione. Se -invece si rispondesse con un RST la \texttt{connect} del client ritornerebbe -con una condizione di errore, mentre questo è il tipico caso in cui è si può -lasciare la gestione della connessione alla ritrasmissione prevista dal -protocollo TCP. +conviene specificarlo con una costante (il cui cambiamento richiederebbe la +ricompilazione del server) ma usare piuttosto una variabile di ambiente (vedi +\secref{sec:xxx_env_var}). + +Lo Stevens tratta accuratamente questo argomento, con esempi presi da casi +reali su web server, ed in particolare evidenzia come non sia più vero che il +compito principale della coda sia quello di gestire il caso in cui il server è +occupato fra chiamate successive alla \texttt{accept} (per cui la coda più +occupata sarebbe quella delle connessioni completate), ma piuttosto quello di +gestire la presenza di un gran numero di SYN in attesa di concludere il +three-way handshake. + +Infine va messo in evidenza che nel caso di socket TCP quando un SYN arriva +con tutte le code piene, il pacchetto deve essere ignorato. Questo perché la +condizione in cui le code sono piene è ovviamente transitoria, per cui se il +client ristrasmette il SYN è probabile che passato un po' di tempo possa +trovare nella coda lo spazio per una nuova connessione. Se invece si +rispondesse con un RST per indicare l'impossibilità di effettuare la +connessione la chiamata a \texttt{connect} nel client ritornerebbe con una +condizione di errore, costringendo a inserire nell'applicazione la gestione +dei tentativi di riconnessione che invece può essere effettuata in maniera +trasparente dal protocollo TCP. \subsection{La funzione \texttt{accept}} @@ -945,16 +952,15 @@ viene messo in attesa. Il prototipo della funzione viene settata ai seguenti valori: \begin{errlist} - \item \texttt{EBADF} L'argomento \texttt{sockfd} non è un file descriptor + \item \texttt{EBADF} l'argomento \texttt{sockfd} non è un file descriptor valido. - \item \texttt{ENOTSOCK} L'argomento \texttt{sockfd} non è un socket. - \item \texttt{EOPNOTSUPP} Il socket è di un tipo che non supporta questa + \item \texttt{ENOTSOCK} l'argomento \texttt{sockfd} non è un socket. + \item \texttt{EOPNOTSUPP} il socket è di un tipo che non supporta questa operazione. - \item \texttt{EAGAIN} or \item \texttt{EWOULDBLOCK} Il socket è stato + \item \texttt{EAGAIN} o \item \texttt{EWOULDBLOCK} il socket è stato settato come non bloccante, e non ci sono connessioni in attesa di essere accettate. - \item \texttt{EFAULT} The addr parameter is not in a writable part of the - user address space. + \item \texttt{EFAULT} l'argomento \texttt{addr} . \item \texttt{EPERM} Firewall rules forbid connection. \item \texttt{ENOBUFS, ENOMEM} Not enough free memory. This often means @@ -977,7 +983,7 @@ esplicita della connessione, (attualmenente in linux solo DECnet ha questo comportamento), la funzione opera solo l'estrazione dalla coda delle connessioni, la conferma della connessione viene fatta implicitamente dalla prima chiamata ad una \texttt{read} o una \texttt{write} mentre il rifiuto -della connessione viene fatta con la funzione \texttt{close}. +della connessione viene fatto con la funzione \texttt{close}. E da chiarire che linux presenta un comportamento diverso nella gestione degli errori rispetto ad altre implementazioni dei socket BSD, infatti la funzione @@ -987,11 +993,11 @@ socket flag come \texttt{O\_NONBLOCK}, che devono essere rispecificati volta volta, questo è un comportamento diverso rispetto a quanto accade con BSD e deve essere tenuto in conto per scrivere programmi portabili. -I due parametri \texttt{cliaddr} e \texttt{addrlen} (si noti che quest'ultimo +I due argomenti \texttt{cliaddr} e \texttt{addrlen} (si noti che quest'ultimo è passato per indirizzo per avere indietro il valore) sono usati per ottenere l'indirizzo del client da cui proviene la connessione. Prima della chiamata \texttt{addrlen} deve essere inizializzato alle dimensioni della struttura il -cui indirizzo è passato come parametro in \texttt{cliaddr}, al ritorno della +cui indirizzo è passato come argomento in \texttt{cliaddr}, al ritorno della funzione \texttt{addrlen} conterrà il numero di bytes scritti dentro \texttt{cliaddr}. Se questa informazione non interessa basterà inizializzare a \texttt{NULL} detti puntatori. @@ -1003,24 +1009,53 @@ prima connessione completa (estratta dalla relativa coda, vedi socket \texttt{sockfd}. Quest'ultimo (detto \textit{listening socket}) è quello creato all'inizio e messo in ascolto con \texttt{listen}, e non viene toccato dalla funzione. - Se non ci sono connessioni pendenti da accettare la funzione mette in attesa il processo\footnote{a meno che non si sia settato il socket per essere non-bloccante, nel qual caso ritorna con l'errore \texttt{EAGAIN}, torneremo su questa modalità di operazione in \secref{sec:xxx_sock_noblock}} fintanto che non ne arriva una. -Questo meccanismo è essenziale per capire il funzionamento di un server, in -generale infatti c'è sempre un solo socket in ascolto, che resta per tutto il -tempo nello stato \texttt{LISTEN}, mentre le connessioni vengono gestite dai -nuovi socket ritornati da \texttt{accept} che sono posti automaticamente nello -stato \texttt{ESTABLISHED} e utilizzati fino alla chiusura della connessione -che avviene su di essi. Si può riconoscere questo schema anche nell'esempio -elementare in \figref{fig:net_serv_code} dove per ogni connessione il socket -creato da \texttt{accept} viene chiuso dopo l'invio dei dati. - - -\section{Un server concorrente su TCP} +Il meccanismo di funzionamento di \texttt{accept} è essenziale per capire il +funzionamento di un server: in generale infatti c'è sempre un solo socket in +ascolto, che resta per tutto il tempo nello stato \texttt{LISTEN}, mentre le +connessioni vengono gestite dai nuovi socket ritornati da \texttt{accept} che +si trovano automaticamente nello stato \texttt{ESTABLISHED} e utilizzati fino +alla chiusura della connessione che avviene su di essi. Si può riconoscere +questo schema anche nell'esempio elementare in \figref{fig:net_serv_code} dove +per ogni connessione il socket creato da \texttt{accept} viene chiuso dopo +l'invio dei dati. + + +\subsection{La funzione \texttt{close}} +\label{sec:TCPel_func_close} + +La funzione standard unix \texttt{close} (vedi \secref{sec:fileunix_close}) +che si usa sui file può essere usata con lo stesso effetto anche sui socket +descriptor. + +L'azione standard di questa funzione quando applicata a socket è di marcarlo +come chiuso e ritornare immediatamente al processo. Una volta chiamata il +socket descriptor non è più utilizzabile dal processo e non può essere usato +come argomento per una \texttt{write} o una \texttt{read} (anche se l'altro +capo della connessione non avesse chiuso la sua parte). Il kernel invierà +comunque tutti i dati che ha in coda prima di inziare la sequenza di chiusura. + +Vedremo più avanti in \secref{sec:TCPadv_so_linger} come è possibile cambiare +questo comportamento, e cosa deve essere fatto perché il processo possa +assicurarsi che l'altro capo abbia ricevuto tutti i dati. + +Come per i file anche per i socket descriptor viene mantenuto un numero di +riferimenti, per cui se più di un processo ha lo stesso socket aperto +l'emissione del FIN e la sequenza di chiusura di TCP non viene innescata +fintanto che il numero di riferimenti non si annulla. Questo è il +comportamento normale che ci si aspetta in un'applicazione client/server quale +quella che illustreremo in \secref{sec:TCPel_cunc_serv}. + +Per attivare immediatamente l'emissione del FIN e la sequenza di chiusura si +può usare la funzione \texttt{shutdown} su cui torneremo in seguito. + + +\section{I server concorrenti su TCP} \label{sec:TCPel_cunc_serv} Il server \texttt{daytime} dell'esempio in \secref{sec:net_cli_sample} è un @@ -1035,11 +1070,17 @@ usare la funzione \texttt{fork} per far creare al server per ogni richiesta da parte di un client un processo figlio che si incarichi della gestione della comunicazione. -Per illustrare questo meccanismo abbiamo allora riscritto il server -\texttt{daytime} in forma concorrente, inserendo anche una opzione per la -stampa degli indirizzi delle connessioni ricevute. -In \nfig\ è mostrato un estratto del codice, in cui si sono tralasciate il + +\subsection{Un esempio di server \textit{daytime}} +\label{sec:TCPel_cunc_daytime} + +Per illustrare il meccanismo usato in generale per creare un server +concorrente abbiamo riscritto il server \texttt{daytime} dell'esempio +precedente in forma concorrente, inserendo anche una opzione per la stampa +degli indirizzi delle connessioni ricevute. + +In \nfig\ è mostrato un estratto del codice, in cui si sono tralasciati il trattamento delle opzioni e le parti rimaste invariate rispetto al precedente esempio. Al solito il sorgente completo del server \texttt{ElemDaytimeTCPCuncServ.c} è allegato nella directory dei sorgenti. @@ -1105,5 +1146,124 @@ int main(int argc, char *argv[]) \label{fig:net_cli_code} \end{figure} -Come si può vedere (\texttt{\small 21--25}) alla funzione \texttt{accept} +Come si può vedere (alle linee \texttt{\small 21--25}) la funzione +\texttt{accept} stavolta è chiamata fornendo una struttura di indirizzi in cui +saranno ritornati numero IP e porta da cui il client effettua la connessione, +che stamperemo, se avremo abilitato il logging, sullo standard output +(\texttt{\small 39--43}). + +Quando \texttt{accept} ritorna il server chiama la funzione \texttt{fork} +(\texttt{\small 26--30}) per creare il processo figlio che effettuerà tutte le +operazioni relative a quella connessione (\texttt{\small 31--45}), mentre il +padre resterà in attesa di ulteriori connessioni. + +Si noti come il figlio operi solo sul socket connesso, chiudendo +immediatamente il socket \texttt{list\_fd}; mentre il padre continua ad +operare solo sul socket in ascolto chiudendo \texttt{sock\_fd} dopo ciascuna +\texttt{accept}. Per quanto abbiamo detto in \secref{sec:TCPel_func_close} +queste due chiusure non causano l'innesco della sequenza di chiusura perchè il +numero di riferimenti non si è annullato. + +Infatti subito dopo la creazione del socket \texttt{list\_fd} ha una +referenza, e lo stesso vale per \texttt{sock\_fd} dopo il ritorno di +\texttt{accept}, ma dopo la fork i descrittori vengono duplicati nel padre e +nel figlio per cui entrambi i socket si trovano con due referenze. Questo fa +si che quando il padre chiude \texttt{sock\_fd} esso resta con una referenza +da parte del figlio, e sarà definitivamente chiuso solo quando quest'ultimo, +dopo aver completato le sue operazioni, chiamerà la funzione \texttt{close}. + +In realtà per il figlio non sarebbero necessarie nessuna delle due chiamate a +\texttt{close} in quanto nella \texttt{exit} tutti i file ed i socket vengono +chiusi, ma si è preferito effettuare la chiusura esplicitamente per avere una +maggiore chiarezza del codice ed evitare possibili errori. + +Si noti come sia essenziale che il padre chiuda ogni volta il socket connesso +dopo la \texttt{accept}; se così non fosse nessuno di questi socket sarebbe +effettivamente chiuso dato che alla chiusura da parte del figlio resterebbe +ancora un riferimento. Si avrebbero così due effetti, il padre potrebbe +esaurire i descrittori disponibili (che sono un numero limitato per ogni +processo) e soprattutto nessuna delle connessioni con i client verrebbe +chiusa. + + +\subsection{Le funzioni \texttt{getsockname} e \texttt{getpeername}} +\label{sec:TCPel_get_names} + +Queste due funzioni vengono usate per ottenere la socket pair associata ad un +certo socket; la prima restituisce l'indirizzo locale, la seconda quello +remoto. + +\begin{prototype}{sys/socket.h} +{int getsockname(int sockfd, struct sockaddr * name, socklen_t * namelen)} + + La funzione restituisce 0 in caso di successo e -1 in caso di errore. I + codici di errore restituiti in \texttt{errno} sono i seguenti: + \begin{errlist} + \item \texttt{EBADF} l'argomento \texttt{sockfd} non è un file descriptor + valido. + \item \texttt{ENOTSOCK} l'argomento \texttt{sockfd} non è un socket. + \item \texttt{ENOBUFS} non ci sono risorse sufficienti nel sistema per + eseguire l'operazione. + \item \texttt{EFAULT} l'argomento \texttt{name} punta al di fuori dello + spazio di indirizzi del processo. + \end{errlist} +\end{prototype} + +La funzione \texttt{getsockname} si usa tutte le volte che si vuole avere +l'indirizzo locale di un socket; ad esempio può essere usata da un client (che +usualmente non chiama \texttt{bind}) per ottenere numero IP e porta locale +associati al socket restituito da una \texttt{connect}, o da un server che ha +chiamato \texttt{bind} su un socket usando 0 come porta locale per ottenere il +numero di porta effiemera assegnato dal kernel. + +Inoltre quando un server esegue una \texttt{bind} su un indirizzo generico, se +chiamata dopo il completamento di una connessione sul socket restituito da +\texttt{accept}, restituisce l'indirizzo locale che il kernel ha assegnato a +quella connessione. + + +\begin{prototype}{sys/socket.h} +{int getpeername(int sockfd, struct sockaddr * name, socklen_t * namelen)} + + La funzione restituisce 0 in caso di successo e -1 in caso di errore. I + codici di errore restituiti in \texttt{errno} sono i seguenti: + \begin{errlist} + \item \texttt{EBADF} l'argomento \texttt{sockfd} non è un file descriptor + valido. + \item \texttt{ENOTSOCK} l'argomento \texttt{sockfd} non è un socket. + \item \texttt{ENOTCONN} il socket non è connesso. + \item \texttt{ENOBUFS} non ci sono risorse sufficienti nel sistema per + eseguire l'operazione. + \item \texttt{EFAULT} l'argomento \texttt{name} punta al di fuori dello + spazio di indirizzi del processo. + \end{errlist} +\end{prototype} + + +La funzione \texttt{getpeername} si usa tutte le volte che si vuole avere +l'indirizzo remoto di un socket. + +Benché nell'esempio precedente si siano usati i valori di ritorno di +\texttt{accept} per ottenere l'indirizzo del client remoto, in generale questo +non è possibile. In particolare questo avviene quando il server invece di far +gestire la connessione direttamente al figlio, come nell'esempio precedente, +lancia un opportuno programma per ciascuna connessione usando \texttt{exec} +(come ad esempio fa il \textsl{super-server} \texttt{inetd} che gestisce tutta +una serie di servizi lanciando per ogni connessione l'opportuno server). + +In questo caso benché il processo figlio abbia una immagine della memoria che +è copia di quella del processo padre (e contiene quindi anche la struttura +ritornata da \texttt{accept}) all'esecuzione di \texttt{exec} viene caricata +in memoria l'immagine del programma eseguito che a questo punto perde ogni +riferimento; ma il socket descriptor resta aperto. Allora se una opportuna +convenzione è seguita per rendere noto al programma eseguito qual'è il socket +connesso (\texttt{inetd} ad esempio fa sempre in modo che i file descriptor 0, +1 e 2 corrispondano al socket connesso) quest'ultimo potrà usare +\texttt{getpeername} per determinare l'indirizzo remoto del client. + +Infine è da chiarire che come per \texttt{accept} il terzo parametro che è +specificato dallo standard POSIX 1003.1g come di tipo \texttt{socklen\_t *} in +realtà deve sempre corrispondere ad un \texttt{int *} come prima dello +standard perché tutte le implementazioni dei socket BSD fanno questa +assunzione. diff --git a/fileunix.tex b/fileunix.tex index de5d17d..aa85117 100644 --- a/fileunix.tex +++ b/fileunix.tex @@ -20,6 +20,9 @@ L'interfaccia standard unix per l'input/output sui file \subsection{La funzione \texttt{creat}} \label{sec:fileunix_creat} +\subsection{La funzione \texttt{close}} +\label{sec:fileunix_close} + \subsection{La funzione \texttt{lseek}} \label{sec:fileunix_lseek} diff --git a/main.tex b/main.tex index 55daeec..19e3539 100644 --- a/main.tex +++ b/main.tex @@ -101,7 +101,7 @@ \include{fileunix} \include{filestd} \include{process} -\include{option} +\include{prochand} \include{signal} \include{ipc} \include{network} diff --git a/option.tex b/option.tex deleted file mode 100644 index 05da09e..0000000 --- a/option.tex +++ /dev/null @@ -1,150 +0,0 @@ -\chapter{Gestione di parametri e opzioni} -\label{cha:parameter_options} - -Il passaggio dei parametri e delle variabili di ambiente dalla riga di comando -al singolo programma quando viene lanciato è effettuato attraverso le -variabili \texttt{argc}, \texttt{argv}, queste vengono passate al programma -come argomenti della funzione principale: - -\begin{verbatim} - main(int argc, char * argv[]) -\end{verbatim} - -\section{Il formato dei parametri} -\label{sec:par_format} -Il passaggio dei parametri al programma viene effettuato dalla shell, 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 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 -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). - -\section{La gestione delle opzioni} -\label{sec:opt_handling} - -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 -no un parametro associato; un comando tipico può essere cioè qualcosa del -tipo: -\begin{verbatim} -touch -r riferimento.txt -m questofile.txt -\end{verbatim} -ed in questo caso le opzioni sono \texttt{m} ed \texttt{r}. - -Per gestire le opzioni all'interno dei parametri passati in \texttt{argv} le -librerie standard del C forniscono la funzione \texttt{getopt} (accessibile -includendo \texttt{unistd.h}), che ha il prototipo: -\begin{verbatim} -int getopt(int argc, char * const argv[], const char * optstring); -\end{verbatim} - -Questa funzione prende come argomenti le due variabili \texttt{argc} e -\texttt{argv} ed una stringa che indica quali sono le opzioni valide; la -funzione effettua la scansione della lista dei parametri ricercando ogni -stringa che comincia con \texttt{-} e ritorna ogni volta che trova una opzione -valida. - -La stringa \texttt{optstring} indica quali sono le opzioni riconosciute ed è -costituita da tutti i caratteri usati per identificare le singole opzioni, se -l'opzione ha un parametro al carattere deve essere fatto seguire un segno di -due punti \texttt{:} nel caso appena accennato ad esempio la stringa di -opzioni sarebbe \texttt{"r:m"}. - -La modalità di uso è pertanto quella di chiamare più volte la funzione -all'interno di un ciclo di while fintanto che essa non ritorna il valore -\texttt{-1} che indica che non ci sono più opzioni. Nel caso si incontri -un'opzione non dichiarata in \texttt{optstring} viene ritornato un \texttt{?} -mentre se l'opzione non è seguita da un parametro viene ritornato un -\texttt{:} infine se viene incontrato il valore \texttt{--} la scansione viene -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: -\begin{itemize} -\item \texttt{char * optarg} contiene il puntatore alla stringa argomento - dell'opzione. -\item \texttt{int optind} alla fine della scansione restituisce l'indice del - primo argomento che non è un'opzione. -\item \texttt{int opterr} previene, se posto a zero, la stampa di un messaggio - di errore in caso di riconoscimento di opzioni non definite. -\item \texttt{int optopt} contiene il carattere dell'opzione non riconosciuta. -\end{itemize} - -In \nfig è mostrato un programma di esempio, - - -\begin{figure}[htbp] - \footnotesize - \begin{lstlisting}{} - opterr = 0; /* don't want writing to stderr */ - while ( (i = getopt(argc, argv, "o:a:i:hve")) != -1) { - switch (i) { - case 'i': /* input file */ - in_file=open(optarg,O_RDONLY); - if (in_file<0) { - perror("Cannot open input file"); - exit(1); - } - break; - case 'o': /* output file (overwrite) */ - out_file=open(optarg,O_WRONLY|O_CREAT); - if (out_file<0) { - perror("Cannot open output file"); - exit(1); - } - break; - break; - case 'a': /* output file (append) */ - out_file=open(optarg,O_WRONLY|O_CREAT|O_APPEND); - break; - case 'h': /* print help usage */ - usage(); - break; - case 'v': /* set verbose mode */ - debug("Option -v active\n"); - verbose=1; - break; - case '?': /* unrecognized options */ - printf("Unrecognized options -%c\n",optopt); - usage(); - default: /* should not reached */ - debug("default option\n"); - usage(); - } - } - debug("Optind %d, argc %d\n",optind,argc); - \end{lstlisting} - \caption{Esempio di codice per la gestione delle opzioni.} - \label{fig:options_code} -\end{figure} - -\subsection{Opzioni in formato esteso} -\label{sec:opt_extended} - -Un'estensione di questo schema è costituito dalle cosiddette -\textit{long-options} espresse nella forma \texttt{--option=parameter}, anche -la gestione di queste ultime è stata standardizzata attraverso l'uso di una -versione estesa di \texttt{getopt}. - - -\section{Le variabili di ambiente} -\label{sec:par_env_var} - - -Questo va fatto. - - - - - - - diff --git a/process.tex b/process.tex index c1fc583..0c94547 100644 --- a/process.tex +++ b/process.tex @@ -1,150 +1,163 @@ -\chapter{I processi} +\chapter{Il funzionamento di un processo unix} \label{cha:process} -Come accennato nell'introduzione in un sistema unix ogni attività del sistema -viene svolta tramite i processi. Questo significa che quando un programma -viene posto in esecuzione, viene fatto partire un processo che si incarica di -eseguirne il codice. In sostanza i processi costituiscono l'unità base per -l'allocazione e l'uso delle risorse del sistema. - -In questo capitolo affronteremo i dettagli della creazione e della distruzione -dei processi, della gestione dei loro attributi e privilegi, e di tutte le -funzioni a questo connesse. - - -\section{Una panoramica sui concetti base} -\label{sec:proc_gen} - -Una delle caratteristiche essenziali di unix (che esamineremo in dettaglio più -avanti) è che ogni processo può a sua volta generare altri processi figli -(\textit{child}): questo è ad esempio quello che fa la shell quando mette in -esecuzione il programma che gli indichiamo nella linea di comando. - -Una seconda caratteristica è che ogni processo viene sempre generato in tale -modo da un processo genitore (\textit{parent}) attraverso una apposita system -call. Questo vale per tutti i processi, tranne per un processo speciale, che -normalmente è \texttt{/sbin/init}, che invece viene lanciato dal kernel finita -la fase di avvio e che quindi non è figlio di nessuno. - -Tutto ciò significa che, come per i file su disco, i processi sono organizzati -gerarchicamente dalla relazione fra genitori e figli; alla base dell'albero in -questo caso c'è init che è progenitore di ogni altro processo. - - -\section{Gli identificatori dei processi} -\label{sec:proc_id} - -Ogni processo viene identificato dal sistema da un numero identificativo -unico, il \textit{process id} o \textit{pid}. Questo viene assegnato in forma -progressiva ogni volta che un nuovo processo viene creato, fino ad un limite -massimo (in genere essendo detto numero memorizzato in un intero a 16 bit si -arriva a 32767) oltre il quale si riparte dal numero più basso disponibile -(FIXME: verificare, non sono sicuro). Per questo motivo processo il processo -di avvio (init) ha sempre il pid uguale a uno. - -Ogni processo è identificato univocamente dal sistema per il suo -pid; quest'ultimo è un apposito tipo di dato, il \texttt{pid\_t} che in -genere è un intero con segno (nel caso di Linux e delle glibc il tipo usato è -\texttt{int}. - -Tutti i processi inoltre portano traccia del pid del genitore, chiamato in -genere \textit{ppid} (da \textit{Parente Process Id}). Questi identificativi -possono essere ottenuti da un programma usando le funzioni: -\begin{itemize} - \item \texttt{pid\_t getpid(void)} restituisce il pid del processo corrente. - - \item \texttt{pid\_t getppid(void)} restituisce il pid del padre del processo - corrente. +Prima di entrare nei dettagli di come un sistema unix gestisce la presenza di +molti processi concorrenti daremo una descrizione del funzionamento di un +singolo processo, come viene posto in esecuzione, come viene terminato, come +vede la memoria e la può gestire, come può ricevere e gestire i parametri. -\end{itemize} -(per l'accesso a queste funzioni e ai relativi tipi di dati occorre includere -gli header files \texttt{unistd.h} e \texttt{sys/types.h}). - - - -\section{Il controllo dei processi} -\label{sec:proc_control} - -Esamineremo in questa sezione le varie funzioni per il controllo dei processi: -la lore creazione, la terminazione, l'esecuzione di altri programmi. Prima di -trattare in dettaglio le singole funzioni, faremo un'introduzione generale ai -contetti che stanno alla base della gestione dei processi in unix. - -\subsection{Una panoramica} -\label{sec:proc_control_overview} - -I processi vengono creati dalla funzione \texttt{fork}; in genere questa è una -system call, ma linux però usa un'altra nomenclatura, e la funzione fork è -basata a sua volta sulla system call \texttt{clone}, che viene usata anche per -generare i \textit{thread}. Il processo figlio creato dalla \textit{fork} è -una copia identica del processo processo padre, solo che ha un suo pid -proprio. - -Se si vuole che il processo padre si fermi fino alla conclusione del processo -figlio questo deve essere specificato subito dopo la fork chiamando la -funzione \texttt{wait} o la funzione \texttt{waitpid}, che restituiscono anche -una informazione abbastanza limitata (il codice di uscita) sulle cause della -terminazione del processo. - -Quando un processo ha concluso il suo compito o ha incontrato un errore non -risolvibile esso può essere terminato con la funzione \texttt{exit} (la -questione è più complessa ma ci torneremo più avanti). La vita del processo -però termina solo quando viene chiamata la quando la sua conclusione viene -ricevuta dal processo padre, a quel punto tutte le risorse allocate nel -sistema ad esso associate vengono rilasciate. - -Avere due processi che eseguono esattamente lo stesso codice non è molto -utile, mormalmente si genera un secondo processo per affidagli l'esecuzione di -un compito specifico (ad esempio gestire una connessione dopo che questa è -stata stabilita), o fargli eseguire (come fa la shell) un altro programma. Per -questo si usa la seconda funzione fondamentale per programmazione coi processi -che è la \texttt{exec}. -Il programma che un processo sta eseguendo si chiama immagine del processo -(\textit{process image}), le funzioni della famiglia \textit{exec} permettono -di caricare un'altro programma da disco sostituendo quest'ultimo alla process -image corrente, questo fa si che la precedente immagine venga completamente -cancellata e quando il nuovo programma esce anche il processo termina, senza -ritornare alla precedente immagine. +\section{La funzione \texttt{main}} +\label{sec:proc_} -Per questo motivo la \texttt{fork} e la \texttt{exec} sono funzioni molto -particolari con caratteristiche uniche rispetto a tutte le altre, infatti la -prima ritorna due volte (nel processo padre e nel figlio) mentre la seconda -non ritorna mai (in quanto con essa viene eseguito un altro programma). -\subsection{La funzione \texttt{fork}} -\label{sec:proc_fork} - - -Dopo l'esecuzione di una fork sia il processo padre che il processo figlio -continuano ad essere eseguiti normalmente, ed il processo figlio esegue -esattamente lo stesso codice del padre. La sola differenza è che nel processo -padre il valore di ritorno della funzione fork è il pid del processo figlio, -mentre nel figlio è zero; in questo modo il programma può identificare se -viene eseguito dal padre o dal figlio. - +\subsection{} +\label{sec:proc_} \subsection{La funzione \texttt{exit}} \label{sec:proc_exit} -\subsection{Le funzioni \texttt{wait} e \texttt{waitpid}} -\label{sec:proc_wait} - -\subsection{Le funzioni \texttt{exec}} -\label{sec:proc_exec} - +\section{Gestione di parametri e opzioni} +\label{sec:parameter_options} + +Il passaggio dei parametri e delle variabili di ambiente dalla riga di comando +al singolo programma quando viene lanciato è effettuato attraverso le +variabili \texttt{argc}, \texttt{argv} che vengono passate al programma +come argomenti della funzione principale: + +\subsection{Il formato dei parametri} +\label{sec:proc_par_format} +In genere passaggio dei parametri al programma viene effettuato dalla shell, +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 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 +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). + +\subsection{La gestione delle opzioni} +\label{sec:proc_opt_handling} + +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 +no un parametro associato; un comando tipico può essere cioè qualcosa del +tipo: +\begin{verbatim} +touch -r riferimento.txt -m questofile.txt +\end{verbatim} +ed in questo caso le opzioni sono \texttt{m} ed \texttt{r}. + +Per gestire le opzioni all'interno dei parametri passati in \texttt{argv} le +librerie standard del C forniscono la funzione \texttt{getopt} (accessibile +includendo \texttt{unistd.h}), che ha il prototipo: +\begin{verbatim} +int getopt(int argc, char * const argv[], const char * optstring); +\end{verbatim} + +Questa funzione prende come argomenti le due variabili \texttt{argc} e +\texttt{argv} ed una stringa che indica quali sono le opzioni valide; la +funzione effettua la scansione della lista dei parametri ricercando ogni +stringa che comincia con \texttt{-} e ritorna ogni volta che trova una opzione +valida. + +La stringa \texttt{optstring} indica quali sono le opzioni riconosciute ed è +costituita da tutti i caratteri usati per identificare le singole opzioni, se +l'opzione ha un parametro al carattere deve essere fatto seguire un segno di +due punti \texttt{:} nel caso appena accennato ad esempio la stringa di +opzioni sarebbe \texttt{"r:m"}. + +La modalità di uso è pertanto quella di chiamare più volte la funzione +all'interno di un ciclo di while fintanto che essa non ritorna il valore +\texttt{-1} che indica che non ci sono più opzioni. Nel caso si incontri +un'opzione non dichiarata in \texttt{optstring} viene ritornato un \texttt{?} +mentre se l'opzione non è seguita da un parametro viene ritornato un +\texttt{:} infine se viene incontrato il valore \texttt{--} la scansione viene +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: +\begin{itemize} +\item \texttt{char * optarg} contiene il puntatore alla stringa argomento + dell'opzione. +\item \texttt{int optind} alla fine della scansione restituisce l'indice del + primo argomento che non è un'opzione. +\item \texttt{int opterr} previene, se posto a zero, la stampa di un messaggio + di errore in caso di riconoscimento di opzioni non definite. +\item \texttt{int optopt} contiene il carattere dell'opzione non riconosciuta. +\end{itemize} - - - -\section{Il controllo di accesso} -\label{sec:process_perms} - -Va messo qui tutta la storia su effective, real, saved uid, e pure le cose di -linux come il filesystem uid. +In \nfig è mostrato un programma di esempio, + + +\begin{figure}[htbp] + \footnotesize + \begin{lstlisting}{} + opterr = 0; /* don't want writing to stderr */ + while ( (i = getopt(argc, argv, "o:a:i:hve")) != -1) { + switch (i) { + case 'i': /* input file */ + in_file=open(optarg,O_RDONLY); + if (in_file<0) { + perror("Cannot open input file"); + exit(1); + } + break; + case 'o': /* output file (overwrite) */ + out_file=open(optarg,O_WRONLY|O_CREAT); + if (out_file<0) { + perror("Cannot open output file"); + exit(1); + } + break; + break; + case 'a': /* output file (append) */ + out_file=open(optarg,O_WRONLY|O_CREAT|O_APPEND); + break; + case 'h': /* print help usage */ + usage(); + break; + case 'v': /* set verbose mode */ + debug("Option -v active\n"); + verbose=1; + break; + case '?': /* unrecognized options */ + printf("Unrecognized options -%c\n",optopt); + usage(); + default: /* should not reached */ + debug("default option\n"); + usage(); + } + } + debug("Optind %d, argc %d\n",optind,argc); + \end{lstlisting} + \caption{Esempio di codice per la gestione delle opzioni.} + \label{fig:proc_options_code} +\end{figure} + +\subsection{Opzioni in formato esteso} +\label{sec:proc_opt_extended} + +Un'estensione di questo schema è costituito dalle cosiddette +\textit{long-options} espresse nella forma \texttt{--option=parameter}, anche +la gestione di queste ultime è stata standardizzata attraverso l'uso di una +versione estesa di \texttt{getopt}. + + +\subsection{Le variabili di ambiente} +\label{sec:proc_env_var} + +Questo va fatto. diff --git a/prochand.tex b/prochand.tex new file mode 100644 index 0000000..390b072 --- /dev/null +++ b/prochand.tex @@ -0,0 +1,155 @@ +\chapter{La gestione dei processi} +\label{cha:process_handling} + +Come accennato nell'introduzione in un sistema unix ogni attività del sistema +viene svolta tramite i processi. In sostanza i processi costituiscono l'unità +base per l'allocazione e l'uso delle risorse del sistema. + +Nel precedente capitolo abbiamo visto come funziona un singolo processo, in +questo capitolo affronteremo i dettagli della creazione e della distruzione +dei processi, della gestione dei loro attributi e privilegi, e di tutte le +funzioni a questo connesse. + + +\section{Una panoramica sui concetti base} +\label{sec:proc_gen} + +Una delle caratteristiche essenziali di unix (che esamineremo in dettaglio più +avanti) è che ogni processo può a sua volta generare altri processi figli +(\textit{child}): questo è ad esempio quello che fa la shell quando mette in +esecuzione il programma che gli indichiamo nella linea di comando. + +Una seconda caratteristica è che ogni processo viene sempre generato in tale +modo da un processo genitore (\textit{parent}) attraverso una apposita system +call. Questo vale per tutti i processi, tranne per un processo speciale, che +normalmente è \texttt{/sbin/init}, che invece viene lanciato dal kernel finita +la fase di avvio e che quindi non è figlio di nessuno. + +Tutto ciò significa che, come per i file su disco, i processi sono organizzati +gerarchicamente dalla relazione fra genitori e figli; alla base dell'albero in +questo caso c'è init che è progenitore di ogni altro processo. + + +\section{Gli identificatori dei processi} +\label{sec:proc_id} + +Ogni processo viene identificato dal sistema da un numero identificativo +unico, il \textit{process id} o \textit{pid}. Questo viene assegnato in forma +progressiva ogni volta che un nuovo processo viene creato, fino ad un limite +massimo (in genere essendo detto numero memorizzato in un intero a 16 bit si +arriva a 32767) oltre il quale si riparte dal numero più basso disponibile +(FIXME: verificare, non sono sicuro). Per questo motivo processo il processo +di avvio (init) ha sempre il pid uguale a uno. + +Ogni processo è identificato univocamente dal sistema per il suo +pid; quest'ultimo è un apposito tipo di dato, il \texttt{pid\_t} che in +genere è un intero con segno (nel caso di Linux e delle glibc il tipo usato è +\texttt{int}. + +Tutti i processi inoltre portano traccia del pid del genitore, chiamato in +genere \textit{ppid} (da \textit{Parente Process Id}). Questi identificativi +possono essere ottenuti da un programma usando le funzioni: +\begin{itemize} + \item \texttt{pid\_t getpid(void)} restituisce il pid del processo corrente. + + \item \texttt{pid\_t getppid(void)} restituisce il pid del padre del processo + corrente. + +\end{itemize} +(per l'accesso a queste funzioni e ai relativi tipi di dati occorre includere +gli header files \texttt{unistd.h} e \texttt{sys/types.h}). + + + +\section{Il controllo dei processi} +\label{sec:proc_control} + +Esamineremo in questa sezione le varie funzioni per il controllo dei processi: +la lore creazione, la terminazione, l'esecuzione di altri programmi. Prima di +trattare in dettaglio le singole funzioni, faremo un'introduzione generale ai +contetti che stanno alla base della gestione dei processi in unix. + +\subsection{Una panoramica} +\label{sec:proc_control_overview} + +I processi vengono creati dalla funzione \texttt{fork}; in genere questa è una +system call, ma linux però usa un'altra nomenclatura, e la funzione fork è +basata a sua volta sulla system call \texttt{clone}, che viene usata anche per +generare i \textit{thread}. Il processo figlio creato dalla \textit{fork} è +una copia identica del processo processo padre, solo che ha un suo pid +proprio. + +Se si vuole che il processo padre si fermi fino alla conclusione del processo +figlio questo deve essere specificato subito dopo la fork chiamando la +funzione \texttt{wait} o la funzione \texttt{waitpid}, che restituiscono anche +una informazione abbastanza limitata (il codice di uscita) sulle cause della +terminazione del processo. + +Quando un processo ha concluso il suo compito o ha incontrato un errore non +risolvibile esso può essere terminato con la funzione \texttt{exit} (la +questione è più complessa ma ci torneremo più avanti). La vita del processo +però termina solo quando viene chiamata la quando la sua conclusione viene +ricevuta dal processo padre, a quel punto tutte le risorse allocate nel +sistema ad esso associate vengono rilasciate. + +Avere due processi che eseguono esattamente lo stesso codice non è molto +utile, mormalmente si genera un secondo processo per affidagli l'esecuzione di +un compito specifico (ad esempio gestire una connessione dopo che questa è +stata stabilita), o fargli eseguire (come fa la shell) un altro programma. Per +questo si usa la seconda funzione fondamentale per programmazione coi processi +che è la \texttt{exec}. + +Il programma che un processo sta eseguendo si chiama immagine del processo +(\textit{process image}), le funzioni della famiglia \textit{exec} permettono +di caricare un'altro programma da disco sostituendo quest'ultimo alla process +image corrente, questo fa si che la precedente immagine venga completamente +cancellata e quando il nuovo programma esce anche il processo termina, senza +ritornare alla precedente immagine. + +Per questo motivo la \texttt{fork} e la \texttt{exec} sono funzioni molto +particolari con caratteristiche uniche rispetto a tutte le altre, infatti la +prima ritorna due volte (nel processo padre e nel figlio) mentre la seconda +non ritorna mai (in quanto con essa viene eseguito un altro programma). + + +\subsection{La funzione \texttt{fork}} +\label{sec:proc_fork} + + +Dopo l'esecuzione di una fork sia il processo padre che il processo figlio +continuano ad essere eseguiti normalmente, ed il processo figlio esegue +esattamente lo stesso codice del padre. La sola differenza è che nel processo +padre il valore di ritorno della funzione fork è il pid del processo figlio, +mentre nel figlio è zero; in questo modo il programma può identificare se +viene eseguito dal padre o dal figlio. + + + + +\subsection{Le funzioni \texttt{wait} e \texttt{waitpid}} +\label{sec:proc_wait} + +\subsection{Le funzioni \texttt{exec}} +\label{sec:proc_exec} + + + + + + +\section{Il controllo di accesso} +\label{sec:process_perms} + +Va messo qui tutta la storia su effective, real, saved uid, e pure le cose di +linux come il filesystem uid. + + +\subsection{Le funzioni \texttt{setuid} e \texttt{setgid}} +\label{sec:proc_setuid} + + +\subsection{Le funzioni \texttt{seteuid} e \texttt{setegid}} +\label{sec:proc_setuid} + + + diff --git a/socket.tex b/socket.tex index ecf1a9e..b379719 100644 --- a/socket.tex +++ b/socket.tex @@ -632,13 +632,15 @@ di indirizzo e pu famiglia indicata non è valida entrambe le funzioni ritornano un valore negativo e settano la variabile \texttt{errno} al valore \texttt{EAFNOSUPPORT}. I prototipi delle suddette funzioni sono i seguenti: -\begin{prototype}{int inet\_pton(int family, const char *src, void *dest)} +\begin{prototype}{sys/socket.h} +{int inet\_pton(int family, const char *src, void *dest)} Converte la stringa puntata da \texttt{src} nell'indirizzo binario da memorizzare all'indirizzo puntato da \texttt{dest}, restituendo 0 in caso di successo e 1 in caso di fallimento. \end{prototype} -\begin{prototype}{char *inet\_ntop(int family, const void *src, char *dest, - size\_t len)} + +\begin{prototype}{sys/socket.h} +{char *inet\_ntop(int family, const void *src, char *dest, size\_t len)} Converte la struttura dell'indirizzo puntata da \texttt{src} in una stringa che viene copiata nel buffer puntato dall'indirizzo \texttt{dest}; questo deve essere preallocato dall'utente e la lunghezza deve essere almeno -- 2.30.2