X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=blobdiff_plain;f=prochand.tex;h=651e14536584e9e1e5289714ea92da1ef8cdc928;hp=f970dc0c93153ffef8b30e67ae40c94f245d2dab;hb=9a6d19e384fe9b1afbe4d9124ac34eaf7aa57562;hpb=be020c396791d8cc5f2564e56cb5be2af891edd4 diff --git a/prochand.tex b/prochand.tex index f970dc0..651e145 100644 --- a/prochand.tex +++ b/prochand.tex @@ -267,7 +267,7 @@ fig.~\ref{fig:proc_fork_code}, nel programma \file{ForkTest.c}. Il fatto che il \acr{pid} sia un numero univoco per il sistema lo rende un candidato per generare ulteriori indicatori associati al processo di cui diventa possibile garantire l'unicità: ad esempio in alcune implementazioni la -funzione \func{tmpname} (si veda sez.~\ref{sec:file_temp_file}) usa il +funzione \func{tempnam} (si veda sez.~\ref{sec:file_temp_file}) usa il \acr{pid} per generare un \index{\textit{pathname}}\textit{pathname} univoco, che non potrà essere replicato da un altro processo che usi la stessa funzione. @@ -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: @@ -679,7 +681,7 @@ eseguite alla chiusura di un processo sez.~\ref{sec:sig_sigchld}); \item se il processo è un leader di sessione ed il suo terminale di controllo è quello della sessione viene mandato un segnale di \const{SIGHUP} a tutti i - processi del gruppo di foreground e il terminale di controllo viene + processi del gruppo di \textit{foreground} e il terminale di controllo viene disconnesso (vedi sez.~\ref{sec:sess_ctrl_term}); \item se la conclusione di un processo rende orfano un \textit{process group} ciascun membro del gruppo viene bloccato, e poi gli vengono @@ -769,10 +771,10 @@ informazione, non pi completamente conclusa. Possiamo utilizzare il nostro programma di prova per analizzare anche questa -condizione: lanciamo il comando \cmd{forktest} in background, indicando al -processo padre di aspettare 10 secondi prima di uscire; in questo caso, usando -\cmd{ps} sullo stesso terminale (prima dello scadere dei 10 secondi) -otterremo: +condizione: lanciamo il comando \cmd{forktest} in \textit{background} (vedi +sez.~\ref{sec:sess_job_control}), indicando al processo padre di aspettare 10 +secondi prima di uscire; in questo caso, usando \cmd{ps} sullo stesso +terminale (prima dello scadere dei 10 secondi) otterremo: \footnotesize \begin{verbatim} @@ -1296,7 +1298,7 @@ vari parametri connessi ai processi. \label{sec:proc_perms} In questa sezione esamineremo le problematiche relative al controllo di -accesso dal punto di vista del processi; vedremo quali sono gli identificatori +accesso dal punto di vista dei processi; vedremo quali sono gli identificatori usati, come questi possono essere modificati nella creazione e nel lancio di nuovi processi, le varie funzioni per la loro manipolazione diretta e tutte le problematiche connesse ad una gestione accorta dei privilegi. @@ -1334,7 +1336,7 @@ kernel nella gestione dei permessi di accesso. Dato che tutte le operazioni del sistema vengono compiute dai processi, è evidente che per poter implementare un controllo sulle operazioni occorre anche poter identificare chi è che ha lanciato un certo programma, e pertanto -anche a ciascun processo dovrà essere associato ad un utente e ad un gruppo. +anche a ciascun processo dovrà essere associato un utente e un gruppo. Un semplice controllo di una corrispondenza fra identificativi non garantisce però sufficiente flessibilità per tutti quei casi in cui è necessario poter @@ -1399,7 +1401,7 @@ nel sistema. Al secondo gruppo appartengono lo \textsl{user-ID effettivo} ed il \textsl{group-ID effettivo} (a cui si aggiungono gli eventuali \textsl{group-ID supplementari} dei gruppi dei quali l'utente fa parte). Questi sono invece -gli identificatori usati nella verifiche dei permessi del processo e per il +gli identificatori usati nelle verifiche dei permessi del processo e per il controllo di accesso ai file (argomento affrontato in dettaglio in sez.~\ref{sec:file_perm_overview}). @@ -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}