Sistemi Operativi (M. Cesati)

Documenti analoghi
Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Esercizio sul Monitor. Ponte con utenti grassi e magri 11 Novembre 2013

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Libreria Linux Threads. Threads nel S.O. Linux. Primitive per la gestione dei thread. Portabilità: libreria pthreads (Posix).

Sistemi operativi. Corso di Laurea Triennale in Ingegneria Informatica. Lezione 7 Mutex Condition Esempi di utilizzo

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Corso di Laboratorio di Sistemi Operativi A.A

LABORATORIO DI SISTEMI OPERATIVI

LinuxThreads. LinuxThreads: monitor & variabili condizione

Thread: sincronizzazione Esercitazioni del 16 Ottobre 2009

istruzioni eseguite in ordine predeterminabile in base al codice del programma e dei valori dei dati in ingresso

Laboratorio su Programmazione Concorrente in C. Problemi classici e derivati Dalla Ottava lezione di laboratorio in avanti

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

Laboratorio su Programmazione Concorrente in C. Problemi classici e derivati Dalla Ottava lezione di laboratorio in avanti

ACSO Programmazione di Sistema e Concorrente

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Programmazione di concorrente - Deadlock

Semafori. Semafori classici con i thread POSIX 2

il tipo di parallelismo dipende dal grado di cooperazione

Linuxthreads: esercizio

Sistemi Operativi (M. Cesati)

= PTHREAD_MUTEX_INITIALIZER

Monitor pthreads. Esercizio

Cosa sono i semafori?

Cosa sono i semafori?

Lezione 22: Input/Output e Files

Esercitazione 2! Mutex e semafori POSIX. 3 Novembre 2016

Esercitazione 2 Sincronizzazione con semafori. 31 Ottobre 2013

Sincronizzazione. I semafori Stefano Quer Dipartimento di Automatica e Informatica Politecnico di Torino

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Corso di Laboratorio di Sistemi Operativi A.A

Problema dei Produttori e dei Consumatori

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A Pietro Frasca.

LinuxThreads: I thread nel sistema operativo LINUX: Linuxthreads. LinuxThreads. LinuxThreads

Sistemi Operativi (M. Cesati)

Elementi lessicali. Lezione 4. La parole chiave. Elementi lessicali. Elementi lessicali e espressioni logiche. Linguaggi di Programmazione I

ACSO Programmazione di Sistema e Concorrente

Dispensa 18 CORSO DI PROGRAMMAZIONE A.A CORSO DI LAUREA IN INGEGNERIA E SCIENZE INFORMATICHE CESENA. Laboratori

Il linguaggio C. Puntatori e dintorni

LABORATORIO DI SISTEMI OPERATIVI

Fondamenti di Informatica

I thread nel sistema operativo LINUX: Linuxthreads

Aritmetica dei puntatori

Perche le CPU multicore

Sincronizzazione di thread POSIX

GESTIONE DEI PROCESSI E DEI THREADS IN UN SISTEMA LINUX (PARTE 3) 1

Programmazione di base

Il linguaggio C. Breve panoramica su stdio.h

Programmazione concorrente

Si possono applicare solo a variabili (di tipi interi, floating o puntatori), ma non a espressioni generiche (anche se di questi tipi).

JAVA. import java.util.*; #include <stdio.h> public static class test { int variable; private int variable; int main (int argc, char *argv[]) {

I files (archivi) Il C definisce in stdio.h tre file aperti automaticamente: stdin, stdout e stderr.! Sono ad accesso sequenziale diretto

ELEMENTI DI INFORMATICA. Linguaggio C

Scrittura formattata - printf

LinuxThreads: I thread nel sistema operativo LINUX: Linuxthreads

File. Molto astratta: file ha un nome (ex. Pippo.c), ed altre caratteristiche (dimensione, tipo, etc). Operazioni: copiare, cancellare, eseguire..

Programmazione I (Classe 1)

void char void char @param void int int int int

Lab. di Sistemi Operativi - Esercitazione n 9- -Thread-

Programmazione I (Classe 1) - prova da 12 fu

Programmazione I (Classe 1)

Esercizi. La funzione swapint() primo tentativo

Sistemi operativi Modulo II I semafori 2 Select

I thread nel sistema operativo LINUX: Linuxthreads

Gestione dinamica della memoria

Thread thread lightweight process

Input / Output. Come già detto, input e output sono realizzati in C da funzioni di stdio.h all'interno della libreria standard

Esercitazione 11. Liste semplici

Programmazione I (Classe 1)

Progetto per il Laboratorio di Programmazione Un interprete per il linguaggio PINO. Modulo I: Le tavole dei simboli (TS)

Transcript:

Sistemi Operativi (M. Cesati) Compito scritto del 6 settembre 2013 Nome: Matricola: Corso di laurea: Cognome: Crediti da conseguire: 5 6 9 Scrivere i dati richiesti in stampatello. Al termine consegnare tutti i fogli. Tempo a disposizione: 2 ore. Tenere presente che saranno valutati anche l ordine e la chiarezza dell esposizione. Esercizio 1. Scrivere una applicazione C/POSIX multithread costituita da tre thread che implementano le seguenti procedure: T 1 ) Il primo thread inserisce in un buffer circolare B1 i valori interi x-(x*x*x)%8+1 per x uguale a 0, 1, 2, ecc. Il buffer B1 può contenere al massimo 1024 numeri interi, quindi se necessario il thread T 1 deve bloccare in attesa che venga liberato spazio in esso. T 2 ) Il secondo thread inserisce in un buffer circolare B2 i valori interi x+(x*x)%7 per x uguale a 0, 1, 2, ecc. Il buffer B2 può contenere al massimo 1024 numeri interi, quindi se necessario il thread T 2 deve bloccare in attesa che venga liberato spazio in esso. T 3 ) Il terzo thread scandisce e confronta gli elementi contenuti nei due buffer B1 e B2 in modo sincrono (il primo elemento di B1 con il primo elemento di B2, il secondo con il secondo, ecc.). Se necessario T 3 deve bloccare in attesa che venga inserito un elemento in uno dei buffer. Se per un certo valore di x il valore risultante nei due buffer è identico, T 3 stampa tale valore in standard output. Dopo ogni confronto T 3 rimuove gli elementi corrispondenti dai due buffer. I thread debbono poter eseguire il più possibile in parallelo tra loro, ma si deve evitare ogni possibile race condition dovuta agli accessi ai buffer circolari condivisi. A titolo di esempio, i primi valori stampati dal thread T 3 dovrebbero essere corrispondenti ai valori di x 7, 9, 21, 23, 35, 37, 49, 49, 51, 63, 65, 77, 79, 91, 93,... 6, 8, 20, 22, 34, 36, 48, 49, 50, 62, 64, 76, 78, 90, 92,... Esercizio 2. In riferimento ad un generico sistema operativo moderno, si descrivano in modo esauriente i possibili stati di un processo e gli eventi che causano le transizioni tra tali stati.

Sistemi Operativi (M. Cesati) Esempio di programmi del compito scritto del 6 settembre 2013 Esercizio 1 (prima soluzione) Le strutture di dati principali del programma sono due buffer circolari, ciascuno dei quali è acceduto esclusivamente da due thread, uno in lettura ed uno in scrittura. Un buffer circolare acceduto unicamente da un lettore e da uno scrittore non presenta particolari problemi di race condition. Pertanto, è possibile realizzare una soluzione che non fa uso di alcuna primitiva di sincronizzazione. Svolgiamo l esercizio seguendo un approccio top-down. Iniziamo con la definizione della struttura di dati necessaria per implementare i due buffer circolari: # include <stdio.h> # include < stdlib.h> # include < unistd.h> # include <errno.h> # include < pthread.h> # define cbuf_ size 1025 struct double_circular_buffer int E1; int E2; int S; unsigned int buf1 [ cbuf_ size ]; unsigned int buf2 [ cbuf_ size ]; ; struct double_circular_buffer db; Si noti che la dimensione dei buffer è pari ad un elemento in più di quanto richiesto; infatti, per semplificare la logica di controllo e distinguere facilmente i casi buffer pieno e buffer vuoto dovrà sempre esserci almeno una posizione del buffer libera. I campi E1 e E2 rappresentano la fine dei dati rispettivamente nel primo e nel secondo buffer. Il campo S rappresenta l inizio dei dati in entrambi i buffer: infatti le letture sono realizzate dal thread T 3 e coinvolgono sempre la stessa posizione relativa nei due buffer. Un altra struttura di dati necessaria per il programma è il descrittore del lavoro che deve essere compiuto dai thread T 1 e T 2 :

struct job_ t unsigned int * buf ; int *pe, *ps; unsigned int (* expr )( unsigned int ); ; struct job_ t job1, job2 ; La struttura job t contiene puntatori al buffer circolare su cui dovrà operare il thread; inoltre contiene il puntatore expr ad una funzione che implementa la diversa espressione matematica utilizzata dai thread T 1 e T 2. La funzione main( ) deve creare i due thread T 1 e T 2, poi prosegue eseguendo il lavoro affidato al thread T 3 : int main ( void ) int s; pthread_ t t; db.s = db.e1 = db.e2 = 0; /* Thread T1 acts on buffer b1 and expression exp1 */ job1. buf = db. buf1 ; job1.pe = &db.e1; job1.ps = &db.s; job1. expr = expr1 ; s = pthread_create (&t, NULL, fill_buffer, & job1 ); if (s!= 0) fprintf ( stderr, " Error in pthread_create \n"); /* Thread T2 acts on buffer b2 and expression exp2 */ job2. buf = db. buf2 ; job2.pe = &db.e2; job2.ps = &db.s; job2. expr = expr2 ; s = pthread_create (&t, NULL, fill_buffer, & job2 ); if (s!= 0) fprintf ( stderr, " Error in pthread_create \n"); /* The initial thread plays as T3 */ scan_buffers (& db); return 0; /* never reached */

Le funzioni che implementano le espressioni matematiche dei thread T 1 e T 2 sono molto semplici: unsigned int expr1 ( unsigned int x) return x -(x*x*x) %8+1; unsigned int expr2 ( unsigned int x) return x+(x*x) %7; La funzione fill buffer( ) è comune ad entrambi i thread creati entro main( ) (T 1 e T 2 ). Ha il compito di riempire un buffer circolare con i dati calcolati per mezzo dell espressione matematica indicata: void * fill_buffer ( void *p) struct job_ t * job = ( struct job_ t *) p; unsigned int * buf = job -> buf ; int *pe = job ->pe; volatile int *ps = job ->ps; unsigned int x, v; for (x =0; ; ++x) int ne = (* pe + 1) % cbuf_ size ; v = job -> expr (x); /* controllare se buffer pieno */ while (ne == *ps) ; /* busy wait */ buf [* pe] = v; *pe = ne; /* NOT REACHED */ Nel caso in cui il buffer circolare sia pieno (ossia vi sia una sola posizione libera), la funzione entra in un ciclo busy wait in cui attende che il thread di lettura renda libera una nuova posizione del buffer incrementando la variabile puntata da job->ps. A tale riguardo si noti come questo puntatore venga assegnato ad una variabile locale di tipo volatile int *. La parola chiave volatile informa il compilatore che il contenuto della cella di memoria referenziata dal puntatore può essere modificato da un altro flusso di esecuzione esterno alla funzione. In assenza di volatile, il compilatore potrebbe generare codice ottimizzato che non rileva accessi alla cella e quindi evita di rileggere ad ogni ciclo il valore nella cella di memoria, con conseguente funzionamento erroneo del programma.

Infine la funzione scan buffers( ) implementa il lavoro affidato al thread T 3 : legge gli elementi di entrambi i buffer, controllando i valori coincidenti, e contemporaneamente libera lo spazio occupato: void scan_buffers ( struct double_circular_buffer *db) volatile int * pe1 = &db ->E1; volatile int * pe2 = &db ->E2; for (;;) int S = db ->S; while ( S == * pe1 S == * pe2 ) ; /* busy wait : a circular buffer is empty */ /* check if the two values are equal */ if (db -> buf1 [S] == db -> buf2 [S]) printf ("%u\n", db -> buf1 [S]); /* remove the elements from the buffers */ db ->S = (S + 1) % cbuf_size ; Anche in questo caso si invoca un busy wait nel caso in cui uno od entrambi i buffer circolari sia vuoto, e si utilizza la parola chiave volatile per accedere ai campi che sono aggiornati in modo asincrono dagli altri due thread del programma. Esercizio 1 (seconda soluzione) Una implementazione alternativa dell esercizio consiste nell evitare l utilizzo dei busy wait, facendo in modo di sospendere il thread in attesa che qualche elemento venga aggiunto o rimosso da un buffer circolare. In pratica è possibile utilizzare ad esempio variabili condizione di tipo pthread cond t. Ciò implica anche utilizzare opportuni mutex per sincronizzare l accesso alle variabili condizione. Il codice è pertanto più complesso della soluzione con busy wait, ma anche considerevolmente più efficiente. Il codice di questa implementazione alternativa, senza ulteriori commenti, è il seguente: # include <stdio.h> # include < stdlib.h> # include < unistd.h> # include <errno.h> # include < pthread.h> # define cbuf_ size 1025

struct circular_ buffer pthread_ mutex_ t mtx ; pthread_ cond_ t cnd_ not_ empty ; pthread_ cond_ t cnd_ not_ full ; int S; int E; unsigned int buf [ cbuf_ size ]; ; struct circular_ buffer b1, b2; struct job_ t struct circular_ buffer * buf ; unsigned int (* expr )( unsigned int ); ; struct job_ t job1, job2 ; void scan_ buffers ( struct circular_ buffer * b1, struct circular_ buffer * b2) for (;;) if ( pthread_mutex_lock (&b1 -> mtx )!= 0) perror (" pthread_mutex_lock "); while (b1 ->S == b1 ->E) /* circular buffer b1 empty */ ; if ( pthread_cond_wait (&b1 -> cnd_not_empty, &b1 -> mtx )!= 0) perror (" pthread_cond_wait "); if ( pthread_mutex_unlock (&b1 -> mtx )!= 0) perror (" pthread_mutex_unlock "); if ( pthread_mutex_lock (&b2 -> mtx )!= 0) perror (" pthread_mutex_lock "); while (b2 ->S == b2 ->E) /* circular buffer b2 empty */ ; if ( pthread_cond_wait (&b2 -> cnd_not_empty, &b2 -> mtx )!= 0) perror (" pthread_cond_wait "); if ( pthread_mutex_unlock (&b2 -> mtx )!= 0) perror (" pthread_mutex_unlock "); /* Check if the two values are equal */

if (b1 -> buf [b1 ->S] == b2 -> buf [b2 ->S]) printf ("%u\n", b1 -> buf [b1 ->S]); /* remove the elements from the buffers */ b1 ->S = (b1 ->S + 1) % cbuf_size ; b2 ->S = b1 ->S; if ( pthread_cond_signal (&b1 -> cnd_not_full )!= 0) perror (" pthread_cond_signal "); if ( pthread_cond_signal (&b2 -> cnd_not_full )!= 0) perror (" pthread_cond_signal "); void * fill_buffer ( void *p) struct job_ t * job = ( struct job_ t *) p; struct circular_ buffer * cb = job - > buf ; unsigned int x, v; int ne; for (x =0; ; ++x) v = job -> expr (x); if ( pthread_mutex_lock (&cb -> mtx )!= 0) perror (" pthread_mutex_lock "); ne = (cb ->E + 1) % cbuf_size ; /* controllare se buffer pieno */ while (ne == cb ->S) /* buffer pieno */ if ( pthread_cond_wait (&cb -> cnd_not_full, &cb -> mtx )!= 0) perror (" pthread_cond_wait "); if ( pthread_mutex_unlock (&cb -> mtx )!= 0) perror (" pthread_mutex_unlock "); cb -> buf [cb ->E] = v; cb ->E = ne; if ( pthread_cond_signal (&cb -> cnd_not_empty )!= 0) perror (" pthread_cond_signal "); /* NOT REACHED */

void initialize_circular_buffer ( struct circular_buffer *cb) cb ->S = cb ->E = 0; if ( pthread_mutex_init (&cb ->mtx, NULL )!= 0) fprintf ( stderr, " Error in pthread_mutex_init \n"); if ( pthread_cond_init (&cb -> cnd_not_empty, NULL )!= 0 pthread_cond_init (&cb -> cnd_not_full, NULL )!= 0) fprintf ( stderr, " Error in pthread_cond_init \n"); unsigned int expr1 ( unsigned int x) return x -(x*x*x) %8+1; unsigned int expr2 ( unsigned int x) return x+(x*x) %7; int main ( void ) int s; pthread_ t t; initialize_circular_buffer (& b1); initialize_circular_buffer (& b2); /* Thread T1 acts on buffer b1 and expression exp1 */ job1. buf = &b1; job1. expr = expr1 ; s = pthread_create (&t, NULL, fill_buffer, & job1 ); if (s!= 0) fprintf ( stderr, " Error in pthread_create \n"); /* Thread T2 acts on buffer b2 and expression exp2 */ job2. buf = &b2; job2. expr = expr2 ; s = pthread_create (&t, NULL, fill_buffer, & job2 ); if (s!= 0) fprintf ( stderr, " Error in pthread_create \n"); /* The initial thread plays as T3 */ scan_buffers (&b1, &b2); return 0; /* never reached */