Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
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 oerte dal sistema
operativo a supporto della programmazione
on
orrente e
ome utilizzarle, e
bene andare a denire in maniera piu formale al
uni
on
etti di base.
int res;
res = sys
all(: : :);
if (res < 0) f
perror("Error
alling sys
all");
exit(-1);
g
=
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.
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
.
un des rittore di pro esso { Il Pro ess IDenti ator, o piu brevemente
<
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
:::
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);
:::
Per evitare di dover s
rivere il
orpo dei due pro
essi nello stesso le,
Unix mette a disposizione la famiglia di funzioni exe
().
<
unistd.h>
Argomenti in ingresso :
7
-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 denite 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
pid t hild;
exit(-1);
<
nella fork");
else if (
hild == 0) f
= A questo punto sono nel glio =
<
<
else f
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 i;
return 0;
10
sys/types.h>
sys/wait.h>
<
<
Argomenti in ingresso :
Valore restituito :
11
res = wait(&status);
if (res < 0) f
= No
hildren to wait for =
perror("Wait failed");
exit(-1);
:::
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 denita 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 denite.
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 eettua 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 eettuare 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 eettua automati amente una wait (nella Sezione 2.2.1,
13
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,
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
<
signal.h>
Argomenti in ingresso :
Valore restituito :
<
signal.h>
int main(void)
:::
15
g
g
:::
<
signal.h>
Argomenti in ingresso :
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 denita:
16
#in lude
<
signal.h>
sa ags { Flags per spe i are il omportamento del sistema durante l'ese uzione dell'handler.
signal.h>
<
sigemptyset(&nuova.sa mask);
17
nuova.sa ags = 0;
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 denis
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)
sigllset { Modi
a la struttura set in modo
he tutti i signal siano attivi.
18
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.
19
#in
lude
#in
lude
<
<
signal.h>
time.h>
Forward de
laration =
stati
void handler (int signo);
=
int se
onds;
int main(void)
sigemptyset(&nuova.sa mask);
nuova.sa ags = 0;
nn");
else if (
hild == 0) f
= A questo punto sono nel task glio =
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();
f
g
se onds++;
21
<
unistd.h>
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
ongurazione 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 eettua
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 eettua una read() su un pipe il
ui ingresso non e identi
ato da nessun des
rittore e
he ha il buer 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
().
#define BUFFSIZE 80
#define DO SIGPIPE
Forward de laration
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;
if (pipe(pipefd) == -1) f
= C'e' stato un errore nell'apertura della pipe =
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);
<
exit(-1);
g
Come ne stringa invio un byte a 0
=
g
g
else f
lose(pipefd[1);
#ifdef DO SIGPIPE
lose(pipefd[0);
#endif
wait(NULL);
exit(0);
g
g
if (lo
al[i == 0)
break;
lo
al[BUFFSIZE-1 = 0;
28
g
g
return 0;
f
g
#in
lude
#in
lude
sys/types.h>
<sys/stat.h>
<
Argomenti in ingresso :
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 IRGRP rea una FIFO a essibile in lettura da parte di tutti gli utenti
S IROTH rea una FIFO a essibile in lettura da parte di tutti gli utenti;
30
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 eettuando
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.
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 manle 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>
<
Argomenti in ingresso :
key { Chiave
he identi
a la struttura di IPC
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 :
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).
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>
<
<
#define PADRE 0
#define FIGLIO 1
har
ampo[9;
= Numero di
aselle libere =
int libere;
int turno;
g tris;
int i;
printf("------");
for (i=0; i<9; i++) f
if ((i%3) == 0)
printf("nnj");
35
g
g
printf("nn------nn");
int main(void)
pid t
hild;
stru
t tris mioCampo;
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
=
for (i=0;i<9;i++)
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);
<
mossa--;
if (mossa == 0) f
mioCampo!
ampo[i = 'o';
break;
g
"Pulis
o" lo s
hermo
=
system(" lear");
stampaCampo(mioCampo! ampo);
sleep(1);
g
In ogni
aso prima di us
ire libero il semaforo
mioCampo!turno = PADRE;
=
return 0;
else f
>
37
<
mossa--;
if (mossa == 0) f
mioCampo!
ampo[i = 'x';
break;
g
"Pulis
o" lo s
hermo
=
system(" lear");
stampaCampo(mioCampo! ampo);
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
dierenza 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.
ID di utente e di gruppo.
ID del thread,
Contesto del pro
essore (Sta
k Pointer, Registri, Program Counter),
Sta
k,
Variabile errno,
Mas
hera dei Signal,
Priorita.
Argomenti in ingresso :
0 { In aso di su esso.
6= 0
<
pthread.h>
Argomenti in ingresso :
Valore restituito :
0 { In aso di su esso.
6= 0
<
pthread.h>
Argomenti in ingresso :
tid { Identi atore del thread sul ui momento di ne i si vuole sin ronizzare.
Valore restituito :
41
0 { In aso di su esso.
6= 0
<
pthread.h>
Argomenti in ingresso :
0 { In aso di su esso.
6= 0
#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
int val;
g shared;
Forward de
laration
void somma1(void );
void somma2(void );
=
int main(void)
int risu;
shared.val = 0;
=
=
43
int i, j;
for(i=0; i<CICLI; i++) f
shared.val++;
=
g
g
return NULL;
int i, j;
for(i=0; i<CICLI; i++) f
shared.val++;
=
g
g
return NULL;
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
eettivamente 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.
Argomenti in ingresso :
0 { In aso di su esso.
6= 0
Argomenti in ingresso :
#in
lude
#in
lude
#in
lude
#in
lude
#in
lude
stdio.h>
pthread.h>
<unistd.h>
<signal.h>
<sys/types.h>
<
<
#define CICLI 10
Lunghezza del buer
#define LUN 50
46
stru t f
int main(void)
<
g
Ogni stringa C deve terminare
on 0!!!!!
=
shared.s
ritta[LUN = 0;
shared.numLetture = 0;
shared.numS
ritture = 0;
g
Fine della fase di
reazione dei thread
=
int i, j, k;
for (i=0; i<CICLI; i++) f
while(1) f
shared.numS
ritture++;
pthread mutex unlo
k(&shared.mutex);
for(j=0; j<DELAY; j++);
48
g
g
return NULL;
int i, j, k;
for (i=0; i<CICLI; i++) f
while(1) f
g
g
g
shared.numS
ritture++;
pthread mutex unlo
k(&shared.mutex);
for(j=0; j<DELAY; j++);
return NULL;
int i;
for (i=0; i<2CICLI; i++) f
while(1) f
g
g
return NULL;
50
Argomenti in ingresso :
Valore restituito :
0 { In aso di su esso
6= 0
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.
51
#in
lude
#in
lude
#in
lude
#in
lude
#in
lude
stdio.h>
<pthread.h>
<unistd.h>
<signal.h>
<sys/types.h>
<
#define CICLI 1
Lunghezza del buer
#define LUN 20
Buer
har s
ritta[LUN+1;
= Variabili per la gestione del buer =
int primo, ultimo, elementi;
= Numero di lettori e s
rittori blo
ati =
int blo
ks
ri, blo
klet;
Forward de
laration
void s
rittore1(void );
void s
rittore2(void );
void lettore(void );
=
int main(void)
52
<
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;
if (res 6= 0) f
53
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
shared.blo
ks
ri++;
pthread
ond wait(&shared.s
rittori, &shared.mutex);
shared.blo ks ri--;
g
Aggiungo un
arattere e aggiorno i vari
ampi
=
g
g
return NULL;
54
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
shared.blo
ks
ri++;
pthread
ond wait(&shared.s
rittori, &shared.mutex);
shared.blo ks ri--;
g
Aggiungo un
arattere e aggiorno i vari
ampi
=
g
g
return NULL;
int i, k, j;
har lo
al[LUN+1;
lo
al[LUN = 0;
if (shared.elementi == 0) f
= Segnalo
he mi sto sblo
ando =
shared.blo
klet++;
pthread
ond wait(&shared.lettore, &shared.mutex);
shared.blo klet--;
g
Leggo un
arattere e aggiorno i vari
ampi
=
g
g
g
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 buer
ir
olare di lunghezza denita
dalla ma
ro LUN, e si blo
ano solo se il buer e gia pieno. L'uni
o thread
he legge
ontinua ad estrarre
aratteri e si blo
a solamente quando il buer
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 ore un nuovo tipo di dato,
sem t
he e un'implementazione del
lassi
o me
anismo semafori
o. Me
56
Argomenti in ingresso :
pro
essi, altrimenti indi
a
he il semaforo deve essere
ondiviso fra piu
pro
essi1 .
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 :
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);
sem { Indirizzo del semaforo su
ui si vuole fare la wait non blo
ante.
Lo standard Posix denis
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 :
3.3.2 Un esempio
In questo esempio vedremo un uso base dell'implementazione dei semafori
dello standard Posix.
#in lude
semaphore.h>
<
58
#in
lude
#in
lude
#in
lude
pthread.h>
signal.h>
<stdio.h>
<
<
#define CICLI 1
Lunghezza del buer
#define LUN 20
Forward de
laration
void s
rittore1(void );
void s
rittore2(void );
void lettore(void );
=
int main(void) f
int res, i;
shared.primo = shared.ultimo = 0;
shared.mutexnn");
return 1;
return 1;
g
Fine della fase di inizializzazione dei semafori
=
if (res 6= 0) f
int i, j, k;
for (i=0; i<CICLI; i++) f
for(k=0; k<LUN; k++) f
= Controllo
he il buer non sia pieno =
sem wait(&shared.vuote);
sem wait(&shared.mutex);
sem post(&shared.mutex);
sem post(&shared.piene);
g
g
return NULL;
61
int i, j, k;
for(i=0; i<CICLI; i++) f
for(k=0; k<LUN; k++) f
= Controllo
he il buer non sia pieno =
sem wait(&shared.vuote);
sem wait(&shared.mutex);
sem post(&shared.mutex);
sem post(&shared.piene);
g
g
return NULL;
int i, j, k;
= Buer lo
ale in
ui metto i
aratteri mano a mano
he vengono letti.
har lo
al[LUN+1;
lo
al[LUN = 0;
sem wait(&shared.mutex);
shared.primo = (shared.primo+1)%(LUN);
sem post(&shared.mutex);
sem post(&shared.vuote);
g
g
g
return NULL;
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.
#in
lude
#in
lude
#in
lude
#in
lude
#in
lude
stdio.h>
<semaphore.h>
<pthread.h>
<signal.h>
<string.h>
<
#define LUN 40
stru
tf
= buer
he
onterra' la stringa =
har buer[LUN;
= semaforo di mutua es
lusione =
sem t mutex;
gshared;
sem t ri
hiesta;
64
Forward de
laration =
void
lient(void );
void server(void );
void realServer(void );
=
int main(void)
shared.buer[0 = 0;
return 1;
if (res 6= 0) f
return 0;
pthread t tmp;
for(;;) f
g
g
66
priv = shared.private;
str
py(buf, shared.buer);
risu = shared.risultato;
len = strlen(buf);
sem post(&shared.mutex);
risu[len-1-i = buf[i;
risu[len = 0;
sem post(priv);
return NULL;
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.buer, "thread numero %d", (int)pthread self());
shared.private = &priv;
67
Argomenti in ingresso :
0 { In aso di su esso
6= 0
68