Sei sulla pagina 1di 69

Programmazione

on orrente su sistemi Unix


Lu a Abeni, Antonino Casile
S uola Superiore di Studi Universitari e di Perfezionamento
S. Anna

Capitolo 1
Introduzione
1.1 Sistemi Multiprogrammati
Un sistema multiprogrammato e un sistema he permette l'ese uzione simultanea di piu programmi, he a loro volta possono essere omposti da
vari pro essi o thread. Questo puo permettere a piu utenti di a edere al
sistema ontemporaneamente, o ad uno stesso utente di eseguire piu programmi simultaneamente (aumentando l'utilizzabilita del sistema), o ad un
singolo programma di s omporre la propria attivita in un insieme di attivita
on orrenti (sempli ando la struttura logi a del programma).
Si passa os dal on etto di programma sequenziale al on etto di programma parallelo, utilizzando un paradigma di programmazione on orrente.
Questo, se da un lato permette di strutturare meglio i programmi, dall'altro ri hiede una parti olare attenzione a problemi di sin ronizzazione fra le
varie attivita (spesso l'ese uzione di un programma on orrente risulta non
deterministi a) e una stretta interazione on il sistema operativo.
Prima di passare a vedere quali sono le funzionalita o erte dal sistema
operativo a supporto della programmazione on orrente e ome utilizzarle, e
bene andare a de nire in maniera piu formale al uni on etti di base.

1.1.1 Con etti Fondamentali


Come a ennato, un sistema on orrente permette l'ese uzione ontemporanea di varie attivita tramite multipro essing o multithreading: andiamo
allora a apire osa si intende on questi on etti.
Si de nis e algoritmo il pro edimento logi o seguito per risolvere un determinato problema. Tale algoritmo puo essere des ritto tramite un opportuno
formalismo ( hiamato linguaggio di programmazione), in modo da poter esse1

re eseguito su un elaboratore. Tale odi a di un algoritmo, espressa tramite


un linguaggio di programmazione e detta programma.
L'ese uzione di un programma puo essere di tipo sequenziale, oppure puo
essere omposta da piu attivita he eseguono in parallelo. In questo se ondo aso (programma on orrente), le varie attivita he eseguono in parallelo
devono essere opportunamente sin ronizzate fra loro e ne essitano di omuni are e ooperare al ne di portare a termine il proprio s opo. Il modo in
ui tali attivita on orrenti ooperano e omuni ano le distingue fra pro essi
e thread.
I pro essi sono aratterizzati da un insieme di risorse private, fra ui
gli spazi di memoria, per ui un pro esso non puo a edere allo spazio di
memoria di un altro per omuni are on esso. Per questo motivo, i pro essi si
sin ronizzano e omuni ano fra loro tramite s ambio di messaggi. Vi eversa i
thread ondividono varie risorse, fra ui lo spazio di memoria, per ui possono
omuni are tramite memoria omune, e sin ronizzarsi tramite semafori.

1.2 Note generali su Unix


Per poter reare pro essi o thread, per sin ronizzarli e per farli omuni are, il programma deve ri hiedere tali funzioni al sistema operativo, tramite
spe i he hiamate di sistema, o system all (brevemente, sys all). Tali sys all dipendono dal sistema operativo, ed in questa sede faremo riferimento
ai sistemi operativi Unix-like (Linux in parti olare), se ondo lo standard
POSIX.
Ogni system all ritorna un intero, he in aso di valore negativo segnala
una ondizione di errore: e allora buona norma veri are he tale valore sia
maggiore o uguale a 0 ogni volta he si invo a una sys all, utilizzando per
esempio un pezzo di odi e simile al seguente.

int res;
res = sys all(: : :);
if (res < 0) f
perror("Error alling sys all");
exit(-1);

g
=

 Program ontinues here... 

La funzione perror() invia sullo standard error (tipi amente, il video) un


messaggio di errore omposto dalla stringa passata ome parametro seguita
da una des rizione del motivo per ui l'ultima sys all hiamata e fallita.
I prototipi delle system all e le de nizioni di ostanti e strutture dati ad
esse ne essarie sono ontenuti in al uni header le di sistema (generalmente
nella dire tory /usr/in lude/sys); per poter utilizzare una determinata sys all bisogna quindi in ludere gli adeguati header. I sistemi Unix mettono a
disposizione il omando man, he fornis e una breve des rizione della semanti a di una sys all spe i ata, assieme a spe i arne la sintassi ed a elen are
gli header da in ludere per utilizzare tale sys all. Riferirsi sempre alle

manpage per in ludere i le orretti ed utilizzare una sys all on


la giusta sintassi.

Capitolo 2
Multipro essing
Unix e un sistema operativo nato pensando ad un paradigma di programmazione a s ambio di messaggi, in ui le varie attivita parallele non ondividono
spazi di memoria in omune. La multiprogrammazione in ambiente Unix e
quindi tradizionalmente basata sul on etto di pro esso, an he se su essive
estensioni hanno introdotto on etti nuovi ome le zone di memoria ondivisa
o i pro essi multithread.
In ogni aso, se non altro per motivazioni di ordine stori o, andiamo
ad analizzare la programmazione on orrente in ambiente Unix omin iando
proprio dalla programmazione multipro esso.

2.1 Pro essi


Un pro esso e in Unix un usso di ese uzione he puo essere eseguito in
maniera on orrente e aratterizzato da :

un orpo { Ossia il odi e he viene eseguito


uno spazio di memoria privato { Composto a sua volta da :
una zona dati { Suddivisa in dati non inizializzati, o BSS, e dati
inizializzati.

uno sta k
uno heap { Ossia una zona di memoria in ui il pro esso puo allo are
memoria dinami a, tramite, per esempio, la funzione mallo .

una tabella di des rittori di le

un des rittore di pro esso { Il Pro ess IDenti ator, o piu brevemente

PID, e tipi amente un numero he identi a univo amente il pro esso


all'interno del sistema.

uno stato privato

2.1.1 Identi atori di pro esso


Come detto, ogni pro esso Unix e aratterizzato da un PID privato, he e
l'entita on ui il sistema identi a tale pro esso. Tale PID e uni o (non
possono esistere due pro essi on lo stesso PID); in prati a si puo pensare
al PID ome ad un \nome" he il sistema assegna ad un pro esso alla sua
reazione e he verra utilizzato in seguito per riferirsi ad esso.
Ogni pro esso e in grado di onos ere il proprio PID tramite la sys all
getpid(), he non prende parametri e restituis e il PID del pro esso hiamante (e da notare he getpid() non puo fallire per nessun motivo, quindi
non ritornera mai un valore negativo).
Tramite la sys all getppid() e inve e possibile ottenere il PID del pro esso he ha reato il pro esso hiamante (pro esso padre); se il pro esso
padre e terminato la getppid() ritornera 1, he e per onvenzione il PID del
pro esso di sistema init (il primo pro esso ad essere reato). Vedremo nella
Sezione 2.1.5 il per he di questo omportamento.

2.1.2 Creazione di nuovi pro essi

E possibile reare un nuovo pro esso tramite la primitiva fork(), he rea


un pro esso \ glio" del pro esso he la invo a, assegnandogli un nuovo PID e
opiando nello stato del pro esso glio e nelle sue strutture private lo stato e
le strutture private del padre. Il pro esso glio avra quindi un proprio spazio
di memoria privato, il quale ontiene una opia (sia per quanto on erne i
dati he per quanto on erne il odi e) dello spazio di memoria del padre.
Per questo motivo il glio \vedra" tutte le variabili ol valore he avevano
nel padre prima dell'ese uzione della fork(), ma potra modi are solo la
propria opia lo ale delle variabili.
La sintassi della fork() e la seguente:
#in lude

<

unistd.h>

pid t fork(void);

Sia padre he glio (in aso di orretta terminazione della sys all) ontinueranno la loro ese uzione da dopo la fork() (ri ordiamo he il orpo del
glio e una opia del orpo del padre): tale sys all verra quindi hiamata una
volta e ritornera due volte (una nel pro esso padre ed una nel pro esso glio).
I due pro essi possono essere distinti onsultando il valore di ritorno della
fork(): il padre otterra il PID del pro esso glio, mentre il glio otterra 0.
Un utilizzo della fork() puo quindi essere il seguente:
pid t res;
:::

res = fork();
if (res < 0) f
perror("fork failed");
exit(-1);

if (res == 0) f
= We are in the hild pro ess =

:::

else f

 We are in the father pro ess 

:::

Come si vede, una soluzione di questo tipo non e pero molto prati a, in
quanto obbliga a s rivere il odi e del pro esso glio e quello del pro esso
padre nei due blo hi then e else di un'istruzione if. Una soluzione per
rendere il odi e piu leggibile puo essere la seguente:
pid t = res;
:::

res = fork();
if (res < 0) f
perror("fork failed");
exit(-1);

if (res == 0) f
= We are in the hild pro ess =
:::

g
=

exit(1);

 We are in the father pro ess 

:::

Per evitare di dover s rivere il orpo dei due pro essi nello stesso le,
Unix mette a disposizione la famiglia di funzioni exe ().

2.1.3 Cari amento di un programma in memoria


Come detto, la primitiva fork() permette di reare un nuovo pro esso, opiando nel suo spazio di memoria lo spazio di memoria del pro esso reante, e questo obbliga a inserire il odi e ed i dati di tutti i pro essi nello
stesso eseguibile, on onseguente aumento delle dimensioni degli eseguibili,
ompli azione della struttura del programma e perdita di essibilita.
Tale problema puo essere risolto sostituendo il orpo del pro esso glio
dopo la fork() ( ari ando nello spazio di memoria del glio un nuovo odi e e
dei nuovi dati). Per permettere questo, Unix mette a disposizione la famiglia
di funzioni exe (), he sovras rive i dati, il segmento BSS ed il odi e di un
pro esso on un'immagine prelevata da dis o.
 da notare he una funzione della famiglia exe () non rea un nuovo
E
pro esso, ma sempli emente modi a lo spazio di memoria del pro esso he
la hiama; per questo motivo, in aso di orretto funzionamento la
exe () non ritorner
a. E quindi hiaro he se la sys all ritorna un valore
al hiamante, tale valore e ne essariamente minore di 0 (tipi amente -1).
Parlando a livello intuitivo, una exe () e paragonabile ad una jmp nel odi e
di un altro le oggetto, he rimpiazza il primo nella memoria privata del
pro esso.
La famiglia di funzioni exe e omposta da exe l(), exe lp(), exe le(),
exe v(), exe vp() ed exe ve(); tipi amente, tutte tali funzioni sono basate sulla exe ve(), ostituendo dei meri front-end verso di essa. Per questo
motivo, in questa sede tratteremo solo la exe ve(), rimandando ai man le
(man exe ) per la sintassi e la semanti a delle altre sys all della famiglia.
#in lude

<

unistd.h>

int exe ve ( onst har *filename, har * onst argv [, har


* onst envp[)

Argomenti in ingresso :
7

lename { Nome del le da ari are in memoria


argv { Lista dei parametri
argv { Lista delle variabili di ambiente.
Valore restituito :

-1 { In aso di errore
Non ritorna { In aso di orretto funzionamento.
Cari a in memoria l'eseguibile indi ato dal parametro filename (nella
dire tory orrente), passandogli ome parametri la lista di stringhe indi ata
da argv, e reandogli un ambiente on de nite le variabili spe i ate da envp.
Il parametro argv e un array di puntatori a stringhe, ognuna delle quali
spe i a un parametro passato al programma. L'ultimo elemento dell'array
deve essere NULL (0). Per onvenzione, il primo parametro deve sempre
essere il nome dell'eseguibile.
E o quindi ome usare la exe ve():

har = argv[5;
int res;
:::

argv[0 = "ls";
argv[1 = "/home";
argv[2 = NULL;
res = exe ve("/bin/ls", argv, NULL);
if (res < 0) f
perror("exe ve failed");
exit(-1);

 We annot be here!!! 

printf("Pani o!!!");
exit(-1);

2.1.4 Un esempio
Riportiamo di seguito un esempio funzionante di quanto detto sopra: in
prati a, un pro esso padre forka un glio e ari a un nuovo orpo per il
8

pro esso glio prelevandolo da un altro eseguibile. A questo punto il padre


attende un se ondo e poi termina, mentre il glio non fa altro he noti are
la sua esistenza indi ando i parametri ri evuti in ingresso.

 Esempio di exe ....


 Quando un pro esso glio nis e al padre viene
 inviata la SIGCHLD. In questo esempio viene quindi
 mostrato an he l'uso della SIGCHLD ....


 Quesito : Per he' la prima printf del pro esso padre


 a volte avviene per due volte????
 Spiegare il omportamento quando inve e l'output
 e' rediretto su le .....


#in lude <sys/types.h>


#define USE POSIX
#in lude <stdio.h>
#in lude <unistd.h>

int main (void)

pid t hild;

 Array usato nell'esempio per passare i parametri


 al task generato tramite una exe 
har array[3 = f"arg1", "arg2", "arg3"g;

 A questo punto parto on la fork() 


if (( hild = fork()) 0) f perror("Errore
=

exit(-1);

<

nella fork");

else if ( hild == 0) f
= A questo punto sono nel glio =

printf("Pro esso figlio ... padre = %dnn", getppid());

 S ommentare la linea seguente e ommentare quella


 an ora dopo per vedere le di erenze far i due tipi
 di passaggio di parametri alla exe .

if (exe lp(" glio", " glio", "arg1", "arg2") 0)f
if (exe vp("figlio", array) 0)f
=

<

<

perror("E' fallita la exe ");


exit(-1);

else f

 Sono nel pro esso padre 

printf("Pro esso padre .... pid t = %dnn", getpid());


sleep(1);

return 0;

g
 Programma he viene eseguito tramite la primitiva
 exe . Si limita a stampare a video i parametro he gli
 sono passati in input

=

#in lude
#in lude

stdio.h>
<unistd.h>
<

int main(int arg , har argv[)

int i;

printf("Programma 'exe ed' - Pro esso padre = %dnn",


getppid());
for (i=0; i<arg ; i++) f
printf("Argomento %d = %s nn", i, argv[i);

return 0;

2.1.5 Terminazione dei pro essi


Abbiamo visto ome sia possibile reare un nuovo pro esso tramite la sys all
fork, o upiamo i ora della terminazione dei pro essi.
Un pro esso puo terminare espli itamente tramite la sys all exit(), o la
library all exit(), o impli itamente quando l'ese uzione del main termina
( io puo avvenire per e il usso di ese uzione e arrivato alla ne della funzione
main() o per he essa ha e ettuato una return). Questo signi a he il
runtime C hiama la funzione main() all'in ir a in questo modo:

 Preparazione di argv e arg 

10

res = main(arg , argv);


exit(res);
La orretta terminazione di un pro esso presuppone he esso ritorni un
valore tramite la funzione exit() o l'istruzione return; tale valore puo essere re uperato dal pro esso padre per determinare l'esito dell'ese uzione del
glio. Per fare questo, il padre ha bisogno di sin ronizzarsi ol glio, attendendo la sua terminazione tramite una primitiva della famigla wait(). La
piu sempli e di tali funzioni e la wait(), he permette ad un pro esso di
attendere la terminazione di uno dei suoi pro essi gli, re uperando il valore di us ita ritornato dalla funzione exit(). E o quindi ome funziona la
wait():

se il pro esso hiamante non ha gli, la sys all fallis e, ritornando un


odi e di errore (< 0);

se il pro esso hiamante ha reato dei pro essi gli on fork(), ma


nessuno di essi e an ora terminato, la sys all si blo a;

se il pro esso hiamante ha dei gli he sono gia terminati, ritorna il


PID ed il valore di us ita del primo pro esso glio terminato.

Il prototipo della sys all e il seguente:


#in lude
#in lude

sys/types.h>
sys/wait.h>

<
<

pid t wait(int *status);

Argomenti in ingresso :

status { Puntatore ad un intero in ui registrare lo stato di is ita del pro esso


glio.

Valore restituito :

-1 { Se il pro esso non ha gli


pid { Il PID di un pro esso glio terminato.
Un esempio tipi o di utilizzo della wait() e il seguente:

11

 This sys all an blo k 

res = wait(&status);

if (res < 0) f
= No hildren to wait for =

perror("Wait failed");
exit(-1);

 Now the value returned from the hild is in status 

:::

Il valore di ritorno di un pro esso in generale risponde ad al une onvenzioni di base: ome detto, un valore negativo e segno di una terminazione
anomala. Per poter analizzare tali valori di ritorno, il sistema mette a disposizione al une ma ro: WIFEXITED, WIFSIGNALED e WIFSTOPPED. La ma ro
WIFEXITED appli ata al valore status ritorna 1 se il pro esso glio e terminato orrettamente (ed in questo aso, WEXITSTATUS ottiene il valore >= 0
ritornato dal glio). Le ma ro WIFSIGNALED e WIFSTOPPED indi ano inve e
se il pro esso glio e stato interrotto da un segnale o e stato blo ato; in questo aso, le ma ro WTERMSIG e WSTOPSIG permettono di ottenere il numero
del segnale he ha terminato o blo ato il pro esso (vedi Sezione 2.2.1. In
al uni sistemi e an he de nita la ma ro WSTOPSIG, per vedere se e avvenuto
un ore dump, on onseguente generazione del le ore. Si invita a onsultare le man page per avere maggiori informazioni su tali ma ro e su altre
eventualmente de nite.
Per permettere la sin ronizzazione ed il passaggio del valore di ritorno
ome spe i ato pre edentemente, il kernel deve mantenere informazioni relative ai pro essi an he dopo la loro terminazione, n he il loro pro esso
padre non e ettua una wait(). Questi pro essi inattivi he ontinuano ad
o upare posto nella tabella dei pro essi, detti pro essi zombie, possono ausare un inutile spre o di des rittori dei pro essi. Per tale motivo, e sempre
bene e ettuare una wait() su tutti i pro essi gli reati.
Quando un pro esso termina, tutti i suoi pro essi gli ( ompresi quelli
he sono nello stato di zombie) vengono ereditati dal pro esso di sistema init
(in prati a, init diventa automagi amente il padre di tutti i pro essi il ui
padre termina). Questo me anismo permette di evitare la reazione di un
e essivo numero di pro essi zombie, in quanto quando un pro esso glio di
12

init termina, init e ettua automati amente una wait (nella Sezione 2.2.1,

parlando dei segnali, vedremo ome e possibile fare io).


Se nessuno dei gli e terminato, la wait() ha un omportamento blo ante; esistono inve e altre sys all he prevedono semanti he non blo anti.
In parti olare, la sys all waitpid() permette di spe i are di attendere la
terminazione un determinato pro esso, ed inoltre a etta in input un ag
he spe i a un omportamento non blo ante. Altre due sys all, wait3()
e wait4(), derivanti dai sistemi BSD, sono sostanzialmente analoghe, ambiando leggermente la sintassi. Si ra omanda di riferirsi alle manpage per
avere informazioni su sintassi e semanti a di waitpid(), wait3() e wait4().

13

2.2 Sin ronizzazione fra pro essi


Abbiamo visto ome reare e terminare pro essi, ome ari are il orpo e i
dati di un pro esso da un'immagine su dis o e ome sin ronizzare un pro esso on la terminazione dei suoi gli re uperando i valori da essi ritornati;
per produrre un lavoro utile, i pro essi he ompongono un programma on orrente devono pero sin ronizzarsi e omuni are in maniera meno banale.
In questa sezione vedremo ome vari pro essi possono sin ronizzarsi fra loro
tramite signal e s ambiarsi dati in maniera sin rona o asin rona.

2.2.1 I segnali
La sin ronizzazione fra pro essi ooperanti puo essere ottenuta permettendo
ad al uni pro essi di attendere eventi asin roni generati da altri, sin ronizzandosi on essi. I segnali sono il me anismo utilizzato dai sistemi Unix per
rendere possibile tale tipo di sin ronizzazione: in prati a, un segnale e un
evento asin rono, talvolta designato ol nome di interrupt software, he un
pro esso puo inviare ad un altro pro esso (o ad un gruppo di pro essi). Il
destinatario puo reagire in maniera diversa all'arrivo di tale evento :
 ignorare il segnale,




eseguire un signal handler spe i ato dal pro esso,





blo are un segnale (mas herarlo),

us ire (abortendo).
Un segnale he non ha an ora generato una delle tre azioni sopra e detto
pendente.
Nelle prossime sezioni saranno introdotte le primitive di nu leo per permettere a un pro esso di:
 spe i are l'azione da intraprendere in risposta ad un segnale,
ontrollare se i sono segnali pendenti,

inviare segnali ad un altro pro esso (o far s attare segnali dopo un erto
tempo).
Purtroppo, la semanti a di tali funzioni (e la semanti a dei segnali stessi)
dipende fortemente dal sistema, per ui si rimanda alle man page di ogni
spe i a implementazione di Unix per una des rizione pre isa. In questa
sede, i limiteremo a spiegare la sintassi e la semanti a delle funzioni previste
dallo standard POSIX. In parti olare, an ora una volta, gli esempi presentati
sono stati provati su un sistema Linux.
14

2.2.2 Spe i a di un signal handler


Ad ogni pro esso e assegnata una tabella dei segnali, he fa parte del suo stato privato e spe i a l'azione he il pro esso deve eseguire in risposta ad un
segnale. Al momento della reazione del pro esso, ad ogni segnale e assegnato
un omportamento di default, he puo essere ambiato tramite un'apposita
system all. Stori amente, i sistemi Unix prevedono la funzione signal(),
he permette di assegnare un signal handler (tipi amente, una funzione del
tipo void f(int s)) al segnale spe i ato e ritorna il signal handler pre edentemente assegnato al segnale, sempre sotto forma di un puntatore a una
funzione.
#in lude

<

signal.h>

void (*signal(int signum, void (*handler)(int)))(int);

Argomenti in ingresso :

signum { Identi ativo della signal di ui si vuole installare l'handler.


handler { Puntatore alla funzione he ontiene il odi e dell'handler, oppure

una delle due ma ro prede nite : SIG IGN, he ha il signi ato di


ignorare la signal spe i ata; SIG DFL, he installa l'handler di default
della signal spe i ata.

Valore restituito :

SIG ERR { In aso di errore.


old signal { Puntatore alla pre edente signal.
Di seguito riportiamo un pi olo esempio di ome settare un nuovo handler per la signal di identi ativo SIGUSR1 :
#in lude

<

signal.h>

 Forward de laration del orpo dell'handler =


stati void handler(int signo);
=

int main(void)

:::

 Installo l'handler e ontrollo il risultato 

15

if (signal(SIGUSR1, handler) == SIG ERR) f

perror("Errore nell'allo azione delle SIGUSR1 nn");


exit(-1);

g
g

:::

Il omportamento di questa sys all non e pero standard, in quanto in


al uni sistemi il signal handler viene resettato al valore di default quando
una signal s atta, mentre su altri rimane settato al valore spe i ato dalla
signal an he in seguito allo s attare del segnale (Linux permette entrambe
le semanti he, sempli emente linkando librerie di erenti; al solito, riferirsi
alla man page). Per evitare questo problema, onviene utilizzare la sys all
siga tion(), he ha un omportamento standard.
#in lude

<

signal.h>

int siga tion(int signum, onst stru t siga tion *a t,


stru t siga tion *old t);

Argomenti in ingresso :

signum { Identi ativo della signal di ui si vuole installare l'handler.


a t { Puntatore alla struttura ontenente le informazioni riguardanti il nuovo
handler.

olda t { Puntatore alla struttura in ui, se diverso da NULL, vengono salvate


le informazioni riguardanti il ve hio handler.

Valore restituito :
0 { In aso di su esso.
1 { In aso di errore
Come si nota le des rizioni dei signal handler sono fornite tramite la
struttura di tipo stru t siga tion, os de nita:

16

#in lude

<

signal.h>

stru t siga tion f


void (*sa handler)();
sigset t sa mask;
int sa flags;

Signi ato dei vari ampi :

sa handler { Puntatore al odi e del nuovo handler.


sa mask { Mas hera dei signal he devono essere blo ati durante l'ese uzione dell'handler.

sa ags { Flags per spe i are il omportamento del sistema durante l'ese uzione dell'handler.

Per installare un nuovo handler vanno quindi fatti i seguenti passi :




s rivere il relativo odi e sotto forma di funzione C,


di hiarare un'istanza di struttura siga tion e settarne in maniera
opportuna i ampi,

hiamare la sys all opportuna.

Nell'esempio seguente si mostra ome installare un handler usando la


semanti a della siga tion() :
# in lude

signal.h>

<

 Forward de laration del orpo dell'handler 

stati void handler (int signo);


int main(void)

stru t siga tion nuova, ve hia;


:::

nuova.sa handler = handler;

 Tutte le signal sono mas herate durante


 l'ese uzione dell'handler 

sigemptyset(&nuova.sa mask);

17

nuova.sa ags = 0;

 Installo l'handler e ontrollo il risultato 

if (siga tion(SIGUSR1, &nuova, &ve hia) == -1) f

perror("Errore nell'allo azione delle SIGUSR1 nn");


exit(-1);

g
g

:::

Nell'esempio sopra e introdotta la funzione sigemptyset(). Questa funzione fa parte di tutta una famiglia di funzioni he servono per la gestione dell'annidamento dei signal. Puo apitare, infatti, he s atti un signal
mentre il task sta eseguendo l'handler relativo ad un altro. In questo aso
il programmatore ha la s elta, ome per le interrupt hardware, di servire il
nuovo signal e quindi ritornare al ve hio, oppure ignorare il nuovo signal
s attato no a he non ha nito di servire quello attuale.
Si puo spe i are tale omportamento per ogni handler installato e questo
e' fatto tramite tutta una famiglia di funzioni di ui la sigemptyset() e solo
uno degli appartanenti. Tutte queste funzioni operano su una struttura di
tipo stru t sigset t he ontiene tutte le informazioni relative ai signal da
mas herare e he, ome visto, e uno dei ampi della siga tion.
La struttura stru t sigset t e un osiddetto tipo opa o: in altre parole,
un programma non deve modi are direttamente il valore dei suoi ampi ( he
il programmatore non dovrebbe nean he onos ere), ma puo a edere ad essa
solo attraverso un insieme di ma ro o funzioni he de nis ono l'em interfa ia
del tipo. In parti olare, l'interfa ia della stru t sigset t e la seguente:
#in lude
int
int
int
int
int

<

signal.h>

sigemptyset(sigset t *set)
sigfillset(sigset t *set)
sigaddset(sigset t *set, int signum)
sigdelset(sigset t *set, int signum)
sigismember( onst sigset t *set, int signum)

Des rizione delle funzioni :

sigemptyset { Modi a la struttura set in modo he tutti i signal siano


mas herate.

sig llset { Modi a la struttura set in modo he tutti i signal siano attivi.
18

sigaddset { Aggiunge alla struttura set il signal signum, ossia lo rende


attivo.

sigdelset { Mas hera il signal signum nella struttura set.


sigismember { Controlla se il signal signum e attivato o mas herato nella
struttura signum.

Valore restituito :
0 { In aso di su esso.
1 { In aso di errore.
La funzione sigismember() fa e ezione ed restituis e i seguenti valori:
1 { Se il signal signum e attivato nella struttura set.
0 { Se il signal signum e mas herato nella struttura set.
1 { In aso di errore.

2.2.3 Un esempio
In questa sezione esamineremo un sepli e esempio di uso delle signal. Il
programma rea due task. Alternativamente ognuno dei due task invia un
signal all'altro e quindi sospende temporaneamente la sua ese uzione, tramite
l'uso della funzione pause(), in attesa di essere risvegliato dalla signal inviata
dall'altro.

 Breve esempio di uso di fork() e signal() he genera


 due task he si s ambiano alternativamente un signal
 a intervalli "regolari di tempo.
 Quesiti:
 1) Per he' e' stato inseria la linea
 if (se onds == 0)
 2) Per he' an he on questa linea 'e' un aso
 riti o di deadlo k e quale e'?


#in lude <sys/types.h>


#define USE POSIX
#in lude <unistd.h>
#in lude <stdio.h>

19

#in lude
#in lude

<
<

signal.h>
time.h>

 Forward de laration =
stati void handler (int signo);
=

int se onds;
int main(void)

pid t hild, father;

stru t siga tion nuova, ve hia;


se onds = 0;

 Utilizzo la sintassi Posix per settare un handler




 Inizializzo i ampi della struttura siga tion 


 indirizzo dell'handler 

nuova.sa handler = handler;

 Quando l'handler s atta mas hera TUTTI i signal 

sigemptyset(&nuova.sa mask);

 Nessun ag viene settato 

nuova.sa ags = 0;

 Installo l'handler prima della fork in modo he


 sia ereditato dal pro esso glio!!!


siga tion(SIGUSR1, &nuova, &ve hia);

if (( hild = fork()) < 0) f

perror("Errore nella fork ....


exit(-1);

nn");

else if ( hild == 0) f
= A questo punto sono nel task glio =

 Ottengo il pid del padre 

father = getppid();
for(;;) f
sleep(1);

20

kill(father, SIGUSR1);
pause();
printf("Se ondi tras orsi (figlio) %d -- time =
%dnn", 2se onds,
(int)time(NULL));

else f
if (se onds == 0)
pause();

= A questo punto sono nel padre =


for(;;)f

printf("Se ondi tras orsi (padre) %d -- time = %d


nn", 2se onds-1,
(int)time(NULL));
sleep(1);
kill( hild, SIGUSR1);
pause();

 Handler del signal =


stati void handler(int signo)
=

f
g

se onds++;

21

2.3 Comuni azione fra pro essi


Dopo aver visto ome vari pro essi ooperanti possono sin ronizzarsi fra loro, vediamo ome possono omuni are. Tipi amente, le primitive di Inter
Pro ess Comuni ation (IPC) possono essere atalogate se ondo vari riteri:
in parti olare, seguendo due riteri fra loro ortogonali, si puo distinguere
fra omuni azione sin rona (primitive blo anti) o asin rona (primitive non
blo anti) e diretta (il pro esso destinatario e la sorgente sono spe i ati
direttamente nelle operazioni di IPC) o indiretta (le omuni azioni non avvengono direttamente, attraverso un'entita intermedia, talvolta detta porta
o un anale).
Nei sistemi Unix, nati proprio supportando il paradigma di programmazione a s ambio di messaggi, la omuni azione avviene attraverso primitive
IPC indirette, generalmente asin rone.

2.3.1 Comuni azione tramite pipe


La forma di IPC piu sempli e he Unix mette a disposizione e quella ottenibile tramite pipe. Un pipe e un anale monodirezionale di omuni azione
asin rona, a essibile tramite le primitive di I/O standard messe a disposizione da Unix. Ad ogni pipe sono asso iati due des rittori di le, uno per
l'input ed uno per l'output: i dati s ritti sul des rittore di input (tramite
la sys all write()) vengono bu erizzati all'interno del pipe e possono essere
letti dal des rittore di us ita (tramite la read()).
Poi he le primitive di output (write()) appli ate ad un pipe sono non
blo anti (la omuni azione e asin rona), un pipe deve ontenere un bu er in
ui depositare i dati s ritti sulla pipe. Tale bu er non e pero in nito, ma ha
dimensione PIPE BUF ( he e una ostante de nita in <unistd.h>): quando
tale bu er e pieno (dopo he PIPE BUF byte sono stati s ritti sulla pipe e
nessuno e stato letto) la write() diventa blo ante.
Un pipe puo essere reato tramite la sys all pipe(), quindi viene a eduto
ome se fosse una oppia di normali le (uno aperto in sola lettura ed uno
aperto in sola s rittura). Il prototipo della sys all pipe() e il seguente:
#in lude

<

unistd.h>

int pipe(int fd[2);

Argomenti in ingresso :

22

fd { Array di due interi in ui la primitiva ritorna gli identi atori dei due
estremi del pipe.

Valore restituito :

0 { In aso di su esso
-1 { In aso di errore.
La sys all rea un pipe e allo a due des rittori di le nella tabella dei le
aperti dal pro esso hiamante, ritornando nell'array fd i loro identi atori.
In parti olare, fd[0 ontiene il des rittore dell'us ita del pipe, mentre fd[1
ontiene il des rittore dell'ingresso. Da questo si apis e ome solo il pro esso
he rea il pipe ed i suoi gli abbiano i due estremi della pipe fra le proprie
risorse private. Per questo motivo, un pipe puo essere utilizzato o per svolgere
lavoro inutile (far omuni are un pro esso on se stesso) o per far omuni are
un pro esso on un suo glio, o per far omuni are pro essi dis endenti dal
pro esso he ha reato il pipe.
Un utilizzo del pipe puo allora essere il seguente:

int fd[2;

.
.
.
res = pipe(fd);
if (res < 0) f
perror("Pipe error");
exit(-1);

res = fork();
if (res < 0) f
perror("Fork error");
exit(-1);

if (fork == 0) f

.
.
.
read(fd[0, : : :);
.
.
23

.
exit();

.
.
.
write(fd[1, : : :);
.
.
.
Sebbene un pipe possa essere usato da piu di un lettore e da piu di
uno s rittore ontemporaneamente ( onsentendo omuni azioni del tipo molti/molti, molti/uno, uno/molti e uno/uno), generalmente un solo pro esso
vi a ede in lettura ed un solo pro esso vi a ede in s rittura (se ondo lo
s hema di omuni azione uno/uno). In una on gurazione di questo tipo,
il pro esso s rittore puo hiudere il des rittore di us ita del pipe, mentre
il pro esso lettore puo hiudere il pro esso di ingresso, se ondo il seguente
s hema:

int fd[2;

.
.
.
res = pipe(fd);
if (res < 0) f
perror("Pipe error");
exit(-1);

res = fork();
if (res < 0) f
perror("Fork error");
exit(-1);

if (fork == 0) f
lose(fd[1);
.
.
.

24

lose(fd[0);
.
.
.
Spesso, il pipe e ollegato allo standard input o allo standard output
del pro esso utilizzando la sys all dup(), he dupli a un des rittore di le
(usando il primo des rittore libero):
.
.
.
lose(0);
dup(fd[0);
lose(fd[0);
.
.
.

 Chiudo lo stdin 
 E dupli o l'output della pipe... 
 ...Che viene osi' ollegata allo stdin 
 Ora hiudo l'output della pipe... 
=

Tutto io puo essere fatto in modo piu sempli e usando la sys all dup2().

E da notare he Unix fornis e la sys all popen() he esegue la sequenza
pipe()/fork()/dup()/exe (), e p lose(), he elimina il pipe ed e ettua
una wait() sul pro esso glio. Queste due sys all sono utilizzate dalle shell
per implementare atene di pro essi, a loro volta dette pipe (utilizzando la
sintassi \<prog1> | <prog2>" i due programmi <prog1> e <prog2> vengono lan iati in due pro essi in parallelo, ollegando l'us ita del primo on
l'ingresso del se ondo.
Abbiamo detto he un pipe e monodirezionale, e tipi amente viene usato
per omuni azioni uno/uno, quindi l'ingresso del pipe e identi ato da un
solo des rittore, os ome pure l'us ita. Vediamo ora inve e osa su ede se
tutti i des rittori ollegati ad un estremo vengono hiusi.
Se un pro esso e ettua una read() su un pipe il ui ingresso non e identi ato da nessun des rittore e he ha il bu er vuoto, la read() ritorna 0
per indi are la ne del le (End Of File, EOF). Se inve e er a di s rivere (tramite una write()) su una pipe la ui us ita non e identi ata da
nessun des rittore, e generato un segnale SIGPIPE. Il pro esso puo gestire
tale segnale tramite un apposito handler (spe i ato tramite siga tion())
o ignorarlo: in ogni aso la write() verra interrotta dal segnale, e ritornera
un errore EPIPE.
25

2.3.2 Un Esempio
Se gue un esempio he mostra un sempli e aso di utilizzo di un pipe per
realizzare una omuni azione monodirezionale fra un pro esso glio ed il
proprio padre.
Il programma utilizza la te ni a pre edentemente spiegata:
1. Crea il pipe
2. Forka il pro esso glio, he eredita i des rittori del pipe
3. Poi he il glio deve inviare dati, hiude il des rittore di ingresso
4. Il padre inve e deve ri evere, quindi hiude il des rittore di us ita
5. Quando il glio ha terminato, hiude an he il des rittore in us ita, ed
il padre pu`ri evere una SIGPIPE.
Notare he il orpo del padre e del glio risiedono nello stesso eseguibile,
e non viene usata al una funzione della famiglia exe ().

 Programma di esempio per l'uso delle pipe.


 In questo aso sempli e il glio ontinua
 ad inviare dati al padre.


#in lude <stdio.h>


#in lude <string.h>
#in lude <unistd.h>
#in lude <sys/types.h>
#define USE POSIX
#in lude <signal.h>
#in lude <wait.h>

 Dimensione del bu er per la stringa ri evuta


 dal pro esso padre 

#define BUFFSIZE 80

 S ommentare la linea seguente per vedere s attare la SIGPIPE 

#define DO SIGPIPE

 Forward de laration 

void handler(int signo);

26

int main(void)

int pipefd[2, i;

pid t hild;
stru t siga tion nuova, ve hia;
har s ritta[ = "Prova di s rittura nella PIPE";
har lo al[BUFFSIZE;

 Installo l'handler della SIGPIPE 

nuova.sa handler = handler;


nuova.sa ags = 0;
sigemptyset(&nuova.sa mask);
if (siga tion(SIGPIPE, &nuova, &ve hia) == -1) f
perror("Errore nell'installazione della SIGPIPE");
exit(-1);

printf("Lunghezza del buffer della FIFO %ldnn",


sys onf( PC PIPE BUF));

if (pipe(pipefd) == -1) f
= C'e' stato un errore nell'apertura della pipe =

perror("Errore nella reazione della pipe");


exit(-1);

if (( hild = fork()) < 0) f

perror("Errore nella fork");


exit(-1);

else if ( hild == 0) f
= Il pro esso glio hiude il des rittore
 in lettura poi he' sulla pipe deve solo
 s rivere. =
lose(pipefd[0);

 Ci lo for on ui viene e ettuato il passaggio della


 stringa.

for(i=0; i strlen(s ritta); i++) f
=

<

if (write(pipefd[1, &s ritta[i, sizeof( har)) == -1) f


perror("Errore nella write ...");
27

exit(-1);

g
 Come ne stringa invio un byte a 0 
=

if (write(pipefd[1, 0, sizeof( har)) == -1) f


perror("Errore nella write");
exit(-1);

g
g

printf("Fine della s rittann");


lose(pipefd[1);

else f

 Il pro esso padre hiude la pipe in s rittura


 per he' deve solo leggere ....


lose(pipefd[1);

 Per vedere s attare la SIGPIPE provare a s ommentare le 2


 linee he seguono.
 La SIGPIPE s atta quando 'e' una pipe
 aperta in s rittura ma non in lettura .. infatti in questo
 aso al task viene noti ato, on la SIGPIPE appunto, he
 potrebbe rimanere inde nitamente blo ato sulla pipe.


#ifdef DO SIGPIPE

lose(pipefd[0);

 Nel aso di hiusura della PIPE il padre aspetta la


 ne del task glio.


#endif

wait(NULL);
exit(0);

for(i=0; i<BUFFSIZE; i++) f


if (read(pipefd[0, &lo al[i, sizeof( har)) == -1) f

g
g

perror("Errore nella read ...");


exit(-1);

if (lo al[i == 0)
break;

lo al[BUFFSIZE-1 = 0;
28

g
g

printf("S ritta ri evuta -- %s nn", lo al);

return 0;

void handler(int signo)

f
g

printf("Ri evuta signal numero %d nn", signo);


printf("SIGPIPE = %d nn", SIGPIPE);
exit(0);

2.3.3 Comuni azione tramite FIFO


Come gia a ennato, uno dei piu grossi problemi he si possono in ontrare
utilizzando i pipe e he essi possono essere utilizzati solo dal pro esso he
ha reato il pipe o dai suoi dis endenti, in quanto i des rittori dei le he
ostituis ono l'ingresso e l'us ita del pipe sono privati del pro esso he lo rea
e vengono opiati nei pro essi reati tramite fork().
Per risolvere tale problema, serve un mezzo per permettere ad un pro esso
di identi are un pipe avendone visibilita globale. Una tipi a struttura a
visibilita globale ondivisa da tutti i pro essi in un sistema Unix e il le
system: per questo motivo molte risorse vengono rimappate su le, in modo
da renderle a essibili a tutti i pro essi he ne onos ano il nome. La soluzione
piu logi a e quindi quella di utilizzare il le system per permettere ai pro essi
di a edere ai pipe: tale soluzione e adottata dai pipe on nome, he sono
visibili ome le. In questo aso i vari pro essi devono quindi espli itamente
aprire la pipe in ingresso o in us ita ( ome un normale le) ollegando un
des rittore all'ingresso o all'us ita del pipe on la sys all open().
I pipe on nome, hiamati an he FIFO, permettono di omuni are a pro essi \non imparentati" fra loro, pur he essi si a ordino sul nome del FIFO
tramite ui omuni are. Per il resto sono del tutto simili ai normali pipe:
anali di omuni azione asin roni e monodirezionali a essibili on le normali primitive di I/O di Unix tramite des rittori di le privati del pro esso.
An he le FIFO hanno un bu er interno di dimensione limitata, ome i pipe
sempli i.
Come per i pipe, una lettura su una FIFO he non e aperta in s rittura
ritorna 0 (EOF), mentre una s rittura su una FIFO he non e aperta in
lettura genera un segnale SIGPIPE (e la write() ritorna EPIPE).
Una FIFO puo essere reata usando la sys all mkfifo(), he ha il seguente
prototipo:
29

#in lude
#in lude

sys/types.h>
<sys/stat.h>
<

int mkfifo( onst har *pathname, mode t mode);

Argomenti in ingresso :

pathname { Nome della FIFO (all'interno del le system)


mode { Spe i a i permessi di a esso alla FIFO.
Valore restituito :

0 { In aso di su esso
-1 { In aso di errore.
dove pathname e una stringa he indi a il nome della FIFO (vista ome
le). L'a essibilita alla FIFO e ontrollata in base allo User ID ( he ontraddistingue ogni utente nel sistema) ome spe i ato dal parametro mode,
he ha lo stesso signi ato he ha nella sys all open() e puo essere ottenuto
tramite or aritmeti o (l'operatore k) di varie ostanti:

S IRWXU rea una FIFO a essibile in lettura, s rittura ed ese uzione

S IRUSR (S IREAD) rea una FIFO a essibile in lettura da parte del-

S IWUSR (S IWRITE) rea una FIFO a essibile in s rittura da parte


dell'utente he la possiede;

S IRWXG rea una FIFO a essibile in lettura, s rittura ed ese uzione

S IRGRP rea una FIFO a essibile in lettura da parte di tutti gli utenti

S IWGRP rea una FIFO a essibile in s rittura da parte di tutti gli

S IRWXO rea una FIFO a essibile in lettura, s rittura ed ese uzione

da parte dell'utente he la possiede;


l'utente he la possiede;

da parte di tutti gli utenti appartenenti al gruppo he la possiede;


appartenenti al gruppo he la possiede;

utenti appartenenti al gruppo he la possiede;

da parte di tutti gli utenti;

S IROTH rea una FIFO a essibile in lettura da parte di tutti gli utenti;

30

S IWOTH rea una FIFO a essibile in s rittura da parte di tutti gli

utenti;

Una volta he la FIFO e stata reata, i vari pro essi possono a edervi
ome ad un normale le, aprendola ( reando ioe un des rittore di le he la
identi a in lettura o in s rittura) on la sys all open() e quindi e ettuando
write() o read() sul des rittore ritornato dalla open(). Quando si er a
di aprire una FIFO, il risultato dipende dal tipo di a esso he si ri hiede
(lettura o s rittura), dallo stato della FIFO (numero di pro essi he l'hanno
aperta in lettura o in s rittura) e dai ag spe i ati nella open().
In parti olare, se si apre in sola lettura (O RDONLY) una FIFO he non e
aperta in s rittura e non si spe i a il ag O NONBLOCK, la open() e blo ante
no a he qual he altro pro esso non apre la FIFO in s rittura. Se inve e si
spe i a il ag O NONBLOCK nella open(), la sys all ritorna immediatamente.
Analogamente, se si tenta di aprire in sola s rittura una FIFO he non e
aperta in lettura e non si spe i a il ag O NONBLOCK la open() si blo a no
a he un pro esso non apre la FIFO in lettura. Se inve e si spe i a il ag
O NONBLOCK la open() fallis e, on errore ENXIO.

2.3.4 Le IPC di System V


System V, una delle due grandi varianti di Unix, propone una serie di primitive di IPC he implementano message queues ( ode di messaggi), shared
memory (memoria ondivisa) e semaphores (semafori).
Questi me anismi non sono standard, ma vengono implementati da kernel molto di usi ome per esempio Linux. Data la loro di usione e il fatto
he sono fra i po hi appro i a onsentire l'utilizzo della memoria ondivisa,
introdurremo an he le IPC di System V, sebbene si dis ostino abbastanza
dalla loso a di Unix. Per quest'ultimo motivo non andremo pero ad approfondire la sintassi e la semanti a delle varie primitive, ma ne introdurremo
solamente l'utilizzo.
System V individua ogni struttura he permette ai pro essi di omuni are
tramite un identi atore uni o nel sistema: in generale, due pro essi possono omuni are a patto di onos ere entrambi l'identi atore globale della
struttura di IPC he intendono utilizzare. Tale valore, o ID, e ritornato dalle
sys all usate per reare o per a quisire le strutture di IPC. Vedremo poi quali
te ni he possono utilizzare i vari pro essi per \ on ordare" sull'identi atore
della struttura di IPC da usare.
Ma passiamo ora ad analizzare i me anismi di IPC di system V, omin iando on le ode di messaggi. Una message queue e, ome di e il nome, una
oda di messaggi ollegati fra loro ed a essibili tramite un identi atore di
31

oda (queue ID). Per poter utilizzare una message queue, un pro esso deve
utilizzare la primitiva msgget() per reare la oda (o ollegarsi ad essa se
gia esiste).
Poi he le ode di messaggi non sono a essibili tramite de rittori di le, le primitive di I/O di Unix non possono essere usate su di esse (e per
questo le IPC di System V si dis ostano pare hio dagli standard Unix),
quindi servono delle apposite primitive per a edere ad esse. Tali primitive
sono msg tl() (equivalente ad io tl), msgsnd() (equivalente a write()) e
msgr v() (equivalente a read()). Si invita a riferirsi ai man le per vedere
la sintassi e la semanti a di tali primitive.
Oltre alla omuni azione a s ambio di messaggi, System V implementa
an he il paradigma a memoria omune, dis ostandosi quindi dal lassiso modello di programmazione di Unix. Le primitive di IPC di System V utilizzano
un'interfa ia omogenea, quindi an he semafori e regioni di memoria omune
saranno reabili e ontrollabili tramite primitive *get() e * tl().
In parti olare, un pro esso puo reare un semaforo (o ollegarsi ad esso
se gia esiste) tramite la sys all semget().
Una volta he un pro esso ha a quisito un semaforo tramite semget(),
e in grado di operare su di esso (in rementando o de rementando il suo
ontatore di valore arbitrario) tramite la primitiva semop().
Per quanto riguarda inve e la memoria omune, un pro esso puo ottenere l'identi atore di una zona di memoria omune tramite la primitiva
shmget(), mapparla nel proprio spazio di memoria privato tramite shmat()
ed iliminare tale mapping tramite shmdt().
Rimane ora da vedere ome pro essi fra loro indipendenti possano on ordare su un ID omune da utilizzare per omuni are. Come detto, l'ID e
ritornato dalle primitive *get:
#in lude
#in lude
#in lude
#in lude
#in lude

sys/types.h>
<sys/ip .h>
<sys/msg.h>
<sys/shm.h>
<sys/sem.h>
<

int *get(key t key, ..., int flags)

Argomenti in ingresso :
key { Chiave he identi a la struttura di IPC

se ondo parametro { shmget e semget hanno an he un se ondo parame-

tro he indi a, rispettivamente, la dimensione della zona di memoria


32

ondivisa (da arrotondarsi a PAGE SIZE), o il numero di semafori da


reare

ags { Flag he spe i a i permessi di a esso alla FIFO, ed in piu indi ano
se reare la struttura o ollegarsi ad una esistente.

Valore restituito :

ID della struttura di IPC { In aso di su esso


-1 { In aso di errore.
Il modo piu sempli e per fare questo e far generare l'ID dal sistema quando uno dei pro essi genera la struttura di IPC, e farlo salvare in un luogo noto
a tutti i pro essi he devono omuni are tramite tale struttura di IPC (tipi amente nel le system). Gli altri pro essi he vogliono a edere a tale struttura
dovranno quindi andare a reperire l'ID in tale lo azione onvenzionale. Un
esempio di tale soluzione e quindi il seguente:
id = semget(IPC PRIVATE, 1, 0);
f = fopen("/tmp/miID", "w");
fwrite(f, id);
f lose(f);
.
.
.
f = fopen("/tmp/myID", "r");
fread(f, &tmp);
f lose(f);
id = semget(tmp, 1, 0);
.
.
.
Un'altra soluzione puo essere quella di embeddare una hiave (key) he
identi hi univo amente la struttura di IPC nel odi e dei vari pro essi: tipi amente un header le in luso da essi onterra la de nizione della hiave
ome ostante. A questo punto la funzione *get() onvertira' la hiave in
un ID. Il problema on questo appro io e he vari set fra loro indipendenti
di pro essi ooperanti possono tentare di usare la stessa hiave per ompiti
33

diversi. Questo e inevitabile, ma bisogna omunque evitare he si generino


situazioni di in onsistenza: in prati a, e auspi abile he la se onda appli azione he er a di utilizzare la stessa hiave si blo hi, senza interferire on
la prima he ha utilizzato tale hiave. Questo e possibile pro edendo nel
seguente modo: un solo pro esso per appli azione rea la struttura di IPC,
spe i ando i ag IPC CREAT | IPC EXCL he fanno generare una ondizione
di errore se la struttura gia esiste.
La terza soluzione e inve e basata sull'utilizzo della funzione ftok(),
he permette di ri ostruire l'ID a partire da una hiave onos iuta dai vari
pro essi ( omposta da un pathname e da un arattere, il proje t ID).
Notare he se inve e i pro essi he devono omuni are fra loro sono legati
da una relazione padre/ glio, il probelma non sussiste per he la struttura
puo essere reata dal padre, e l'ID viene ereditato dal glio.

2.3.5 Un esempio
Come esempio di utilizzo della memoria ondivisa se ondo le primitive di
IPC di System V portiamo un programma leggermente piu omplesso del
solito. Il programma e omposto da due task (padre e glio) he gio ano a
tris, utilizzando la memoria ondivisa per omuni are.
L'aspetto del programma he i interessa e appunto la omuni azione
attraverso memoria ondivisa: in parti olare, notare ome per ondividere
l'ID della zona di memoria ondivisa i due pro essi utilizzino la prima delle
tre te ni he mostrate sopra (utilizzo di IPC PRIVATE al posto della hiave),
avvantaggiandosi della loro relazione padre/ glio (il padre rea la zona di
memoria ondivisa prima di fare la fork(), osi he il glio eredita l'ID).

 Programma di esempio di memoria ondivisa ...


 In questo aso si tratta di due task he "gio ano"
 a tris. La strategia e' abbastanza a " aso", nel senso
 he ogni task mette il suo simbolo a aso in una delle
 aselle libere.
 Dovrebbe risultare abbastanza sempli e estenderlo in modo
 da avere delle modalita' di gio o piu' omplesse.
 La sin ronizzazione in questo aso viene ottenuta per
 mezzo di un polling.


#in lude <stdio.h>


#in lude <unistd.h>
#define USE POSIX

34

#in lude
#in lude
#in lude
#in lude
#in lude
#in lude
#in lude
#in lude
#in lude
#in lude

signal.h>
sys/types.h>
<sys/stat.h>
<sys/ip .h>
<sys/shm.h>
<semaphore.h>
<f ntl.h>
<errno.h>
<stdlib.h>
<time.h>
<
<

 Nome del le usato per la ondivisione 

#define CAMPO "./ ampo"

 Simbolo he indi a he la orispondente asella e' libera 

#define LIBERO '+'

 Valori he indi ano hi fra i due task ha il turno per


 gio are ...

=

#define PADRE 0
#define FIGLIO 1

 De zione e istanziazione di una struttura usata


 dai due task per "gio are".

stru t tris f
=

har ampo[9;
= Numero di aselle libere =
int libere;
int turno;

g tris;

 Funzione he serve per stampare a video la s hermata


 on l'attuale situazione del gio o.

void stampaCampo( har  ampo) f

int i;

printf("------");
for (i=0; i<9; i++) f
if ((i%3) == 0)
printf("nnj");
35

g
g

printf("% j", ampo[i);

printf("nn------nn");

int main(void)

int memd, i, mossa;

pid t hild;
stru t tris mioCampo;

 Ottengo la zona di memoria ondivsa 

memd = shmget(IPC PRIVATE, sizeof(stru t tris), SHM R j


SHM W j
IPC CREAT j IPC EXCL);
if (memd == -1) f
perror("Non sono rius ito a reare la memoria
ondivisann");
exit(-1);

g
 "atta o" la zona di memoria ondivisa al mio spazio
 di indirizzamento 
mioCampo = shmat(memd, 0, SHM R j SHM W);
if (mioCampo == -1) f
=

perror("Non sono rius ito ad aggan iare la memoria


ondivisann");
exit(-1);

 Inizializzo la zona di memoria ondivisa .... 

for (i=0;i<9;i++)

mioCampo! ampo[i = LIBERO;


mioCampo!libere=9;

 Il padre e' quello he inizia a gio are per primo!!! 


mioCampo !turno = PADRE;
=

= A questo punto parto on le fork ....


if (( hild = fork()) < 0) f

perror("Errore nella fork");


exit(-1);
36

else if ( hild == 0) f
= Pro esso glio .... =
while(mioCampo!libere > 0) f
= Aspetto in polling he il turno passi al glio =
while(mioCampo!turno == PADRE);

 Estraggo aso una delle aselle libere 


mossa = (rand()%mioCampo!libere)+1;

 Riempio la asella estratta prima on il simbolo


 "giusto".

for (i=0; i 9; i++) f
if (mioCampo! ampo[i == LIBERO) f
=

<

mossa--;
if (mossa == 0) f
mioCampo! ampo[i = 'o';
break;

g
 "Pulis o" lo s hermo 
=

system(" lear");

 Stampo a video il " ampo" di tris 

stampaCampo(mioCampo! ampo);

 Aggiorno il valore delle aselle libere 


mioCampo!libere--;
 Cedo il turno all'altro task 
mioCampo!turno = PADRE;
 Fermo il task per 1 se ondo 
=

sleep(1);

g
 In ogni aso prima di us ire libero il semaforo 
mioCampo!turno = PADRE;
=

return 0;

else f

 Pro esso Padre .... 


while(mioCampo!libere 0) f
 Aspetto in polling he il turno passi al padre 
while(mioCampo!turno == FIGLIO);

>

37

 Estraggo aso una delle aselle libere 


mossa = (rand()%mioCampo!libere)+1;
=

 Riempio la asella estratta prima on il simbolo


 "giusto".

for (i=0; i 9; i++) f
if (mioCampo! ampo[i == LIBERO) f

<

mossa--;
if (mossa == 0) f
mioCampo! ampo[i = 'x';
break;

g
 "Pulis o" lo s hermo 
=

system(" lear");

 Stampo a video il " ampo" di tris 

stampaCampo(mioCampo! ampo);

 Aggiorno il valore delle aselle libere 


mioCampo!libere--;
 Cedo il turno all'altro task 
mioCampo!turno = FIGLIO;
 Fermo il task per 1 se ondo 
=

sleep(1);

g
 In ogni aso prima di us ire libero il semaforo 
mioCampo!turno = FIGLIO;
=

return 0;

g
g

return 0;

38

Capitolo 3
Multithreading
Come gia detto pre edentemente, un thread e un usso di ese uzione non
ne essariamente legato ad uno spazio di memoria privato (ed in questo si
di erenza da un pro esso). Un thread ha bisogno di uno spazio di memoria in
ui eseguire, ma tale spazio non e ne essariamente privato. In questo senso si
puo dire he ogni pro esso onterra uno o piu thread: un pro esso ontenente
un uni o thread sara un lassi o pro esso unix, ome quelli des ritti nel
Capitolo 2, mentre un pro esso omposto da piu thread ( he possono quindi
omuni are tra loro tramite memoria omune) sara un pro esso multithreaded.
I thread reati da un pro esso girano nello stesso spazio di indirizzamento (quello del pro esso he li ha reati) e quindi possono usufruire, senza
al un aggravio da parte del kernel, di una zona di memoria ondivisa tramite la quale puo avvenire lo s ambio di dati. Naturalmente la gestione della
on orrenza fra thread resta un onere dell'appli azione.

3.1 Generalita sui Thread


Informalmente parlando, un thread non e altro he una funzione (nel senso
he tale termine ha nel linguaggio C) he viene eseguita in maniera on orrente ad altre funzioni, nell'ambito di un pro esso.
Tutti i thread reati nell'ambito di un pro esso ne ondividono lo spazio
di indirizzamento. In aggiunta a questo ogni thread eredita dal Pro esso he
lo rea i seguenti dati :





Des rittori dei le,


Handler dei Signal,
Dire tory Corrente,
39

ID di utente e di gruppo.

Ogni thread possiede pero un suo :








ID del thread,
Contesto del pro essore (Sta k Pointer, Registri, Program Counter),
Sta k,
Variabile errno,
Mas hera dei Signal,
Priorita.

Come avviene sempre nella programmazione il supporto per determinate


aratteristi he all'interno di un sistema operativo viene dato essenzialmente
tramite due me anismi: tipi di dato e funzioni del sistema operativo. Nei
prossimi paragra introdurremo quindi on gradualita sia al uni dei nuovi tipi
di dato he al une delle nuove funzioni impli ate nella gestione dei thread.

3.1.1 Creazione dei Thread


Per supportare il me anismo dei thread lo standard Posix de nis e innanzitutto un nuovo tipo di dato he e il pthread t . Tale nuovo tipo di dato
serve per ontenere l'identi atore he Unix assegna in modo univo o ad ogni
thread.
Ogni Pro esso prima di iniziare la fase di reazione di uno o piu thread
deve omuni are al nu leo il numero di thread da reare detto on urren y
level. Cio e fatto invo ando la routine :
#in lude <pthread.h>
int pthread set on urren y (int nThread);

Argomenti in ingresso :

nThread { Numero dei thread he si vogliono reare.


Valore restituito :

0 { In aso di su esso.

6= 0

{ In aso di insu esso.


40

Un thread e reato invo ando la seguente routine del nu leo :


#in lude

<

pthread.h>

int pthread reate (pthread t *tid, onst p thread attr *attr,


(void *(*fun )(void *), void *arg);

Argomenti in ingresso :

tid { Al ritorno dalla hiamata ontiene l'ID del thread


attr { Valori passati dell'utente per gli attributi del thread. Nel aso si
volessero utilizzare quelli di default si deve passare il valore NULL.
fun { E la funzione he ontiene il odi e del thread.
arg { Parametro opzionale da passare al thread. E un puntatore alla zona
di memoria he ontiene i dati.

Valore restituito :

0 { In aso di su esso.

6= 0

{ In aso di insu esso.

Un volta reato un thread puo esser i la ne essita di sin ronizzarsi on il


suo istante di terminazione, ioe sospendersi n he il thread non e terminato.
Questo e fatto tramite la funzione di nu leo :
#in lude

<

pthread.h>

int pthread join (pthread t tid, void **status);

Argomenti in ingresso :

tid { Identi atore del thread sul ui momento di ne i si vuole sin ronizzare.

status { Puntatore all'area di memoria in ui viene salvato il valore retirtuito


dal thread.

Valore restituito :
41

0 { In aso di su esso.

6= 0

{ In aso di insu esso.

Tale funzione ritorna al hiamante solo una volta he il thread il ui ID e


passato nel parametro tid ha terminato la sua ese uzione. Il nu leo onserva le
informazioni su un thread he ha terminato la sua ese uzione no a quando
su quest'ultimo non viene eseguita una pthread join. Questo serve, per
esempio, per evitare he un thread si blo hi inde nitamente aspettando un
altro thread he ha gia terminato la sua ese uzione.
La funzione pthread kill, serve per mandare un segnale software a un thread. Il nome della funzione e giusti ato dal fatto he in genere tale funzione
viene usata per inviare un segnale di terminazione. Il problema delle segnalazioni software a un thread o a un task sara a rontato piu approfonditamente
in seguito.
#in lude

<

pthread.h>

int pthread kill (pthread t tid, intsigno);

Argomenti in ingresso :

tid { Identi atore del thread a ui si vuole inviare il segnale software.


signo { Identi atore del segnale he si vuole inviare.
Valore restituito :

0 { In aso di su esso.

6= 0

{ In aso di insu esso.

3.1.2 Un esempio: Creazione di due thread


In questo esempio vedremo ome reare due sempli i thread. Tutto quello he
faranno questi due thread sara leggere e in rementare una variabile ondivisa
e quindi stamparla a video.

 Programma di esempio di due thread .... 

#in lude
#in lude
#in lude

stdio.h>
<pthread.h>
<unistd.h>
<

42

#in lude
#in lude

<
<

signal.h>
sys/types.h>

#define CICLI 10

 Variabile he regola il tempo perso nel i lo for 

#define DELAY 2000000

 Memoria Condivisa fra i thread 


stru t f
=

int val;

g shared;

 Forward de laration 
void somma1(void );
void somma2(void );
=

int main(void)

pthread t som1TID, som2TID;

int risu;

 Inizializzo la memoria ondivisa 

shared.val = 0;

 Setto il on urren y level a 2 


pthread set on urren y(2);

=
=

 A questo punto posso reare i thread .... 

risu = pthread reate(&som1TID, NULL, somma1, NULL);


if (risu 6= 0) f
printf("Errore nella reazione del primo threadnn");
return -1;

risu = pthread reate(&som2TID, NULL, somma2, NULL);


if (risu 6= 0) f
printf("Errore nella reazione del se ondo threadnn");
pthread kill(som1TID, SIGKILL);
return -1;

43

 A questo punto aspetto he i due thread nis ano ... 

pthread join(som1TID, NULL);


pthread join(som2TID, NULL);

printf("E' finito l'esperimento ....nn");


return (0);

void somma1(void in)

int i, j;
for(i=0; i<CICLI; i++) f
shared.val++;
=

 Ci lo messo solo per perdere tempo 

for(j=0; j<DELAY; j++);

g
g

printf("somma1 -- shared = %d nn", shared.val);

return NULL;

void somma2(void in)

int i, j;
for(i=0; i<CICLI; i++) f
shared.val++;
=

 Ci lo messo solo per perdere tempo 

for(j=0; j<DELAY; j++);

g
g

printf("somma2 -- shared = %d nn", shared.val);

return NULL;

Dato he un thread altro non e he una funzione C he gira in maniera


on orrente ad altre funzioni (nell'esempio i due thread sono le due funzioni
somma1 e somma2), allora si appli ano le regole di s ope del C standard.
Sono quindi ondivise fra tutti i thread quelle variabili he sono globali a
tutte le funzioni.
Nell'esempio i due thread denominati somma1 e somma2 a edono entrambi alla variabile ondivisa shared.val in rementandola, entrano quindi in
44

un i lo pensato solo per far passare del tempo e quindi rileggono e stampano a video la variabile ondivisa. Tale a esso non viene regolamentato
da al un me anismo di gestione delle on orrenza, non si puo avere quindi
al una garanzia sul fatto he il valore he viene alla ne stampato a video sia
e ettivamente quello he la variabile aveva assunto dopo il suo in remento,
poi he, nel frattempo, puo essere stato s hedulato l'altro thread he l'ha a
sua volta modi ato.
Oltre a dare un'esempli azione di ome possono essere reati dei thread
l'esempio mostra ome la on orrenza non regolamentata genera, an he in
un aso sempli e, problemi di onsistenza dei dati, quando a questi si a ede
senza il supporto di appositi me anismi.

3.2 Me anismi di mutua es lusione


Lo standard Posix supporta me anismi di mutua es lusione per mezzo del
nuovo tipo di dato pthread mutex t, he e la struttura dati usata per la
gestione della mutua es lusione, e di tutta una serie di routine di nu leo he
vi operano.
Le routine messe a disposizione dallo standard Posix sono prin ipalmente
due :
#in lude <pthread.h>
int pthread mutex lo k (pthread mutex t *mutex);
int pthread mutex unlo k (pthread mutex t *mutex);

Argomenti in ingresso :

mutex { Indirizzo del semaforo.


Valore restituito :

0 { In aso di su esso.

6= 0

{ In aso di insu esso.

Queste due funzioni servono rispettivamente per a quisire e rilas iare un


semaforo di mutua es lusione. La funzione pthread mutex lo k ritorna
al thread hiamante solamente ad a quisizione avvenuta o allo s attare di
una ondizione di errore. Di questa funzione esiste an he una versione non
blo ante he ritorna immediatamente e he ha la seguente sintassi :
45

#in lude <pthread.h>


int pthread mutex trylo k (pthread mutex t *mutex);

Argomenti in ingresso :

mutex { Indirizzo del semaforo.


Valore restituito :

0 { Se il semaforo e stato a quisito.


EBUSY { Se il semaforo era gia o upato.
 importante sottolineare he le routine e il tipo di dato appena visti
E
servono solamente per asi di mutua es lusione. Per asi piu omplessi di
sin ronizzazione su una erta ondizione esistono dei me anismi piu ranati
he verranno esaminati nel seguito.

3.2.1 Un esempio: Me anismi di mutua es lusione


In questo esempio vedremo il piu sempli e me anismo di gestione della
on orrenza, he e la mutua es lusione.

 Programma di esempio di due thread ....


 Ora aggiungiamo un semaforo di mutua es lusione!!!!


#in lude
#in lude
#in lude
#in lude
#in lude

stdio.h>
pthread.h>
<unistd.h>
<signal.h>
<sys/types.h>
<
<

 Numero di i li di s rittura/lettura nel bu er 

#define CICLI 10

 Lunghezza del bu er 

#define LUN 50

 Numero di i li a vuoto di attesa 

#define DELAY 10000000

 Memoria Condivisa fra i threads ...

46

stru t f

pthread mutex t mutex;

 Stringa stampata a video =


har s ritta[LUN+1;
= Numero dei aratteri inseriti =
int numLetture;
int numS ritture;
=

g shared = fPTHREAD MUTEX INITIALIZERg;


 Forward de laration =
void s rittore1(void );
void s rittore2(void );
void lettore(void );
=

int main(void)

pthread t s rit1, s rit2, let;


int res, i;

 Inizializzo la stringa s ritta 


for(i=0; i LUN; i++) f
=

<

shared.s ritta[i = 'x';

g
 Ogni stringa C deve terminare on 0!!!!! 
=

shared.s ritta[LUN = 0;
shared.numLetture = 0;
shared.numS ritture = 0;

 Setto il on urren y level a 3 

pthread set on urren y(3);

 A questo punto posso reare i thread .... 

res = pthread reate(&s rit1, NULL, s rittore1, NULL);


if (res 6= 0) f
printf("Errore nella reazione del primo threadnn");
return -1;

res = pthread reate(&s rit2, NULL, s rittore2, NULL);


if (res 6= 0) f
47

printf("Errore nella reazione del se ondo threadnn");


pthread kill(s rit1, SIGKILL);
return -1;

res = pthread reate(&let, NULL, lettore, NULL);


if (res 6= 0) f
printf("Errore nella reazione del se ondo threadnn");
pthread kill(s rit1, SIGKILL);
pthread kill(s rit2, SIGKILL);
return -1;

g
 Fine della fase di reazione dei thread 
=

 A questo punto aspetto he i due thread nis ano ... 

pthread join(s rit1, NULL);


pthread join(s rit2, NULL);
pthread join(let, NULL);

printf("E' finito l'esperimento ....nn");


return (0);

void s rittore1(void in)

int i, j, k;
for (i=0; i<CICLI; i++) f
while(1) f

pthread mutex lo k(&shared.mutex);


if (shared.numLetture == shared.numS ritture) f
break;

pthread mutex unlo k(&shared.mutex);

for(k=0; k<LUN; k++) f

shared.s ritta[k = '+';

shared.numS ritture++;
pthread mutex unlo k(&shared.mutex);
for(j=0; j<DELAY; j++);
48

g
g

return NULL;

void s rittore2(void in)

int i, j, k;
for (i=0; i<CICLI; i++) f
while(1) f

pthread mutex lo k(&shared.mutex);


if (shared.numLetture == shared.numS ritture) f
break;

pthread mutex unlo k(&shared.mutex);

for(k=0; k<LUN; k++) f

g
g
g

shared.s ritta[k = '-';

shared.numS ritture++;
pthread mutex unlo k(&shared.mutex);
for(j=0; j<DELAY; j++);

return NULL;

void lettore(void in)

int i;
for (i=0; i<2CICLI; i++) f
while(1) f

pthread mutex lo k(&shared.mutex);


if (shared.numLetture 6= shared.numS ritture) f
break;

pthread mutex unlo k(&shared.mutex);

printf("Lettore -- s ritta = %snn", shared.s ritta);


shared.numLetture++;
pthread mutex unlo k(&shared.mutex);
49

g
g

return NULL;

L'esempio e una rivisitazione del lassi o problema dei lettori/s rittori.


In questo aso si hanno due thread s rittori (s rittore1 e s rittore2) he
i li amente s rivono una stringa in un bu er ondiviso, aspettando un erto
tempo fra una s rittura e l'altra, e un thread lettore he si deve sin ronizzare
on l'istante di ne s rittura sul bu er e quindi stampare la stringa risultante.
L'esempio e ongegnato in modo he un thread s rittore non puo ris rivere i
dati del bu er se prima il thread lettore non ne ha fatto al relativa stampa a
video.
Si noti ome, visto he l'uni o strumento he abbiamo e la mutua e lusione, la sin ronizzazione sulla ondizione di ' ne s rittura stringa' puo essere
fatta solamemente tramite un polling.
Un'altra osa da notare e he l'inizializzazione della struttura mutex e
fatta in modo stati o uguagliandola a una ma ro.
Eser izio: Si risalga al le in lude in ui e de nita la ma ro PTHREAD MUTEX INITIALIZER e si veri hi la sua onsistenza on la
de nizione della struttura pthread mutex t.

3.3 Attesa di una ondizione


Come detto nella sezione 3.2 on i soli me anismi di mutua es lusione l'uni o
me anismo di attesa per il veri arsi di una erta ondizione e quello del polling. Tale me anismo e ovviamente dispendioso dal punto omputazionale
oltre he po o elegante.
Per ovviare a tale in onveniente lo standard Posix mette a disposizione
un ulteriore me anismo per onsentire a un thread di blo arsi in attesa he
una erta ondizione diventi vera.
Tale me anismo si avvale della de nizione di un nuovo tipo di dato
(pthread ond t), ontenente i ampi ne essari alla gestione del blo aggio/sblo aggio dei thread. Una variabile di tipo di tipo pthread ond t
si hiama variabile ondizione ed e asso iata in maniera impli ita ad una
ondizione logi a he si vuole veri are.
Le funzioni he operano su tale struttura dati sono :

50

#in lude <pthread.h>


int pthread ond wait (pthread ond t * ond var, pthread mutex t
*mutex);
int pthread ond signal (pthread ond t * ond var);

Argomenti in ingresso :

ond { Indirizzo di una istanza del tipo pthread ond t he rappresenta


la ondizione di sin ronizzazione.

mutex { Semaforo di mutua es lusione ne essarrio alla gestione orretta


onsistenza dei dati.

Valore restituito :

0 { In aso di su esso

6= 0

{ In aso di insu esso.

La pthread ond wait serve per sin ronizzarsi on una erta ondizione
all'interno di un blo o di dati ondivisi e protetti da un semaforo di mutua
es lusione. La presenza, fra gli input, del semaforo di mutua es lusione,
nel aso della wait, serve a garantire he al momento del blo aggio esso
venga liberato per onsentire ad altri thread di potere a edere alla zona
di memoria ondivisa. Inoltre, se la wait ritorna in modo regolare, allora
il sistema garantis e he an he il semaforo di mutua es lusione sia stato
nuovamente a quisito.
Si tenga inoltre presente he la funzione signal non libera il semaforo di
mutua es lusione, poi he esso non e tra i suoi argomenti in ingresso, e quindi
questo deve essere espli itamente liberato per onsentire l'a esso ai dati, in
aso ontrario si ris hiano situazioni di deadlo k.

3.3.1 Un Esempio: Me anismi di sin ronizzazione su


una ondizione
In questo esempio vedremo ome dei thread possano blo arsi in attesa he
una erta ondizione, asso iata in maniera impli ita a una variabile di tipo
pthread ond t sia veri ata.

 Programma di esempio di due thread ....


 Ora aggiungiamo un semaforo di mutua es lusione!!!!

51

 ... piu' una variabile ondition!!!



=

#in lude
#in lude
#in lude
#in lude
#in lude

stdio.h>
<pthread.h>
<unistd.h>
<signal.h>
<sys/types.h>
<

 Numero di i li di lettura/s rittura he vengono fatti dai thread 

#define CICLI 1

 Lunghezza del bu er 

#define LUN 20

 Numero di i li di attesa a vuoto di uno s rittore 

#define DELAY WRITER 200000

 Numero di i li di attesa a vuoto di uno s rittore 

#define DELAY READER 2000000

 Memoria Condivisa fra i thread ... 


stru t f
 Semaforo di mutua es lusione 

pthread mutex t mutex;

 Variabile ondition per il lettore 

pthread ond t lettore;

 Variabile ondition per gli s rittori 

pthread ond t s rittori;

 Bu er 

har s ritta[LUN+1;
= Variabili per la gestione del bu er =
int primo, ultimo, elementi;
= Numero di lettori e s rittori blo ati =
int blo ks ri, blo klet;

g shared = fPTHREAD MUTEX INITIALIZER,

PTHREAD COND INITIALIZER,


PTHREAD COND INITIALIZERg;

 Forward de laration 
void s rittore1(void );
void s rittore2(void );
void lettore(void );
=

int main(void)
52

pthread t s1TID, s2TID, lTID;


int res, i;

 Inizializzo la stringa s ritta 


for(i=0; i LUN; i++) f
=

<

shared.s ritta[i = 'x';

g
 Ogni stringa C deve terminare on 0!!!!! 
=

shared.s ritta[LUN = 0;
shared.primo = shared.ultimo = shared.elementi = 0;
shared.blo klet = shared.blo ks ri = 0;

 Setto il on urren y level a 3 

pthread set on urren y(3);

 A questo punto posso reare i thread .... 

res = pthread reate(&lTID, NULL, lettore, NULL);


if (res 6= 0) f
printf("Errore nella reazione del primo threadnn");
return -1;

res = pthread reate(&s1TID, NULL, s rittore1, NULL);


if (res 6= 0) f
printf("Errore nella reazione del se ondo threadnn");

 Devo far terminare il thread he ho reato 

pthread kill(s1TID, SIGKILL);


return -1;

res = pthread reate(&s2TID, NULL, s rittore2, NULL);

if (res 6= 0) f

printf("Errore nella reazione del terzo threadnn");


pthread kill(lTID, SIGKILL);
pthread kill(s1TID, SIGKILL);
return -1;

 A questo punto aspetto he i tre thread nis ano ... 

pthread join(s1TID, NULL);

53

pthread join(s2TID, NULL);


pthread join(lTID, NULL);
printf("E' finito l'esperimento ....nn");
return (0);

g
= Codi e relativo a uno dei thread he s rivono =
void s rittore1(void in)

int i, j, k;
for (i=0; i<CICLI; i++) f
for(k=0; k<LUN; k++) f
= Come prima osa a quisis o la mutua es lusione =
pthread mutex lo k(&shared.mutex);
while (shared.elementi == LUN) f

 Segnalo he mi sto blo ando 

shared.blo ks ri++;
pthread ond wait(&shared.s rittori, &shared.mutex);

 Segnalo he mi sto sblo ando 

shared.blo ks ri--;

g
 Aggiungo un arattere e aggiorno i vari ampi 
=

shared.s ritta[shared.ultimo = '-';


shared.ultimo = (shared.ultimo+1)%(LUN);
shared.elementi++;

= Controllo se devo sblo are il lettore =


if (shared.blo klet 6= 0)

pthread ond signal(&shared.lettore);

 Rilas io la mutua es lusione 

pthread mutex unlo k(&shared.mutex);

 ... perdo tempo (in modo ORRIBILE!!!!) 

g
g

for(j=0; j<DELAY WRITER; j++);

return NULL;

 Codi e relativo a uno dei thread he s rivono 

54

void s rittore2(void in)

int i, j, k;
for (i=0; i<CICLI; i++) f
for(k=0; k<LUN; k++) f
= Come prima osa a quisis o la mutua es lusione =
pthread mutex lo k(&shared.mutex);
while (shared.elementi == LUN) f

 Segnalo he mi sto blo ando 

shared.blo ks ri++;
pthread ond wait(&shared.s rittori, &shared.mutex);

 Segnalo he mi sto sblo ando 

shared.blo ks ri--;

g
 Aggiungo un arattere e aggiorno i vari ampi 
=

shared.s ritta[shared.ultimo = '+';


shared.ultimo = (shared.ultimo+1)%(LUN);
shared.elementi++;

 Controllo se devo sblo are il lettore 


if (shared.blo klet =
6 0)
=

pthread ond signal(&shared.lettore);

 Rilas io la mutua es lusione 

pthread mutex unlo k(&shared.mutex);

 ... perdo tempo (in modo ORRIBILE!!!!) 

g
g

for(j=0; j<DELAY WRITER; j++);

return NULL;

void lettore(void in)

int i, k, j;
har lo al[LUN+1;
lo al[LUN = 0;

for (i=0; i<2CICLI; i++) f


for (k=0; k<LUN; k++) f
= Come prima osa a qisis o la mutua es lusione =
55

pthread mutex lo k(&shared.mutex);

if (shared.elementi == 0) f
= Segnalo he mi sto sblo ando =

shared.blo klet++;
pthread ond wait(&shared.lettore, &shared.mutex);

 Segnalo he mi sono blo ato 

shared.blo klet--;

g
 Leggo un arattere e aggiorno i vari ampi 
=

lo al[k = shared.s ritta[shared.primo;


shared.primo = (shared.primo+1)%(LUN);
shared.elementi--;

 Controllo se devo sblo are uno s rittore 


if (shared.blo ks ri =
6 0)
=

pthread ond signal(&shared.s rittori);

 Rilas io la mutua es lusione 

pthread mutex unlo k(&shared.mutex);

 ... perdo tempo (in modo ORRIBILE!!!!) 

g
g
g

for(j=0; j<DELAY READER; j++);

printf("Stringa = %s nn", lo al);

return NULL;

L'esempio verte sempre sul problema dei lettori e degli s rittori. In questo
aso gli s rittori inseris ono i dati in un bu er ir olare di lunghezza de nita
dalla ma ro LUN, e si blo ano solo se il bu er e gia pieno. L'uni o thread
he legge ontinua ad estrarre aratteri e si blo a solamente quando il bu er
risulta essere vuoto.
Le strutture dati he si sono viste no ad ora sono piu he su ienti per
blo are un thread in attesa he una erta ondizione si veri hi. Appare
an he piu he logi a la s elta di dividere i semafori nelle due tipologie di
semafori di mutua es lusione e semafori di ondizione. Man a pero an ora un
me anismo sintatti o he sia simile alle primitive semafori he viste durante
il orso e he fanno parte dei trattati lassi i di Sistemi Operativi.
Eser izio: Nell'esempio e ne essario he il thread lettore ottenga an he

il semaforo
di mutua es lusione? La risposta ambierebbe se i fossero due
thread he leggono? Giusti are la risposta on degli esempi.
Per olmare questo vuoto lo standard Posix o re un nuovo tipo di dato,
sem t he e un'implementazione del lassi o me anismo semafori o. Me 56

anismi analoghi potrebbero essere ostruiti di volta in volta sfruttando le


primitive e i tipi di dato gia visti (e il aso dell'esempio 3.3.1) ma questo
ompli herebbe ogni volta la s rittura del programma. Si deve onsiderare
questo tipo di dato, e le relative primitive asso iate, ome un'utile s or iatoia
o erta al programmatore.
Data la sua diversa natura non e stata prevista una ma ro di inizializzazione di un semaforo, per ui esso dovra essere espli itamente inizializzato
on il valore voluto tramite la primitiva :
#in lude <semaphore.h>
int sem init(sem t *sem, int pshared, unsigned int value);

Argomenti in ingresso :

sem { Indirizzo del semaforo he si vuole inizializzare.


pshared { Se posto a zero indi a he il semaforo non e ondiviso fra piu

pro essi, altrimenti indi a he il semaforo deve essere ondiviso fra piu
pro essi1 .

value { Valore iniziale del semaforo.


Valore restituito :

0 { In aso di su esso
-1 { In aso di insu esso.
Dopo aver istanziato una variabile di tipo sem t ed averla inizializzata
on la primtiva appena vista potremo operare su di essa on le seguenti
primitive :
#in lude <semaphore.h>
int sem wait(sem t *sem);
int sem post(sem t *sem);

Argomenti in ingresso :

sem { Indirizzo del semaforo su ui si vuole operare.


Valore restituito :
1 Allo

stato attuale l'implementazione dei LinuxThreads supporta solamente

57

pshared =

0 { In aso di su esso
-1 { In aso di insu esso.
Queste due funzioni rappresentano il modo lassi o di operare su dei
semafori per ui non i si dilunghera ulteriormente sul loro uso.
Per ompletezza riportiamo le seguenti primitive he operano sui semafori
:
#in lude <semaphore.h>
int sem trywait(sem t *sem);

Tale primitiva e la versione non blo ante della sem wait.


Argomenti in ingresso :

sem { Indirizzo del semaforo su ui si vuole fare la wait non blo ante.
Lo standard Posix de nis e an he una primitiva per potere leggere il
valore attuale di un semaforo :
#in lude <semaphore.h>
int sem getvalue(sem t *sem, int *sval));

Argomenti in ingresso :

sem { Indirizzo del semaforo di ui si vuole leggere il valore.


sval { Puntatore all'intero in ui viene s ritto il valore del semaforo.
Per entrambe le primitive vale quanto visto sopra riguardo al valore di
ritorno.

3.3.2 Un esempio
In questo esempio vedremo un uso base dell'implementazione dei semafori
dello standard Posix.

 Programma di esempio dei semafori ....


 Implementazione on il me anismo semafori o del me anismo
 visto nel le ondition.


#in lude

semaphore.h>

<

58

#in lude
#in lude
#in lude

pthread.h>
signal.h>
<stdio.h>
<
<

 Numero di i li di lettura/s rittura he vengono fatti dai thread 

#define CICLI 1

 Lunghezza del bu er 

#define LUN 20

 Numero di i li di attesa a vuoto di uno s rittore 

#define DELAY WRITER 200000

 Numero di i li di attesa a vuoto di uno s rittore 

#define DELAY READER 2000000

 Memoria Condivisa fra i thread ... =


stru t f
har s ritta[LUN+1;
= variabili per la gestione del bu er =
int primo, ultimo;
= Variabili semafori he =

sem t mutex, piene, vuote;


g shared;

 Forward de laration 
void s rittore1(void );
void s rittore2(void );
void lettore(void );
=

int main(void) f

pthread t s1TID, s2TID, lTID;

int res, i;

 Fase di inzializzazione delle strutture dati =


for(i=0; i<LUN; i++) f
=

shared.s ritta[i = 'x';

shared.primo = shared.ultimo = 0;

 Fine della fase di inizializzazione delle strutture dati 

 Fase di inizializzazione dei semafori 

if (sem init(&shared.mutex, 0, 1) == -1) f

printf("Non sono rius ito ad inizializzare


59

shared.mutexnn");

return 1;

if (sem init(&shared.piene, 0, 0) == -1) f

printf("Non sono rius ito ad inizializzare


shared.pienenn");

return 1;

if (sem init(&shared.vuote, 0, LUN) == -1) f

printf("Non sono rius ito ad inizializzare


shared.vuotenn");
return 1;

g
 Fine della fase di inizializzazione dei semafori 
=

 Ora puo' iniziare la fase di reazione dei thread ... 

 Setto il on urren y level a 3 

pthread set on urren y(3);

 A questo punto posso reare i thread .... 

res = pthread reate(&lTID, NULL, lettore, NULL);

if (res 6= 0) f

printf("Errore nella reazione del primo threadnn");


return -1;

res = pthread reate(&s1TID, NULL, s rittore1, NULL);


if (res 6= 0) f
printf("Errore nella reazione del se ondo threadnn");

 Devo far terminare il thread he ho reato 

pthread kill(s1TID, SIGKILL);


return -1;

res = pthread reate(&s2TID, NULL, s rittore2, NULL);


if (res 6= 0) f
printf("Errore nella reazione del terzo threadnn");
pthread kill(lTID, SIGKILL);
60

pthread kill(s1TID, SIGKILL);


return -1;

 A questo punto aspetto he i tre thread nis ano ... 

pthread join(s1TID, NULL);


pthread join(s2TID, NULL);
pthread join(lTID, NULL);

printf("E' finito l'esperimento ....nn");


return 0;

= Codi e del primo thread s rittore =


void s rittore1(void in)

int i, j, k;
for (i=0; i<CICLI; i++) f
for(k=0; k<LUN; k++) f
= Controllo he il bu er non sia pieno =
sem wait(&shared.vuote);

 A quisis o la mutua es lusione 

sem wait(&shared.mutex);

 Inseris o un arattere e aggiorno le strutture dati 

shared.s ritta[shared.ultimo = '-';


shared.ultimo = (shared.ultimo+1)%(LUN);

 Libero il semaforo di mutua es lusione 

sem post(&shared.mutex);

 Segnalo he ho aggiunto un arattere 

sem post(&shared.piene);

g
g

= ... perdo un po o di tempo =


for(j=0; j<DELAY WRITER; j++);

return NULL;

 Codi e del se ondo thread s rittore 


void s rittore2(void in)
f
=

61

int i, j, k;
for(i=0; i<CICLI; i++) f
for(k=0; k<LUN; k++) f
= Controllo he il bu er non sia pieno =
sem wait(&shared.vuote);

 A quisis o la mutua es lusione 

sem wait(&shared.mutex);

 Inseris o un arattere e aggiorno le strutture dati 

shared.s ritta[shared.ultimo = '+';


shared.ultimo = (shared.ultimo+1)%(LUN);

 Libero il semaforo di mutua es lusione 

sem post(&shared.mutex);

 Segnalo he ho aggiunto un arattere 

sem post(&shared.piene);

g
g

= ... perdo un po o di tempo =


for(j=0; j<DELAY WRITER; j++);

return NULL;

 Thread he legge i dati 


void lettore(void in)
f
=

int i, j, k;
= Bu er lo ale in ui metto i aratteri mano a mano
 he vengono letti.

har lo al[LUN+1;
lo al[LUN = 0;

for (i=0; i<2CICLI; i++) f


for(k=0; k<LUN; k++) f
= Controllo he il bu er non sia vuoto =
sem wait(&shared.piene);

 A quisis o la mutua es lusione 

sem wait(&shared.mutex);

 Aggiungo un arattere e aggiorno le strutture dati 

lo al[k = shared.s ritta[shared.primo;


62

shared.primo = (shared.primo+1)%(LUN);

 Libero il semaforo di mutua es lusione 

sem post(&shared.mutex);

 Segnalo he ho letto un arattere 

sem post(&shared.vuote);

g
g
g

= ... perdo un po o di tempo =


for(j=0; j<DELAY READER; j++);

printf("Stringa = %s nn", lo al);

return NULL;

L'esempio e essenzialmente analogo all'esempio , la di erenza sostanziale


e rappresentata dal fatto he il odi e dei thread risulta molto piu snello
poi he molte operazioni sono ompiute impli itamente dalle nuove primitive
asso iate al tipo sem t.

3.3.3 Esempio di ar hitettura Client Server


Il paradigma lient-server si basa sull'assunto he esiste un erto insieme di
entita he ha bisogno di ottenere un erto servizio da un'altra entita e he
esiste un me anismo di omuni azione a s ambio di messaggi. A ogni entita
he ri hiede il servizio viene dato il nome di lient, mentre a quella he lo
o re il nome di server.
Con il termine entita si intende un'ampia asisti a he puo andare da un
host in rete no a un thread. Nel seguito intenderemo in modo impli ito he
lient e server siano dei task o dei thread.
Questa sempli e spiegazione nas onde in realta la omplessita del problema. Una ompli azione he sorge subito e he l'ar hitettura os prospettata
si presta subito ad un atta o, he in gergo te ni o prende il nome di denial
of servi e.
Tale atta o viene portato da un lient he vuole negare, per s opi piu o
meno le iti, l'uso del server a tutti gli altri lient. Quello he il lient deve
fare e ri hiedere al server un servizio valido ma il ui tempo di elaborazione
sia os lungo da blo are, di fatto, il server e quindi renderlo ina essibile a
tutti gli altri lient.
Per evitare tale tipo di atta o il server agis e nel seguente modo :

Si mette in attesa sul anale di omuni azione.


63

All'atto della ri ezione di una ri hiesta si servizio genera un task o un


thread.

Tale task o thread apre un anale di omuni azione on il lient e porta


a termine la ri hiesta.

Eser izio: Si di a per he, on l'ar hitettura appena dis ussa, non e
possibile un atta o di tipo denial of servi e.

3.3.4 Ar hitettura Client-Server


In questo listato e esempli ata un'ar hitettura lient-server. Il server e
naturalmente molto sempli e e si limita ad invertire una stringa he gli viene
passata tramite la memoria ondivisa.

 Programma di esempio di un lient e di un server


 sfruttando i thread e i semafori privati.


#in lude
#in lude
#in lude
#in lude
#in lude

stdio.h>
<semaphore.h>
<pthread.h>
<signal.h>
<string.h>

<

 Lunghezza massima del bu er 

#define LUN 40

 Numero di lient he viene generato 

#define NUM CLIENT 30

stru tf
= bu er he onterra' la stringa =
har bu er[LUN;
= semaforo di mutua es lusione =
sem t mutex;

 Semaforo privato su ui si blo a il lient 


sem t private;
 Puntatore alla zona in ui verra' messo il risultato 
har risultato;
=

gshared;

sem t ri hiesta;
64

 Forward de laration =
void  lient(void );
void server(void );
void realServer(void );
=

int main(void)

pthread t lientID[NUM CLIENT, serverID;


int res, i, j;

 Fase di inzializzazione delle strutture dati 


 Ogni stringa C deve terminare on 0!!!!! 

shared.bu er[0 = 0;

 Fine della fase di inizializzazione delle strutture dati 

 Fase di inizializzazione dei semafori 

if (sem init(&shared.mutex, 0, 1) == -1) f

printf("Non sono rius ito ad inizializzare


shared.mutexnn");

return 1;

if (sem init(&ri hiesta, 0, 0) == -1) f

printf("Non sono rius ito ad inizializzare


shared.mutexnn");
return 1;

 Ora inizia la fase di reazione dei thread 


pthread set on urren y(1+2NUM CLIENT);
=

 Come prima osa reo il server 

res = pthread reate(&serverID, NULL, server, NULL);

if (res 6= 0) f

printf("Non ho potuto reare il server nn");


exit(-1);

for(i=0; i<NUM CLIENT; i++) f


65

res = pthread reate(& lientID[i, NULL, lient, NULL);


if (res 6= 0) f
printf("Non ho potuto reare i lient nn");
for(j=0; j<i; j++) f
pthread kill( lientID[j, SIGKILL);

pthread kill(serverID, SIGKILL);


exit(-1);

 A questo punto aspetto i thread dei lient 

for(i=0; i<NUM CLIENT; i++)

pthread join( lientID[i, NULL);

pthread kill(serverID, SIGKILL);

return 0;

 Codi e del server he genera un nuovo thread ogni volta


 he un nuovo lient fa una ri hiesta. 
void server(void in)
f
=

pthread t tmp;

for(;;) f

sem wait(&ri hiesta);


tmp = pthread reate(&tmp, NULL, realServer, NULL);
if (tmp 6= 0) f
printf("Non sono rius ito a reare un server ....nn");
printf("Se ontinuo sono in deadlo k!!!!!nn");
exit(-1);

g
g

pthread deta h(tmp);

 Server vero he inverte la stringa 


void realServer(void in)
f
=

66

har buf[LUN, risu;


sem t priv;
int i, j, len;

 Come prima osa opio i dati nello spazio lo ale 

priv = shared.private;
str py(buf, shared.bu er);
risu = shared.risultato;
len = strlen(buf);
sem post(&shared.mutex);

 Ora svolgo il lavoro del server 


for (i=len-1; i0; i--) f
=

risu[len-1-i = buf[i;

risu[len = 0;

 A questo punto perdo un po o di tempo 

for(j=0; j<20000000; j++);

sem post(priv);
return NULL;

 Codi e dei vari lient 


void  lient(void in)
f
=

har risu[LUN;
sem t priv;

risu[0 = 0;
if (sem init(&priv, 0, 0) == -1) f
printf("Non sono rius ito ad inizializzare un semaforo
privatonn");
pthread exit(NULL);

sem wait(&shared.mutex);
shared.risultato = risu;
sprintf(shared.bu er, "thread numero %d", (int)pthread self());
shared.private = &priv;
67

sem post(&ri hiesta);


sem wait(&priv);
printf("Thread %d -- Risultato = %snn", (int)pthread self(), risu);
return NULL;

Nell'esempio viene introdotta an he la seguente funzione :


#in lude <pthread.h>
int pthread deta h(pthread t *thread);

Argomenti in ingresso :

thread { Identi atore del thread.


Valore restituito :

0 { In aso di su esso

6= 0

{ In aso di insu esso.

Normalmente il kernel mantiene un erta quantita di informazioni per


ogni thread. Tali informazioni servono, dopo la terminazione del thread, per
permettere ad altri thread di e ettuare una pthread join() su di esso; io
omporta he nessuno e ettua una join su un thread si puo veri are uno
spre o di memoria. La pthread deta h() serve proprio a soe i are he
nessuno e ettuera una join sul thread spe i ato, e he quindi le informazioni
ad esso relative possono essere an ellate direttamente alla sua terminazione.

68

Potrebbero piacerti anche