From: Simone Piccardi Date: Sun, 17 Apr 2005 22:38:07 +0000 (+0000) Subject: Inserita la trattazione delle affinita di processore, sistemati tutti i X-Git-Url: https://gapil.gnulinux.it/gitweb/?a=commitdiff_plain;h=c48c736afae5eb92cc217cb2288fbb9288827d23;p=gapil.git Inserita la trattazione delle affinita di processore, sistemati tutti i riferimenti alle macro degli insiemi di processori e di file descriptor, risistemate varie indicizzazione sbagliate. Inserita correzione all'esempio del gestore di SIGCHLD suggerita da Fabio Rossi. --- diff --git a/fileadv.tex b/fileadv.tex index 9e2bf77..2f55a32 100644 --- a/fileadv.tex +++ b/fileadv.tex @@ -136,16 +136,16 @@ opportune macro di preprocessore: \headdecl{sys/time.h} \headdecl{sys/types.h} \headdecl{unistd.h} - \funcdecl{FD\_ZERO(fd\_set *set)} + \funcdecl{void \macro{FD\_ZERO}(fd\_set *set)} Inizializza l'insieme (vuoto). - \funcdecl{FD\_SET(int fd, fd\_set *set)} + \funcdecl{void \macro{FD\_SET}(int fd, fd\_set *set)} Inserisce il file descriptor \param{fd} nell'insieme. - \funcdecl{FD\_CLR(int fd, fd\_set *set)} + \funcdecl{void \macro{FD\_CLR}(int fd, fd\_set *set)} Rimuove il file descriptor \param{fd} nell'insieme. - \funcdecl{FD\_ISSET(int fd, fd\_set *set)} + \funcdecl{int \macro{FD\_ISSET}(int fd, fd\_set *set)} Controlla se il file descriptor \param{fd} è nell'insieme. \end{functions} @@ -193,7 +193,7 @@ La funzione restituisce il numero di file descriptor pronti,\footnote{questo funzione è recente, ed esistono ancora alcune versioni di Unix che non si comportano in questo modo.} e ciascun insieme viene sovrascritto per indicare quali sono i file descriptor pronti per le operazioni ad esso -relative, in modo da poterli controllare con \const{FD\_ISSET}. Se invece si +relative, in modo da poterli controllare con \macro{FD\_ISSET}. Se invece si ha un timeout viene restituito un valore nullo e gli insiemi non vengono modificati. In caso di errore la funzione restituisce -1, ed i valori dei tre insiemi sono indefiniti e non si può fare nessun affidamento sul loro diff --git a/listati/hand_sigchild.c b/listati/hand_sigchild.c index 2ae8cc6..f55168f 100644 --- a/listati/hand_sigchild.c +++ b/listati/hand_sigchild.c @@ -12,7 +12,7 @@ void HandSigCHLD(int sig) if (pid > 0) { debug("child %d terminated with status %x\n", pid, status); } - } while ((pid > 0) && (errno == EINTR)); + } while (pid > 0); /* restore errno value */ errno = errno_save; /* return */ diff --git a/process.tex b/process.tex index b19f7dd..43dac1d 100644 --- a/process.tex +++ b/process.tex @@ -579,12 +579,13 @@ dell'applicazione in esecuzione. In altri linguaggi come il java e recentemente il C\# il problema non si pone nemmeno perché la gestione della memoria viene fatta totalmente in maniera automatica, ovvero il programmatore non deve minimamente preoccuparsi di -liberare la memoria allocata precedentemente quando non serve più, poiché il -framework gestisce automaticamente la cosiddetta \textit{garbage collection}. -In tal caso, attraverso meccanismi simili a quelli del \textit{reference - counting}, quando una zona di memoria precedentemente allocata non è più -riferita da nessuna parte del codice in esecuzione, può essere deallocata -automaticamente in qualunque momento dall'infrastruttura. +liberare la memoria allocata precedentemente quando non serve più, poiché +l'infrastruttura del linguaggio gestisce automaticamente la cosiddetta +\index{\textit{garbage~collection}}\textit{garbage collection}. In tal caso, +attraverso meccanismi simili a quelli del \textit{reference counting}, quando +una zona di memoria precedentemente allocata non è più riferita da nessuna +parte del codice in esecuzione, può essere deallocata automaticamente in +qualunque momento dall'infrastruttura. Anche questo va a scapito delle prestazioni dell'applicazione in esecuzione (inoltre le applicazioni sviluppate con tali linguaggi di solito non sono diff --git a/prochand.tex b/prochand.tex index f970dc0..9be38cc 100644 --- a/prochand.tex +++ b/prochand.tex @@ -571,7 +571,7 @@ file senza una qualche forma di sincronizzazione in quanto, come visto anche con il nostro esempio, le varie scritture risulteranno mescolate fra loro in una sequenza impredicibile. Per questo le modalità con cui in genere si usano i file dopo una \func{fork} sono sostanzialmente due: -\begin{enumerate} +\begin{enumerate*} \item Il processo padre aspetta la conclusione del figlio. In questo caso non è necessaria nessuna azione riguardo ai file, in quanto la sincronizzazione della posizione corrente dopo eventuali operazioni di lettura e scrittura @@ -579,7 +579,7 @@ i file dopo una \func{fork} sono sostanzialmente due: \item L'esecuzione di padre e figlio procede indipendentemente. In questo caso ciascuno dei due processi deve chiudere i file che non gli servono una volta che la \func{fork} è stata eseguita, per evitare ogni forma di interferenza. -\end{enumerate} +\end{enumerate*} Oltre ai file aperti i processi figli ereditano dal padre una serie di altre proprietà; la lista dettagliata delle proprietà che padre e figlio hanno in @@ -603,6 +603,8 @@ comune dopo l'esecuzione di una \func{fork} \item i segmenti di memoria condivisa agganciati al processo (vedi sez.~\ref{sec:ipc_sysv_shm}); \item i limiti sulle risorse (vedi sez.~\ref{sec:sys_resource_limit}); +\item le priorità real-time e le affinità di processore (vedi + sez.~\ref{sec:proc_real_time}); \item le variabili di ambiente (vedi sez.~\ref{sec:proc_environ}). \end{itemize*} Le differenze fra padre e figlio dopo la \func{fork} invece sono: @@ -2256,8 +2258,8 @@ Il valore della priorit \struct{sched\_param} (riportata in fig.~\ref{fig:sig_sched_param}), il cui solo campo attualmente definito è \var{sched\_priority}, che nel caso delle priorità assolute deve essere specificato nell'intervallo fra un valore -massimo ed uno minimo, che nel caso sono rispettivamente 1 e 99 (il valore -zero è legale, ma indica i processi normali). +massimo ed uno minimo, che nel caso sono rispettivamente 1 e 99; il valore +nullo è legale, ma indica i processi normali. \begin{figure}[!bht] \footnotesize \centering @@ -2386,7 +2388,10 @@ il suo prototipo La funzione restituisce il valore dell'intervallo di tempo usato per la politica \textit{round robin} in una struttura \struct{timespec}, (la cui -definizione si può trovare in fig.~\ref{fig:sys_timeval_struct}). +definizione si può trovare in fig.~\ref{fig:sys_timeval_struct}). In realtà +dato che in Linux questo intervallo di tempo è prefissato e non modificabile, +questa funzione ritorna sempre un valore di 150 millisecondi, e non importa +specificare il PID di un processo reale. Come accennato ogni processo che usa lo scheduling real-time può rilasciare @@ -2409,33 +2414,176 @@ in modalit con pari priorità quando la sezione più urgente è finita. Infine con il supporto dei sistemi multiprocessore sono state introdotte delle -funzioni che permettono di controllare ... - - -\begin{functions} - \headdecl{sched.h} - - \funcdecl{int sched\_setaffinity(pid\_t pid, unsigned int len, unsigned long - *mask)} +funzioni che permettono di controllare in maniera più dettagliata la scelta di +quale processore utilizzare per eseguire un certo programma. Uno dei problemi +che si pongono nei sistemi multiprocessore è infatti quello +dell'\textsl{effetto ping-pong}.\index{\textsl{effetto ping-pong}} Può +accadere cioè che lo scheduler, quando riavvia un processo precedentemente +interrotto, scegliendo il primo processore disponibile lo faccia eseguire da +un processore diverso rispetto a quello su cui era stato eseguito in +precedenza. Se il processo passa da un processore all'altro in questo modo +(cosa che avveniva abbastanza di frequente con i kernel della seria 2.4.x) si +ha l'\textsl{effetto ping-pong}. + +Questo tipo di comportamento può generare dei seri problemi di prestazioni; +infatti tutti i processori moderni utilizzano una memoria interna (la +\textit{cache}) contenente i dati più usati, che permette di evitare di +eseguire un accesso (molto più lento) alla memoria principale sulla scheda +madre. Chiaramente un processo sarà favorito se i suoi dati sono nella cache +del processore, ma è ovvio che questo può essere vero solo per un processore +alla volta, perché in presenza di più copie degli stessi dati su più +processori, non si potrebbe determinare quale di questi ha la versione dei +dati aggiornata rispetto alla memoria principale. + +Questo comporta che quando un processore inserisce un dato nella sua cache, +tutti gli altri processori che hanno lo stesso dato devono invalidarlo, e +questa operazione è molto costosa in termini di prestazioni. Il problema +diventa serio quando si verifica l'\textsl{effetto ping-pong}, in tal caso +infatti un processo \textsl{rimbalza} continuamente da un processore all'altro +e si ha una continua invalidazione della cache, che non diventa mai +disponibile. + +Per ovviare a questo tipo di problemi è nato il concetto di \textsl{affinità + di processore} (o \index{\textit{CPU~affinity}}\textit{CPU affinity}); la +possibilità cioè di far sì che un processo possa essere assegnato per +l'esecuzione sempre allo stesso processore. Lo scheduler dei kernel della +serie 2.4.x aveva una scarsa \textit{CPU affinity}, e l'effetto ping-pong era +comune; con il nuovo scheduler dei kernel della 2.6.x questo problema è stato +risolto ed esso cerca di mantenere il più possibile ciascun processo sullo +stesso processore. + +In certi casi però resta l'esigenza di poter essere sicuri che un processo sia +sempre eseguito dallo stesso processore,\footnote{quella che viene detta + \textit{hard CPU affinity}, in contrasto con quella fornita dallo scheduler, + detta \textit{soft CPU affinity}, che di norma indica solo una preferenza, + non un requisito assoluto.} e per poter risolvere questo tipo di +problematiche nei nuovi kernel\footnote{le due system call per la gestione + della \textit{CPU affinity} sono state introdotte nel kernel 2.5.8, e le + funzioni di libreria nelle \textsl{glibc} 2.3.} è stata introdotta +l'opportuna infrastruttura ed una nuova system call che permette di impostare +su quali processori far eseguire un determinato processo attraverso una +\textsl{maschera di affinità}. La corrispondente funzione di libreria è +\funcd{sched\_setaffinity} ed il suo prototipo\footnote{di questa funzione (e + della corrispondente \func{sched\_setaffinity}) esistono versioni diverse + per gli argomenti successivi a \param{pid}: la prima (quella riportata nella + pagina di manuale) prevedeva due ulteriori argomenti di tipo + \texttt{unsigned int len} e \texttt{unsigned long *mask}, poi l'argomento + \texttt{len} è stato eliminato, successivamente si è introdotta la versione + riportata con però un secondo argomento di tipo \texttt{size\_t cpusetsize} + (anche questa citata nella pagina di manuale); la versione citata è quella + riportata nel manuale delle \textsl{glibc} e corripondente alla definizione + presente in \file{sched.h}.} è: +\begin{prototype}{sched.h} + {int sched\_setaffinity (pid\_t pid, const cpu\_set\_t *cpuset)} Imposta la maschera di affinità del processo \param{pid}. - - \funcdecl{int sched\_getaffinity(pid\_t pid, unsigned int len, unsigned long - *mask)} - Legge la maschera di affinità del processo \param{pid}. - + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, nel qual caso \var{errno} può assumere i valori: \begin{errlist} \item[\errcode{ESRCH}] il processo \param{pid} non esiste. - \item[\errcode{EINVAL}] la maschera \param{mask} fa riferimento a - processori che non esistono o la sua lunghezza \param{len} è minore di - quella usata dal kernel. + \item[\errcode{EINVAL}] il valore di \param{cpuset} contiene riferimenti a + processori non esistenti nel sistema. \item[\errcode{EPERM}] il processo non ha i privilegi sufficienti per eseguire l'operazione. \end{errlist} ed inoltre anche \errval{EFAULT}.} +\end{prototype} + +La funzione imposta, con l'uso del valore contenuto all'indirizzo +\param{cpuset}, l'insieme dei processori sui quali deve essere eseguito il +processo identificato tramite il valore passato in \param{pid}. Come in +precedenza il valore nullo di \param{pid} indica il processo corrente. Per +poter utilizzare questa funzione sono richiesti i privilegi di amministratore +(è necessaria la capacità \const{CAP\_SYS\_NICE}) altrimenti essa fallirà con +un errore di \errcode{EPERM}. Una volta impostata una maschera di affinità, +questa viene ereditata attraverso una \func{fork}, in questo modo diventa +possibile legare automaticamente un gruppo di processi ad un singolo +processore. + +Nell'uso comune, almeno con i kernel della serie 2.6.x, l'uso di questa +funzione non è necessario, in quanto è lo scheduler stesso che provvede a +mantenere al meglio l'affinità di processore. Esistono però esigenze +particolari, ad esempio quando un processo (o un gruppo di processi) è +utilizzato per un compito importante (ad esempio per applicazioni real-time o +la cui risposta è critica) e si vuole la massima velocità, con questa +interfaccia diventa possibile selezionare gruppi di processori utilizzabili in +maniera esclusiva. Lo stesso dicasi quando l'accesso a certe risorse (memoria +o periferiche) può avere un costo diverso a seconda del processore (come +avviene nelle architetture NUMA). + +Infine se un gruppo di processi accede alle stesse risorse condivise (ad +esempio una applicazione con più thread) può avere senso usare lo stesso +processore in modo da sfruttare meglio l'uso della sua cache; questo +ovviamente riduce i benefici di un sistema multiprocessore nell'esecuzione +contemporanea dei thread, ma in certi casi (quando i thread sono inerentemente +serializzati nell'accesso ad una risorsa) possono esserci sufficienti vantaggi +nell'evitare la perdita della cache da rendere conveniente l'uso dell'affinità +di processore. + +Per facilitare l'uso dell'argomento \param{cpuset} le \acr{glibc} hanno +introdotto un apposito dato di tipo, \ctyp{cpu\_set\_t},\footnote{questa è una + estensione specifica delle \acr{glibc}, da attivare definendo la macro + \macro{\_GNU\_SOURCE}, non esiste infatti una standardardizzazione per + questo tipo di interfaccia e POSIX al momento non prevede nulla al + riguardo.} che permette di identificare un insieme di processori. Il dato è +una maschera binaria: in generale è un intero a 32 bit in cui ogni bit +corrisponde ad un processore, ma dato che per architetture particolari il +numero di bit di un intero può non essere sufficiente, è stata creata questa +che è una interfaccia generica che permette di usare a basso livello un tipo +di dato qualunque rendendosi indipendenti dal numero di bit e dalla loro +disposizione. + +Questa interfaccia, oltre alla definizione del tipo di dato apposito, prevede +anche una serie di macro di preprocessore per la manipolazione dello stesso, +che consentono di svuotare un insieme, aggiungere o togliere un processore da +esso o verificare se vi è già presente: +\begin{functions} + \headdecl{sched.h} + \funcdecl{void \macro{CPU\_ZERO}(cpu\_set\_t *set)} + Inizializza l'insieme (vuoto). + + \funcdecl{void \macro{CPU\_SET}(int cpu, cpu\_set\_t *set)} + Inserisce il processore \param{cpu} nell'insieme. + + \funcdecl{void \macro{CPU\_CLR}(int cpu, cpu\_set\_t *set)} + Rimuove il processore \param{cpu} nell'insieme. + + \funcdecl{int \macro{CPU\_ISSET}(int cpu, cpu\_set\_t *set)} + Controlla se il processore \param{cpu} è nell'insieme. \end{functions} +Oltre a queste macro, simili alle analoghe usate per gli insiemi di file +descriptor (vedi sez.~\ref{sec:file_select}) è definita la costante +\const{CPU\_SETSIZE} che indica il numero massimo di processori che possono +far parte dell'insieme, e che costituisce un limite massimo al valore +dell'argomento \param{cpu}. + +In generale la maschera di affinità è preimpostata in modo che un processo +possa essere eseguito su qualunque processore, se può comunque leggere il +valore per un processo specifico usando la funzione +\funcd{sched\_getaffinity}, il suo prototipo è: +\begin{prototype}{sched.h} + {int sched\_getaffinity (pid\_t pid, const cpu\_set\_t *cpuset)} + Legge la maschera di affinità del processo \param{pid}. + + \bodydesc{La funzione ritorna 0 in caso di successo e -1 in caso di errore, + nel qual caso \var{errno} può assumere i valori: + \begin{errlist} + \item[\errcode{ESRCH}] il processo \param{pid} non esiste. + \item[\errcode{EFAULT}] il valore di \param{cpuset} non è un indirizzo + valido. + \end{errlist} } +\end{prototype} + +La funzione restituirà all'indirizzo specificato da \param{cpuset} il valore +della maschera di affinità del processo, così da poterla riutilizzare per una +successiva reimpostazione. In questo caso non sono necessari privilegi +paricolari. + +È chiaro che queste funzioni per la gestione dell'affinità hanno significato +soltanto su un sistema multiprocessore, esse possono comunque essere +utilizzate anche in un sistema con un processore singolo, nel qual caso però +non avranno alcun risultato effettivo. \section{Problematiche di programmazione multitasking} diff --git a/sources/SigHand.c b/sources/SigHand.c index 6112700..b5d6c7b 100644 --- a/sources/SigHand.c +++ b/sources/SigHand.c @@ -113,7 +113,7 @@ void HandSigCHLD(int sig) // if (pid > 0) { // debug("child %d terminated with status %x\n", pid, status); // } - } while ((pid > 0) && (errno == EINTR)); + } while (pid > 0); /* restore errno value*/ errno = errno_save; /* return */ diff --git a/tcpsock.tex b/tcpsock.tex index 7fa5507..90dbaf7 100644 --- a/tcpsock.tex +++ b/tcpsock.tex @@ -3250,7 +3250,7 @@ che ci servir connessioni o di dati in arrivo, e processarli immediatamente. Per implementare lo schema mostrato in fig.~\ref{fig:TCP_echo_multiplex}, il programma usa una tabella dei socket connessi mantenuta nel vettore -\var{fd\_open} dimensionato al valore di \macro{FD\_SETSIZE}, ed una variabile +\var{fd\_open} dimensionato al valore di \const{FD\_SETSIZE}, ed una variabile \var{max\_fd} per registrare il valore più alto dei file descriptor aperti. Prima di entrare nel ciclo principale (\texttt{\small 6--56}) la nostra