devono eseguire operazioni che possono bloccarsi su più file descriptor:
mentre si è bloccati su uno di essi su di un'altro potrebbero essere presenti
dei dati; così che nel migliore dei casi si avrebbe una lettura ritardata
-inutilmente, e nel peggiore si potrebbe addirittura arrivare ad un deadlock.
+inutilmente, e nel peggiore si potrebbe addirittura arrivare ad un
+\textit{deadlock}.
Abbiamo già accennato in \secref{sec:file_open} che è possibile prevenire
questo tipo di comportamento aprendo un file in modalità
riportati sul file. Ne viene fatta una copia
privata cui solo il processo chiamante ha
accesso. Le modifiche sono mantenute attraverso
- il meccanismo del \textit{copy on write} e
+ il meccanismo del
+ \textit{copy on write}\index{copy on write} e
salvate su swap in caso di necessità. Non è
specificato se i cambiamenti sul file originale
vengano riportati sulla regione
\macro{MAP\_EXECUTABLE}& Ignorato. \\
\macro{MAP\_NORESERVE} & Si usa con \macro{MAP\_PRIVATE}. Non riserva
delle pagine di swap ad uso del meccanismo di
- \textit{copy on write} per mantenere le
+ \textit{copy on write}\index{copy on write}
+ per mantenere le
modifiche fatte alla regione mappata, in
questo caso dopo una scrittura, se non c'è più
memoria disponibile, si ha l'emissione di
La prima modalità di file locking che è stata implementata nei sistemi
unix-like è quella che viene usualmente chiamata \textit{advisory
- locking},\footnote{Stevens in APUE fa riferimento a questo argomento come al
- \textit{record locking}, dizione utilizzata anche dal manuale delle
+ locking},\footnote{Stevens in \cite{APUE} fa riferimento a questo argomento
+ come al \textit{record locking}, dizione utilizzata anche dal manuale delle
\acr{glibc}; nelle pagine di manuale si parla di \textit{discretionary file
lock} per \func{fcntl} e di \textit{advisory locking} per \func{flock},
mentre questo nome viene usato anche da Stevens per riferirsi al
che pertanto possono coesistere senza interferenze.
Entrambe le interfacce prevedono la stessa procedura di funzionamento: si
-inizia sempre con il richiere l'opportuno \textit{file lock} (un
+inizia sempre con il richiedere l'opportuno \textit{file lock} (un
\textit{exclusive lock} per una scrittura, uno \textit{shared lock} per una
lettura) prima di eseguire l'accesso ad un file. Se il lock viene acquisito
il processo prosegue l'esecuzione, altrimenti (a meno di non aver richiesto un
I primi due valori, \macro{LOCK\_SH} e \macro{LOCK\_EX} permettono di
richiedere un \textit{file lock}, ed ovviamente devono essere usati in maniera
alternativa. Se si specifica anche \macro{LOCK\_NB} la funzione non si
-bloccherà qualora il lock non possa essere aqcuisito, ma ritornerà subito con
+bloccherà qualora il lock non possa essere acquisito, ma ritornerà subito con
un errore di \macro{EWOULDBLOCK}. Per rilasciare un lock si dovrà invece usare
\macro{LOCK\_NB}.
La semantica del file locking di BSD è diversa da quella del file locking
POSIX, in particolare per quanto riguarda il comportamento dei lock nei
confronti delle due funzioni \func{dup} e \func{fork}. Per capire queste
-differenze occore prima descrivere con maggiore dettaglio come viene
+differenze occorre prima descrivere con maggiore dettaglio come viene
realizzato il file locking nel kernel.
In \figref{fig:file_flock_struct} si è riportato uno schema essenziale
L'operazione effettivamente svolta dalla funzione è stabilita dal valore
dall'argomento \param{cmd} che, come già riportato in \secref{sec:file_fcntl},
-specifica l'azione da compiere; i valori relativi al file loking sono tre:
+specifica l'azione da compiere; i valori relativi al file locking sono tre:
\begin{basedescript}{\desclabelwidth{2.0cm}}
\item[\macro{F\_GETLK}] verifica se il file lock specificato dalla struttura
puntata da \param{lock} non è bloccato da qualche altro lock: in caso
che quello che conta è solo il \acr{pid} del processo. Da questo deriva una
ulteriore sottile differenza di comportamento: dato che alla chiusura di un
file i lock ad esso associati vengono rimossi, nella semantica POSIX basterà
-chiudere un file descriptor per cancellare tutti i lock realtivi al file cui
+chiudere un file descriptor per cancellare tutti i lock relativi al file cui
esso faceva riferimento, anche se questi fossero stati creati usando altri
file descriptor che restano aperti.
(e non possono accedere direttamente alle zone di memoria riservate o alle
porte di input/output).
-Una parte del kernel, lo \textit{scheduler}, si occupa di stabilire, ad
-intervalli fissi e sulla base di un opportuno calcolo delle priorità, quale
-``processo'' deve essere posto in esecuzione (il cosiddetto \textit{preemptive
- scheduling}\index{preemptive scheduling}). Questo verrà comunque eseguito in
-modalità protetta; quando necessario il processo potrà accedere alle risorse
-hardware soltanto attraverso delle opportune chiamate al sistema che
-restituiranno il controllo al kernel.
+Una parte del kernel, lo \textit{scheduler}\index{scheduler}, si occupa di
+stabilire, ad intervalli fissi e sulla base di un opportuno calcolo delle
+priorità, quale ``processo'' deve essere posto in esecuzione (il cosiddetto
+\textit{preemptive scheduling}\index{preemptive scheduling}). Questo verrà
+comunque eseguito in modalità protetta; quando necessario il processo potrà
+accedere alle risorse hardware soltanto attraverso delle opportune chiamate al
+sistema che restituiranno il controllo al kernel.
La memoria viene sempre gestita dal kernel attraverso il meccanismo della
\textsl{memoria virtuale}\index{memoria virtuale}, che consente di assegnare a
ricorrere ad un file speciale sul filesystem, i descrittori sono del tutto
analoghi a quelli che si avrebbero con una chiamata a \func{pipe}, con la sola
differenza è che in questo caso il flusso dei dati può essere effettuato in
-emtrambe le direzioni. Il prototipo della funzione è:
+entrambe le direzioni. Il prototipo della funzione è:
\begin{functions}
\headdecl{sys/types.h}
\headdecl{sys/socket.h}
essi possono essere cancellati anche se ci sono dei processi che li stanno
utilizzando, con tutte le conseguenze (negative) del caso.
-Un'ulteriore caratterestica negativa è che gli oggetti usati nel System V IPC
+Un'ulteriore caratteristica negativa è che gli oggetti usati nel System V IPC
vengono creati direttamente dal kernel, e sono accessibili solo specificando
il relativo \textsl{identificatore}. Questo è un numero progressivo (un po'
come il \acr{pid} dei processi) che il kernel assegna a ciascuno di essi
\end{figure}
In \figref{fig:ipc_mq_fortune_server} si è riportato un estratto delle parti
-primcipali del codice del nuovo server (il codice completo è nel file
+principali del codice del nuovo server (il codice completo è nel file
\file{MQFortuneServer.c} nei sorgenti allegati). Il programma è basato su un
uso accorto della caratteristica di poter associate un ``tipo'' ai messaggi
per permettere una comunicazione indipendente fra il server ed i vari client,
usando il \acr{pid} di questi ultimi come identificativo. Questo è possibile
in quanto, al contrario di una fifo, la lettura di una coda di messaggi può
-non essere sequanziale, proprio grazie alla classificazione dei messaggi sulla
+non essere sequenziale, proprio grazie alla classificazione dei messaggi sulla
base del loro tipo.
Il programma, oltre alle solite variabili per il nome del file da cui leggere
principale (\texttt{\small 32--41}). Questo inizia (\texttt{\small 33}) con il
porsi in attesa di un messaggio di richiesta da parte di un client; si noti
infatti come \func{msgrcv} richieda un messaggio con \var{mtype} uguale a 1:
-questo è il valore usato per le richieste dato che corriponde al \acr{pid} di
+questo è il valore usato per le richieste dato che corrisponde al \acr{pid} di
\cmd{init}, che non può essere un client. L'uso del flag \macro{MSG\_NOERROR}
è solo per sicurezza, dato che i messaggi di richiesta sono di dimensione
fissa (e contengono solo il \acr{pid} del client).
preesistente. In caso di errore (ad esempio se il server non è stato avviato)
il programma termina immediatamente.
-Una volta acquistito l'identificatore della coda il client compone il
+Una volta acquisito l'identificatore della coda il client compone il
messaggio di richiesta (\texttt{\small 12--13}) in \var{msg\_read}, usando 1
per il tipo ed inserendo il proprio \acr{pid} come dato da passare al server.
Calcolata (\texttt{\small 14}) la dimensione, provvede (\texttt{\small 15}) ad
passo (\texttt{\small 17}) prima di uscire è quello di stampare a video il
messaggio ricevuto.
-Benché funzionante questa archietettura risente dello stesso inconveniente
+Benché funzionante questa architettura risente dello stesso inconveniente
visto anche nel caso del precedente server basato sulle fifo; se il client
viene interrotto dopo l'invio del messaggio di richiesta e prima della lettura
della risposta, quest'ultima resta nella coda (così come per le fifo si aveva
\label{fig:ipc_sem}
\end{figure}
-I dati mentenuti nella struttura, ed elencati in \figref{fig:ipc_sem},
+I dati mantenuti nella struttura, ed elencati in \figref{fig:ipc_sem},
indicano rispettivamente:
\begin{description*}
\item[\var{semval}] il valore numerico del semaforo.
\var{semid\_ds} ed il relativo vettore di strutture \var{sem}. Quando si
richiede una operazione viene anzitutto verificato che tutte le operazioni
possono avere successo; se una di esse comporta il blocco del processo il
-kernel creaa una struttura \var{sem\_queue} che viene aggiunta in fondo alla
+kernel crea una struttura \var{sem\_queue} che viene aggiunta in fondo alla
coda di attesa associata a ciascun insieme di semafori\footnote{che viene
referenziata tramite i campi \var{sem\_pending} e \var{sem\_pending\_last}
di \var{semid\_ds}.}. Nella struttura viene memorizzato il riferimento alle
operazioni richieste (nel campo \var{sops}, che è un puntatore ad una
struttura \var{sembuf}) e al processo corrente (nel campo \var{sleeper}) poi
-quest'ultimo viene messo stato di attesa e viene invocato lo scheduler per
-passare all'esecuzione di un altro processo.
+quest'ultimo viene messo stato di attesa e viene invocato lo
+scheduler\index{scheduler} per passare all'esecuzione di un altro processo.
Se invece tutte le operazioni possono avere successo queste vengono eseguite
immediatamente, dopo di che il kernel esegue una scansione della coda di
\end{figure}
-Come accennato in \secref{sec:intro_unix_struct} è lo \textit{scheduler} che
-decide quale processo mettere in esecuzione; esso viene eseguito ad ogni
-system call ed ad ogni interrupt,\footnote{più in una serie di altre
- occasioni. NDT completare questa parte.} (ma può essere anche attivato
-esplicitamente). Il timer di sistema provvede comunque a che esso sia invocato
-periodicamente, generando un interrupt periodico secondo la frequenza
-specificata dalla costante \macro{HZ}, definita in \file{asm/param.h}, ed il
-cui valore è espresso in Hertz.\footnote{Il valore usuale di questa costante è
- 100, per tutte le architetture eccetto l'alpha, per la quale è 1000. Occorre
- fare attenzione a non confondere questo valore con quello dei clock tick
- (vedi \secref{sec:sys_unix_time}).}
+Come accennato in \secref{sec:intro_unix_struct} è lo
+\textit{scheduler}\index{scheduler} che decide quale processo mettere in
+esecuzione; esso viene eseguito ad ogni system call ed ad ogni
+interrupt,\footnote{più in una serie di altre occasioni. NDT completare questa
+ parte.} (ma può essere anche attivato esplicitamente). Il timer di sistema
+provvede comunque a che esso sia invocato periodicamente, generando un
+interrupt periodico secondo la frequenza specificata dalla costante
+\macro{HZ}, definita in \file{asm/param.h}, ed il cui valore è espresso in
+Hertz.\footnote{Il valore usuale di questa costante è 100, per tutte le
+ architetture eccetto l'alpha, per la quale è 1000. Occorre fare attenzione a
+ non confondere questo valore con quello dei clock tick (vedi
+ \secref{sec:sys_unix_time}).}
%Si ha cioè un interrupt dal timer ogni centesimo di secondo.
-Ogni volta che viene eseguito, lo \textit{scheduler} effettua il calcolo delle
-priorità dei vari processi attivi (torneremo su questo in
+Ogni volta che viene eseguito, lo \textit{scheduler}\index{scheduler} effettua
+il calcolo delle priorità dei vari processi attivi (torneremo su questo in
\secref{sec:proc_priority}) e stabilisce quale di essi debba essere posto in
esecuzione fino alla successiva invocazione.
Esaminiamo questo risultato: una prima conclusione che si può trarre è che non
si può dire quale processo fra il padre ed il figlio venga eseguito per
primo\footnote{a partire dal kernel 2.5.2-pre10 è stato introdotto il nuovo
- scheduler di Ingo Molnar che esegue sempre per primo il figlio; per
- mantenere la portabilità è opportuno non fare comunque affidamento su questo
- comportamento.} dopo la chiamata a \func{fork}; dall'esempio si può notare
-infatti come nei primi due cicli sia stato eseguito per primo il padre (con la
-stampa del \acr{pid} del nuovo processo) per poi passare all'esecuzione del
-figlio (completata con i due avvisi di esecuzione ed uscita), e tornare
-all'esecuzione del padre (con la stampa del passaggio al ciclo successivo),
-mentre la terza volta è stato prima eseguito il figlio (fino alla conclusione)
-e poi il padre.
+ scheduler\index{scheduler} di Ingo Molnar che esegue sempre per primo il
+ figlio; per mantenere la portabilità è opportuno non fare comunque
+ affidamento su questo comportamento.} dopo la chiamata a \func{fork};
+dall'esempio si può notare infatti come nei primi due cicli sia stato eseguito
+per primo il padre (con la stampa del \acr{pid} del nuovo processo) per poi
+passare all'esecuzione del figlio (completata con i due avvisi di esecuzione
+ed uscita), e tornare all'esecuzione del padre (con la stampa del passaggio al
+ciclo successivo), mentre la terza volta è stato prima eseguito il figlio
+(fino alla conclusione) e poi il padre.
In generale l'ordine di esecuzione dipenderà, oltre che dall'algoritmo di
scheduling usato dal kernel, dalla particolare situazione in si trova la
\label{sec:proc_priority}
In questa sezione tratteremo più approfonditamente i meccanismi con il quale
-lo \textit{scheduler} assegna la CPU ai vari processi attivi. In particolare
-prenderemo in esame i vari meccanismi con cui viene gestita l'assegnazione del
-tempo di CPU, ed illustreremo le varie funzioni di gestione.
+lo \textit{scheduler}\index{scheduler} assegna la CPU ai vari processi attivi.
+In particolare prenderemo in esame i vari meccanismi con cui viene gestita
+l'assegnazione del tempo di CPU, ed illustreremo le varie funzioni di
+gestione.
\subsection{I meccanismi di \textit{scheduling}}
multitasking}) non sono i singoli processi, ma il kernel stesso a decidere
quando la CPU deve essere passata ad un altro processo. Come accennato in
\secref{sec:proc_hierarchy} questa scelta viene eseguita da una sezione
-apposita del kernel, lo \textit{scheduler}, il cui scopo è quello di
-distribuire al meglio il tempo di CPU fra i vari processi.
+apposita del kernel, lo \textit{scheduler}\index{scheduler}, il cui scopo è
+quello di distribuire al meglio il tempo di CPU fra i vari processi.
La cosa è resa ancora più complicata dal fatto che con le architetture
multi-processore si deve anche scegliere quale sia la CPU più opportuna da
processo viene eseguito per la prima volta e diminuito progressivamente ad
ogni interruzione del timer.
-Quando lo scheduler viene eseguito scandisce la coda dei processi in stato
-\textit{runnable} associando, sulla base del valore di \var{counter}, un peso
-a ciascun processo in attesa di esecuzione,\footnote{il calcolo del peso in
- realtà è un po' più complicato, ad esempio nei sistemi multiprocessore viene
- favorito un processo che è eseguito sulla stessa CPU, e a parità del valore
- di \var{counter} viene favorito chi ha una priorità più elevata.} chi ha il
-peso più alto verrà posto in esecuzione, ed il precedente processo sarà
-spostato in fondo alla coda. Dato che ad ogni interruzione del timer il
-valore di \var{counter} del processo corrente viene diminuito, questo assicura
-che anche i processi con priorità più bassa verranno messi in esecuzione.
+Quando lo scheduler\index{scheduler} viene eseguito scandisce la coda dei
+processi in stato \textit{runnable} associando, sulla base del valore di
+\var{counter}, un peso a ciascun processo in attesa di esecuzione,\footnote{il
+ calcolo del peso in realtà è un po' più complicato, ad esempio nei sistemi
+ multiprocessore viene favorito un processo che è eseguito sulla stessa CPU,
+ e a parità del valore di \var{counter} viene favorito chi ha una priorità
+ più elevata.} chi ha il peso più alto verrà posto in esecuzione, ed il
+precedente processo sarà spostato in fondo alla coda. Dato che ad ogni
+interruzione del timer il valore di \var{counter} del processo corrente viene
+diminuito, questo assicura che anche i processi con priorità più bassa
+verranno messi in esecuzione.
La priorità di un processo è così controllata attraverso il valore di
\var{nice}, che stabilisce la durata della \textit{time-slice}; per il
shell cui si sia assegnata la massima priorità assoluta, in modo da poter
essere comunque in grado di rientrare nel sistema.
-Quando c'è un processo con priorità assoluta lo scheduler lo metterà in
-esecuzione prima di ogni processo normale. In caso di più processi sarà
-eseguito per primo quello con priorità assoluta più alta. Quando ci sono più
-processi con la stessa priorità assoluta questi vengono tenuti in una coda
-tocca al kernel decidere quale deve essere eseguito.
+Quando c'è un processo con priorità assoluta lo scheduler\index{scheduler} lo
+metterà in esecuzione prima di ogni processo normale. In caso di più processi
+sarà eseguito per primo quello con priorità assoluta più alta. Quando ci sono
+più processi con la stessa priorità assoluta questi vengono tenuti in una coda
+tocca al kernel decidere quale deve essere eseguito.
Il meccanismo con cui vengono gestiti questi processi dipende dalla politica
di scheduling che si è scelto; lo standard ne prevede due:
\textit{delivered}) quando viene eseguita l'azione per esso prevista, mentre
per tutto il tempo che passa fra la generazione del segnale e la sua consegna
esso è detto \textsl{pendente} (o \textit{pending}). In genere questa
-procedura viene effettuata dallo scheduler quando, riprendendo l'esecuzione
-del processo in questione, verifica la presenza del segnale nella
-\var{task\_struct} e mette in esecuzione il gestore.
+procedura viene effettuata dallo scheduler\index{scheduler} quando,
+riprendendo l'esecuzione del processo in questione, verifica la presenza del
+segnale nella \var{task\_struct} e mette in esecuzione il gestore.
In questa semantica un processo ha la possibilità di bloccare la consegna dei
segnali, in questo caso, se l'azione per il suddetto segnale non è quella di
ignorarlo).
Normalmente l'invio al processo che deve ricevere il segnale è immediato ed
-avviene non appena questo viene rimesso in esecuzione dallo scheduler che
-esegue l'azione specificata. Questo a meno che il segnale in questione non sia
-stato bloccato prima della notifica, nel qual caso l'invio non avviene ed il
-segnale resta \textsl{pendente} indefinitamente. Quando lo si sblocca il
-segnale \textsl{pendente} sarà subito notificato.
+avviene non appena questo viene rimesso in esecuzione dallo
+scheduler\index{scheduler} che esegue l'azione specificata. Questo a meno che
+il segnale in questione non sia stato bloccato prima della notifica, nel qual
+caso l'invio non avviene ed il segnale resta \textsl{pendente}
+indefinitamente. Quando lo si sblocca il segnale \textsl{pendente} sarà subito
+notificato.
Si ricordi però che se l'azione specificata per un segnale è quella di essere
ignorato questo sarà scartato immediatamente al momento della sua generazione,
nanosecondo, la precisione di \func{nanosleep} è determinata dalla risoluzione
temporale del timer di sistema. Perciò la funzione attenderà comunque il tempo
specificato, ma prima che il processo possa tornare ad essere eseguito
-occorrerà almeno attendere il successivo giro di scheduler e cioè un tempo che
-a seconda dei casi può arrivare fino a 1/\macro{HZ}, (sempre che il sistema
-sia scarico ed il processa venga immediatamente rimesso in esecuzione); per
-questo motivo il valore restituito in \param{rem} è sempre arrotondato al
-multiplo successivo di 1/\macro{HZ}.
+occorrerà almeno attendere il successivo giro di scheduler\index{scheduler} e
+cioè un tempo che a seconda dei casi può arrivare fino a 1/\macro{HZ}, (sempre
+che il sistema sia scarico ed il processa venga immediatamente rimesso in
+esecuzione); per questo motivo il valore restituito in \param{rem} è sempre
+arrotondato al multiplo successivo di 1/\macro{HZ}.
In realtà è possibile ottenere anche pause più precise del centesimo di
secondo usando politiche di scheduling real time come \macro{SCHED\_FIFO} o