Inserita la trattazione delle affinita di processore, sistemati tutti i
authorSimone Piccardi <piccardi@gnulinux.it>
Sun, 17 Apr 2005 22:38:07 +0000 (22:38 +0000)
committerSimone Piccardi <piccardi@gnulinux.it>
Sun, 17 Apr 2005 22:38:07 +0000 (22:38 +0000)
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.

fileadv.tex
listati/hand_sigchild.c
process.tex
prochand.tex
sources/SigHand.c
tcpsock.tex

index 9e2bf77df693713c7f845d5b83c00828ddcdf0fa..2f55a322ac0c34087b7b09545541eb81142f32fb 100644 (file)
@@ -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
index 2ae8cc61f375eaa361d3f4581cc283afd63fc653..f55168f8aea0b153151696a884f56afc108ab2fc 100644 (file)
@@ -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 */
index b19f7dd9eecee4e135d2c8479427c263481a1dbb..43dac1d9e82f79237febc7703000a9bf8d51fc18 100644 (file)
@@ -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
index f970dc0c93153ffef8b30e67ae40c94f245d2dab..9be38cc84eafd92a8f8b4464be3ade84dcc8b4d2 100644 (file)
@@ -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 99il 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}
index 6112700af7a7525eba3c574aecd01d95dc7373ca..b5d6c7bda49de7c003e7cdf80bf2dd83b38212e6 100644 (file)
@@ -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 */
index 7fa55073608f878586e2ee15a8addfe46975a0ba..90dbaf7a659424e75fac2c0332653fc63b9db3d1 100644 (file)
@@ -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