1.6: Processi Concorrenti



Documenti analoghi
Sincronizzazione e comunicazione tra processi in Unix. usati per trasferire ad un processo l indicazione che un determinato evento si è verificato.

Capitolo 7: Sincronizzazione

Monitor. Introduzione. Struttura di un TDA Monitor

Pronto Esecuzione Attesa Terminazione

Inter Process Communication. Laboratorio Software C. Brandolese

Sistemi Operativi. Lezione 7 Comunicazione tra processi

Computazione multi-processo. Condivisione, Comunicazione e Sincronizzazione dei Processi. Segnali. Processi e Threads Pt. 2

I/O su Socket TCP: read()

Sistemi Operativi. Lez. 13: primitive per la concorrenza monitor e messaggi

Realizzazione di Politiche di Gestione delle Risorse: i Semafori Privati

SISTEMI OPERATIVI. Sincronizzazione dei processi. Domande di verifica. Luca Orrù Centro Multimediale Montiferru 30/05/2007

Sistemi Operativi (modulo di Informatica II) I processi

IPC System V. Code di messaggi

Il problema del produttore e del consumatore. Cooperazione tra processi

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

Java Virtual Machine

Il costrutto monitor [Hoare 74]

Sistemi Operativi (modulo di Informatica II)

1. Che cos è la multiprogrammazione? Si può realizzare su un sistema monoprocessore? 2. Quali sono i servizi offerti dai sistemi operativi?

Modello dei processi. Riedizione delle slide della Prof. Di Stefano

Corso di Sistemi di Elaborazione delle informazioni

Organizzazione Monolitica

Gestione dei processi

Il costrutto monitor [Hoare 74]

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione.

Thread: sincronizzazione Esercitazioni del 09 Ottobre 2009

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Meccanismi di sincronizzazione: Semafori e Monitor

Sistemi Operativi. Processi GESTIONE DEI PROCESSI. Concetto di Processo. Scheduling di Processi. Operazioni su Processi. Processi Cooperanti

Esempio produttori consumatori. Primitive asincrone

CREAZIONE PROCESSI IN UNIX 20

ESERCIZI DI PROGRAMMAZIONE C IN AMBIENTE UNIX

Drivers. Introduzione Tipologie Struttura Interazione con il kernel

FONDAMENTI di INFORMATICA L. Mezzalira

Comunicazione. La comunicazione point to point e' la funzionalita' di comunicazione fondamentale disponibile in MPI

Sistemi Operativi Esercizi Sincronizzazione

GESTIONE INFORMATICA DEI DATI AZIENDALI

Programmazione multiprocesso

Introduzione. Meccanismi di sincronizzazione: Semafori e Monitor. Semafori - Definizione. Semafori - Descrizione informale

AXO. Operativo. Architetture dei Calcolatori e Sistema. programmazione di sistema

Esercizio sulla gestione di file in Unix

Esercitazione [5] Input/Output su Socket

CAPITOLO 7 - SCAMBIO DI MESSAGGI

Java threads (2) Programmazione Concorrente

Corso di Sistemi Operativi Ingegneria Elettronica e Informatica prof. Rocco Aversa. Raccolta prove scritte. Prova scritta

Programmazione concorrente in Java. Dr. Paolo Casoto, Ph.D

SOMMARIO Coda (queue): QUEUE. QUEUE : specifica QUEUE

I THREAD O PROCESSI LEGGERI Generalità

GESTIONE DEI PROCESSI

Sistemi Operativi L-A. Esercizi 14 Giugno Esercizio monitor

Processi UNIX. I Processi nel SO UNIX. Gerarchie di processi UNIX. Modello di processo in UNIX

Per scrivere una procedura che non deve restituire nessun valore e deve solo contenere le informazioni per le modalità delle porte e controlli

CAPITOLO 27 SCAMBIO DI MESSAGGI

POSIX - Gestione dei Segnali. E.Mumolo, DEEI mumolo@units.it

1 Processo, risorsa, richiesta, assegnazione 2 Concorrenza 3 Grafo di Holt 4 Thread 5 Sincronizzazione tra processi

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

Processi. Laboratorio Software C. Brandolese

Il Sistema Operativo

dall argomento argomento della malloc()

Sistema Operativo. Fondamenti di Informatica 1. Il Sistema Operativo

ASPETTI GENERALI DI LINUX. Parte 2 Struttura interna del sistema LINUX

Laboratorio di Sistemi Operativi

CAP. 6: Nucleo del sistema operativo (La gestione dei processi)

Esercitazione [8] Pipe e FIFO

System call fcntl e record locking

Sistemi Operativi. 3 LEZIONE PROCESSI CORSO DI LAUREA TRIENNALE IN INFORMATICA. Sistemi Operativi 2007/08

ISTITUTO TECNICO INDUSTRIALE STATALE LA GESTIONE DEI FILE DI TESTO IN C++

SISTEMI OPERATIVI 14 settembre 2015 corso A nuovo ordinamento e parte di teoria del vecchio ordinamento indirizzo SR

ESERCIZIO 1 (b) Dove è memorizzato il numero del primo blocco del file? Insieme agli altri attributi del file, nella cartella che contiene il file.

Corso sul linguaggio Java

MODELLO AD AMBIENTE GLOBALE

Introduzione alla programmazione in C

INFORMATICA 1 L. Mezzalira

SISTEMI OPERATIVI. Prof. Enrico Terrone A. S: 2008/09

Il sistema di I/O. Hardware di I/O Interfacce di I/O Software di I/O. Introduzione

LA SINCRONIZZAZIONE TRA PROCESSI

Università di Torino Facoltà di Scienze MFN Corso di Studi in Informatica. Programmazione I - corso B a.a prof.

Lab. di Sistemi Operativi - Esercitazione n 7- -Gestione dei processi Unix-

Sommario. G. Piscitelli

STRUTTURE DEI SISTEMI DI CALCOLO

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino

Introduzione al Linguaggio C

Corso di Linguaggi di Programmazione

Esercitazione finale per il corso di Sistemi Operativi (A.A. 2004/2005)

Strutture. Strutture e Unioni. Definizione di strutture (2) Definizione di strutture (1)

Lo scheduler di UNIX (1)

Le system call: fork(), wait(), exit()

Transcript:

1.6: Processi Concorrenti Programmi concorrenti Chiamate di sistema Unix per il controllo di processi Creazione, terminazione di processi Sincronizzazione sulla terminazione, segnalazione di eventi Comunicazione Esempi di codice Sincronizzazione tra processi Mutua esclusione Produttore consumatore Primitive di sincronizzazione; semafori Implementazione 1.6.1

Programmi Concorrenti Programmi: Sequenziali singolo processo Concorrenti più processi (o thread) che competono tra loro per risorse di sistema, si sincronizzano, comunicano tra loro. Più versatili ed efficienti Applicazioni più complesse e sofisticate Requisiti: normale linguaggio sequenziale + primitive (chiamate di sistema o API) per creazione/terminazione processi sincronizzazione/comunicazione/segnalazione tra processi 1.6.2

Programmi Concorrenti Esempi: P 0 (sh) P 0 P 1 (ls) P 1 P 2 P 3 P 4 P 0 (sh) Sincronizzazione sulla terminazione Comunicazione dati P 0 1.6.3

Chiamate di Sistema Unix pid = fork(); creazione crea nuovo processo (child figlio), duplicando quello originale (parent padre) ritorna al figlio: 0 al padre: il PID del figlio appena creato in caso di errore, 1 (al padre) il processo figlio eredita tutto l'ambiente del padre, compresi i canali aperti (tutto tranne il PID) il figlio si trova come se avesse eseguito tutto il programma fino al fork, come il padre; continua ad eseguire (indipendentemente) N.B. il figlio riceve una copia dell'ambiente del padre (programma, dati etc.); non c'è condivisione di dati tra padre e figlio. es.: pid = fork(); if(pid diverso da 0) { /* padre */ if(pid uguale a 1) { errore(); exit(1); }... } else { 1.6.4

Chiamate di Sistema Unix (Cont.) exit(code); terminazione termina il processo flush dell'i/o pending (stdout etc.), chiude tutti i file, esce trasmette code al Kernel (codice d'uscita exit code) convenzione: code = 0 OK ; code 0 errore (vari codici possibili) non ritorna mai pid = wait(status_address); sincronizzazione su terminazione aspetta finchè uno dei processi figli termina (sincronizzazione) ritorna il PID del primo figlio che ha terminato se status_address non è nullo, ritorna anche, nell'intero puntato, lo stato d'uscita del figlio (exit code e altro). execl(path, name, arg1,...); sostituisce, nel processo corrente, un nuovo programma all'attuale, e lo esegue path è il percorso del file eseguibile, name è il nome del programma, arg1 è il primo argomento, etc. il processo rimane lo stesso, quindi tutto l'ambiente è inalterato "viaggio senza ritorno": se c'è un valore di ritorno, è 1 (impossibile eseguire il nuovo programma) 1.6.5

Esempio di Codice 1 Combinazione tipica delle chiamate viste (pseudo codice): pid = fork(); if(pid diverso da 0) { } else { /* processo padre */ if(pid uguale a 1) error_exit("cannot fork");... wait(status_address); excode = (estrai codice da status); exit(excode); /* esce col codice del figlio */ /* processo figlio */ /* eventuale ridirezione dell'i/o */ execl("/usr/local/bin/program", "program", " x", "file", NULL); 1.6.6

Chiamate di Sistema Unix (Cont.) kill(pid, sig); segnalazione invia il segnale sig al processo con PID pari a pid (segnalazione: comunicazione di evento asincrono) processo ricevente: stesso UID del processo mittente (salvo se mittente è root) segnale: provoca interrupt software nel processo ricevente azione di default all'interrupt: a) exit b) core dump + exit [*] SIGINT (2) intr char. da terminale SIGQUIT (3) quit char. da terminale [*] SIGKILL (9) terminazione forzata (non intercettabile) SIGTERM (15) terminazione software (default per kill(1)) N.B. la chiamata di sistema signal(sig, func) permette di predisporre un'azione specifica (la chiamata alla funzione func) al ricevimento del segnale sig. 1.6.7

Esempio di Codice 2 prog() { pid = fork(); if(pid non uguale a 0) { /* processo padre */ print("pid processo figlio = ", pid); print("attesa 10 secondi"); sleep(10); /* attendi 10 sec. senza fare nulla */ print("terminazione del processo figlio"); kill(pid, SIGTERM); /* segnale di terminazione al processo "pid" */ exit(0); } else { /* processo figlio */ print("processo figlio inizia"); pid = getpid(); /* ottengo il mio proprio identificativo */ loop { print("processo figlio", pid, "lavora"); sleep(1); 1.6.8

Come lavora la shell di Unix shell() { loop { write(1, "$ ", 2); /* prompt 2 caratteri allo stdout */ read(0, line, SIZE); /* leggi riga da stdin */ parse(line); /* separa i token; applica i vari operatori ($, *,?, [ ], etc.) */ pid = fork(); if(pid non uguale a 0) { /* processo padre: la shell originaria */ if( line non termina con '&' ) { } wait(status_address); excode = (estrai codice d'uscita da status); /* tieni conto di excode per istruzioni di controllo della shell: if, while, etc. */ } else { /* processo figlio */ redirect(); /* applica ridirezioni di stdin, stdout etc. (operatori <, >, >> etc.) */ } execl(file, program, args); /* esegui il comando nel processo figlio */ 1.6.9

Chiamate di Sistema Unix (Cont.) pipe(fdc); comunicazione/sincronizzazione è il più semplice (e più limitato) meccanismo di comunicazione dati tra processi stabilisce un canale di comunicazione tra processi, che può essere condiviso con i processi figli (che ereditano i canali aperti) il canale è unidirezionale fdc[0] serve per leggere dal pipe, fdc[1] per scrivere il pipe è implementato come un canale di trasmissione di tipo FIFO, di dimensione finita (di solito 4 Kbyte) sincronizzazione automatica di processi scrittori e lettori, che vanno a dormire in caso di pipe pieno/vuoto EOF fdc[1] (0 byte letti) ritornato dalla read() al lettore se il pipe è chiuso in scrittura fdc[1] fdc[1] N.B. questa descrizione è incompleta per un uso reale. Padre Padre Figlio fdc[0] fdc[0] fdc[0] 1.6.10

Esempio di Codice 3 Padre (produttore) fdc[1] fdc[0] Figlio (consum.) stdout file sel file pattern grep pattern 1.6.11

Esempio di Codice 3 (Cont.) sel(file, pattern) { pipe(fdc); pid = fork(); if(pid diverso da 0) { /* padre produttore */ ifd = open(file, read only); loop(nread = read(ifd, buffer, 1024); finchè nread diverso da 0) write(fdc[1], buffer, nread); close(fdc[1]); /* così il figlio troverà EOF dopo lo svuotamento della pipe */ close(ifd); wait(status_address); /* sincronizzati sulla terminazione di grep */ code = (estrai exit code da status); exit(code); } else { /* figlio consumatore */ redirect(fdc[0] sul f.d. 0, cioè lo stdin); execl("/usr/bin/grep", "grep", pattern); 1.6.12

Sincronizzazione tra Processi Due o più processi interagiscono tra loro quando si contendono l'uso di risorse, permanenti o consumabili. Due paradigmi di base: Mutua esclusione Produttore consumatore Tutti i casi sono riconducibili a produzione e consumo di risorse consumabili. Vincolo di sincronizzazione tra P 1 e P 2 : "P 1 è il produttore di una risorsa consumabile R e P 2 è il consumatore di R". R è una risorsa software creata ad hoc dal Kernel. 1.6.13

Mutua Esclusione Paradigma della mutua esclusione: risorsa permanente R contesa da più processi P 1... P n garantire che Ad ogni istante vi sia al max. un processo Pi che occupi R (Mutua Esclusione) Ogni processo richiedente ottenga l'uso di R entro un intervallo limitato di tempo (Attesa Limitata) Generalizzazione: esecuzione di una parte di codice che deve gestire dati in modo esclusivo problema della sezione critica. L'accesso concorrente a dati condivisi può provocare un'inconsistenza dei dati stessi, se non si adotta una disciplina di accesso. Caso tipico: quando si vuole rendere seriale una risorsa permanente del sistema (area RAM, disk controller, etc.) 1.6.14

Problema della Sezione Critica N processi che si contendono l'uso di dati condivisi Ogni processo ha una parte di codice, chiamata sezione critica, nella quale accede ai dati condivisi. Problema della sezione critica garantire che quando un processo P i sta eseguendo codice nella propria sezione critica, nessun altro processo possa fare altrettanto trovare un protocollo adeguato. Struttura del generico processo P i for(;;) { entry section critical section exit section non critical section 1.6.15

Produttore Consumatore Paradigma produttore consumatore: Risorse consumabili (dati) R prodotte in loop da uno o più P p e consumate da uno o più P c Dati normalmente trasmessi tramite un buffer di dimensione finita (canale di comunicazione a capacità limitata) problema del buffer limitato (bounded buffer problem); talvolta il canale è concettualmente a capacità illimitata. Il processo consumatore va comunque a dormire nel caso di risorsa non disponibile (canale vuoto) Il processo produttore va a dormire, solo nel caso di capacità limitata, se il canale è pieno. 1.6.16

Produttore Consumatore (Cont.) Esempio tipico: due processi comunicano attraverso un buffer comune, di dimensione limitata Dati condivisi: #define N...; typedef... item; item buffer[n]; int in = 0, out = 0,counter = 0; /* in: next free slot in buffer out: first full slot */ 1.6.17

Produttore Consumatore (Cont.) Processo produttore: item nextp; for(;;) { } produce an item in nextp; while ( counter == N ) /* (while buffer full) */ no op; buffer [in] =nextp; in = ++in % N; ++counter; 1.6.18

Produttore Consumatore (Cont.) Processo consumatore: item nextc; for(;;) { } while ( counter == 0 ) /* (while buffer empty) */ no op; nextc = buffer [out]; out = ++out % N; counter; consume the item in nextc; Problema: a causa dell'esecuzione concorrente, le istruzioni: ++counter counter 1.6.19

Produttore Consumatore (Cont.) Varianti: Un produttore, un consumatore es. 1: pipe (canale a capacità limitata) es. 2: processo che legge caratteri da tastiera (canale concettualmente a capacità illimitata). Più produttori, un consumatore es.: spooler di stampa Più produttori, più consumatori es.: programma concorrente con più processi che eseguono in multiprogrammazione la stessa funzione. Il server HTTP Apache è normalmente configurato in modo da preallocare da 4 a 10 processi server in parallelo, per essere pronto a picchi di carico. 1.6.20

Hardware di Sincronizzazione L'hardware può aiutare a risolvere il problema dell'atomicità. Istruzione test+modify di una variabile, in modo atomico: boolean TestAndSet(boolean target) { boolean result = target; target = TRUE; return result; } Dati condivisi: boolean lock = FALSE; Processo P i for(;;) { while (TestAndSet(lock)) no op; critical section lock = FALSE; non critical section 1.6.21

Semaforo I problemi visti diventano più complessi quando si estendono a un numero arbitrario di processi concorrenti Occorre disporre di uno strumento di sincronizzazione di uso generale. Mutua esclusione di risorsa permanente R: può ottenersi associando risorse consumabili X R ; ogni processo acquisisce una X R prima di utilizzare R rilascia X R dopo aver utilizzato R. X R : tipo di risorsa fittizia generata dal Kernel, solo per regolare il traffico di processi: affluiscono verso la risorsa permanente R (acquisizione di X R ) se ne distaccano (rilascio di X R ) 1.6.22

Primitive di Sincronizzazione lock() / unlock(): primitive di implementazione di semafori (la risorsa consumabile). Risolvono qualunque problema di mutua esclusione. Pseudo codice: lock(x) { loop(x non è disponibile) aspetta; consuma X; } unlock(x) { } produci X; /* sblocca eventuale processo in attesa nella chiamata a lock() */ 1.6.23

Primitive di Sincronizzazione (Cont.) send() / receive(): primitive di implementazione di comunicazione sincronizzata di messaggi (la risorsa consumabile). Risolvono problemi di tipo produttore consumatore. Pseudo codice (caso di canale a capacità limitata): send(m,dest) { loop(canale pieno) aspetta; inserisci <m,dest> nel canale; /* sblocca eventuale processo in attesa nella chiamata a receive() */ } receive(m,mitt) { loop(canale vuoto) aspetta; 1.6.24

Esempio: Sezione Critica di n Processi Variabile condivisa: semaphore mutex = 1; /* mutex: mutual exclusion */ Processo P i for(;;) { wait(mutex); critical section signal(mutex); non critical section } 1.6.25

Implementazione dei Semafori Nello pseudo codice visto per lock()/unlock() (ma anche per send()/receive() ), l'istruzione "aspetta" può significare in pratica due cose: No op (loop continuo, ovvero busy waiting; impegno di CPU) > spinlock (spin while locked) Vai a dormire (il processo passa in stato Waiting; sarà svegliato successivamente; non impegna CPU) semaforo bloccante In generale, molto più adatto ad ambienti multitasking 1.6.26

Implementazione dei Semafori (Cont.) Un semaforo spinlock è normalmente una variabile intera, a cui si accede esclusivamente tramite due operazioni atomiche: wait (S): while (S <= 0) S; no op; signal (S): ++S; Può assumere due valori (semaforo binario): 0 risorsa occupata (semaforo bloccato) 1 risorsa diponibile (semaforo libero) 1.6.27

Implementazione dei Semafori (Cont.) Implementazione a livello Kernel di un semaforo bloccante: Semaforo definito come un record (struttura di dati): struct semaphore { int value; process *proclist; /* puntatore a lista di Process Descriptor */ }; Basato su due semplici operazioni: sleep() sospende il processo che la invoca (il processo va a dormire) wakeup(p) riprende l'esecuzione di un processo sospeso P (il processo vene risvegliato). N.B. queste due operazioni possono essere implementate facilmente in qualunque sistema multitasking; in particolare, Unix dispone di funzioni sleep() e wakeup() nel Kernel (N.B. nulla a che vedere con la funzione 1.6.28

Implementazione dei Semafori (Cont.) Le primitive di semaforo possono ora definirsi così: wait(s): if ( S.value < 0) signal(s): if (S.value++ < 0) { } add this process to S.proclist; sleep(); { } remove a process P from S.proclist; wakeup(p); Diversamente dall'implementazione vista in precedenza, questo semaforo può assumere valori negativi (semaforo contatore); questi corrispondono al numero di processi in attesa della risorsa. 1.6.29

Sezione Critica di n Processi: Scenario Sem.value Resource (Free, Busy) 1 F P1: wait() 0 P1: go B... P2: wait() 1 P2: sleep() P3: wait() 2 P3: sleep()... P1: signal() 1 F P1: wakeup(p2) P2: go B P2: signal() 0 F P2: wakeup(p3) P3: go B 1.6.30

Sincronizzazione Generica: Scenario Exegui B in P j solo dopo aver eseguito A in P i Si usa un semaforo flag initializzato a 0 Codice: P i P j...... A signal(flag) wait(flag) B 1.6.31

Implementazione in Unix Unix System V: IPC (InterProcess Communication) insieme di chiamate di sistema per sincronizzazione e comunicazione tra processi Comprende: a) code di messaggi b) memoria condivisa c) semafori Implementato ormai in tutti i tipi di Unix, incluso Linux Un esempio di implementazione di lock() / unlock() in Unix a livello utente: Sfrutta l'apertura di file, in modo esclusivo open(file, O_CREAT+O_EXCL): ha successo solo se il file non esiste (e viene dunque creato ex novo) Un semaforo = un file; file esiste: risorsa occupata; file non esiste: risorsa libera. 1.6.32

Esempio di Codice 4 /* name: nome del file, cioè del semaforo */ lock(name) { loop { fd = open(name, O_CREAT+O_EXCL); if(errore: il file esiste già) sched_yield(); else if(altro tipo di errore) return errore; else return fd; /* file creato in modo esclusivo = risorsa acquisita; restituisci descrittore del semaforo */ } } 1.6.33

Esempio di Codice 4 (Cont.) /* name: nome del file, cioè del semaforo; fd: descrittore del semaforo */ unlock(name, fd) { close(fd); if(errore) return errore; /* uso errato del semaforo */ else { unlink(name); /* file rimosso = risorsa rilasciata; da questo momento può essere ricreato tramite lock() */ return OK; } } 1.6.34

Problemi di sincronizzazione L'uso delle primitive viste può portare a problemi in caso di errori di programmazione (uso improprio delle primitive) Stallo (deadlock): due o più processi attendono indefinitamente un evento che può essere causato solo da un altro dei processi in attesa. Esempio: processi P A e P B ; semafori R 1 e R 2 P A lock(r 1 ); lock(r 2 );... unlock(r 1 ); unlock(r 2 ); P B lock(r 2 ); lock(r 1 );... unlock(r 2 ); unlock(r 1 ); Soluzione: acquisire semafori in un ordine prefissato (e rilasciarli nell'ordine inverso). 1.6.35

Problemi di sincronizzazione (Cont.) Blocco indefinito (starvation): uno o più processi possono non essere mai rimossi dalla coda dei processi in attesa di acquisizione di un semaforo, perchè altri processi in attesa hanno sempre maggior priorità. Ciò può avvenire ad esempio se la coda è gestita in ordine LIFO (Last In, First Out). Problema analogo a quello già visto nel caso dello scheduling di CPU con priorità 1.6.36