










Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Dispensa sui processi con introduzione alla programmazione concorrente nel linguaggio C.
Tipologia: Dispense
1 / 18
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!











4 A INF
Fabrizio Cafolla 28/04/
Introduzione sui processi e la programmazione concorrente su S.O. Linux in linguaggio C. Utilizzo di costrutti Fork-Join e Cobegin-Coend.
Pagina 2
Pagina 4
FCFS ( First Come First Served ) esegue i processi nello stesso ordine in cui essi vengono avviati dal sistema. Il primo processo ad essere eseguito è esattamente quello che per primo richiede l'uso della CPU. Quelli successivi vengono serviti non appena questo ha terminato la propria esecuzione. Questo tipo di algoritmo è molto semplice da implementare ma è anche poco efficiente, almeno considerando il tempo medio d'attesa.
Round-Robin è un particolare algoritmo che esegue i processi nell'ordine d'arrivo, come l' FCFS, ma ad ogni processo assegna un quanto di tempo in base alla grandezza del processo stesso, se un processo termina il quanto di tempo prima ancora di essere terminato viene spostato nello stato di attesa per poi riandare nella pila FCFS.
Le prestazioni di quest'algoritmo sono dunque influenzate dal tempo medio d'attesa sebbene consenta a tutti i processi di ottenere il controllo della CPU ed evita quindi il problema dell'attesa delle risorse HW e SW. È inoltre da tenere in considerazione l'impatto dovuto ai frequenti cambi di contesto (context switch) effettuati.
Priorità è un tipo di algoritmo che assegna a ogni processo un numero intero che corrisponde al livello di priorità con il quale deve essere eseguito. I processi sono inseriti in una coda dove lo scheduler in base alla priorità massima sceglierà il processo da eseguire prima, non tiene conto del tempo di esecuzione del processo.
La priorità viene assegnata dal S.O. in base al tipo di richiesta:
Interna al sistema : ovvero processi di sistema che vengono lanciati dal S.O. e la priorità viene calcolata sulla base delle caratteristiche e del comportamento del processo da eseguire. Esterna al sistema : processi lanciati dall' utente, il S.O. in base a dei criteri assegna la priorità.
L' algoritmo di priorità può essere di tipo pre-emptive ovvero che permette l'interruzione del processo che è in esecuzione, o di tipo non pre-emptive che non permette l'interrupt.
Esistono altri tipi di algoritmi Scheduling come SJF , MLFQ.
Pagina 5
La classificazione dei processi si riferisce al modo in cui un processo evolve.
Indipendenti : Sono quei processi che evolvono in modo autonomo, senza necessità di comunicare con altri processi. Cooperanti : Sono quei processi che evolvono solamente comunicando con altri processi, scambiandosi informazioni necessarie per l' evoluzione del processo. Competitivi : Sono quei processi che evolvono in modo indipendente, ma che entrano in conflitto con altri processi per l' utilizzo di risorse o dati.
Figura 1 Stati dei processi
Un processo è un' entità dinamica che si può trovare in cinque stati differenti.
New (nuovo) è lo stato in cui si trova un processo appena creato. Ready (pronto) è lo stato per cui il processo viene inserito in base allo scheduler nella lista dei processi pronti detta RL(Ready List). Running (esecuzione) è lo stato in cui un processo viene prima scelto dalla RL in base all'algoritmo di scheduling, una volta scelto viene eseguito, se l' esecuzione è terminata passa allo stato termined. Se non è terminato può tornare nello stato di ready per via di un' interrupt, oppure se ha necessita di risorse o informazioni passa allo wait. Waiting (attesa) è lo stato in cui il processo ha bisogno di risorse o informazioni per poter evolvere, oppure ha finito il suo time slice(tempo di esecuzione). Lo stato wait ha una lista detta WS (Waiting List) dove il processo viene inserito per poi passare nuovamente allo stato ready. Terminated (terminato) quando è finita la sua esecuzione.
Pagina 7
La programmazione concorrente nacque per sfruttare al meglio le risorse hardware, successivamente trovò utilizzo per aumentare la velocità di esecuzione delle applicazioni poiché da un singolo programma possono nascere N processi in cui ci sarà un padre e a esso saranno collegati i processi figli. Questa caratteristica permette di diminuire i tempi di esecuzione, perchè scompone un programma in N processi, ed ogni processo svolge blocchi di istruzioni differenti.
Perché è importante aumentare la velocità di esecuzione di un’applicazione? Migliorare i tempi di risposta e quindi l’interattività con l’utente. Risparmiare energia (perché aumenta l’efficienza). Perché da la possibilità di scomporre in più sotto parti problemi complessi.
Processo Padre è quel processo che crea dei processi figli a partire dal suo codice, conosce il loro PID cosi da poter comunicare e sincronizzare la loro esecuzione.
Processo Figlio è quel processo che è stato creato a partire da un processo padre tramite dei costrutti, è una copia identica del padre solamente che avrà dei compiti differenti da esso, il suo PID è diverso da quello del padre.
I processi orfani sono tutti quei processi che sono stati creati a partire da un processo padre (es. processo1) che per qualche " evento " termina prima della conclusione dei processi figli (es. processo2-3) in questi casi Linux assegna ai processi rimasti orfani un padre che per convenzione è init.
Pagina 8
Un S.O. può essere definito :
Sistema sequenziale : quando i processi vengono eseguiti uno per volta e non si verifica alcuna forma di interazione tra essi durante l'esecuzione. Sistema concorrente :é un modello avanzato che garantisce il parallelismo di processi in esecuzione, questo sistema rispetta la regola che ogni processo pur lavorando in parallelo con altri venga eseguito come se fosse in un sistema sequenziale.
La programmazione concorrente ovviamente può avvenire solamente su sistemi concorrenti, ovvero S.O. che permetto di operare con il parallelismo reale o virtuale.
Parallelismo Virtuale : nel caso di sistemi multiprocessore* dove si possono eseguire parallelamente un numero di processi pari al numero dei processori(core).
Parallelismo Reale : nel caso di sistemi monoprocessore dove è possibile l' esecuzione di un singolo processo in un certo periodo, ma con la possibilità di alternare l'esecuzione di più processi, cosi da elaborare più processi apparentemente in contemporanea.
*Il fenomeno di multitasking si riferisce alla possibilità di svolgere contemporaneamente più processi, questo si verifica quando si ha un multiprocessore ovvero una CPU con più di un core, a sua volta il core può generare uno o due tread in base al tipo di CPU.
Pagina 10
Il costrutto fork-join è utilizzato per scrivere programmi concorrenti poiché permette la creazione(fork) di un processo figlio da parte del padre e successivamente il loro ricongiungimento(join).
Il processo figlio è la copia del processo padre quindi ha tutte le istruzioni successive al richiamo della funzione fork(), questo vuol dire che i processi sono identici, quindi il programmatore deve prevedere dei controlli sul PID ed in base a quello far svolgere istruzioni differenti per ogni processo figlio. Quando si creano processi figli essi vengono eseguiti in maniera parallela(reale o virtuale) ma comunque sia ogni processo viene eseguito in modo sequenziale e in base all'algoritmo di scheduling la CPU esegue N istruzioni del processo, è possibile che vengano eseguite in modo alternato istruzioni di un processo padre e di un processo figlio. Il costrutto fork-join viene utilizzato in linguaggio C su S.O Linux e su Mac OSx, mentre per Windows il corrispettivo di fork-join è CreateProcess che ha le stesse funzionalità.
La fork determina la creazione di UN nuovo processo(B), che viene eseguito parallelamente al processo padre.
La join permette di ricongiungere due (o più) processi figli al proprio padre(sincronizzazione).
Pagina 11
Linux per manipolare i processi utilizza delle funzioni già implementate, le principali sono:
creare processo figlio, restituisce un numero di tipo pid_t (è un TDA, num. intero). istruzione fork(); terminare processo , restituisce un codice(è un numero) di terminazione al processo padre. istruzione exit(); sincronizzazione , corrisponde alla join serve a far attendere il padre fino termine del processo figlio. istruzione wait();
Fork() è una funzione che quando viene richiamata in un programma crea a partire da esso un processo figlio che è la copia esatta del padre ma con PID diverso. La fork restituisce sempre un numero intero:
al processo padre restituisce il PID del figlio cosi che lo riconosca. al processo figlio restituisce 0. mentre se restituisce -1 il processo figlio non è stato creato.
Utilizzo nel programma: fork(); non ha parametri, e ritorna un numero intero.
Contenuta nella libreria: #include <unistd.h>
Exit() è una funzione che permette ad un processo figlio di informare un processo padre della sua terminazione e di passare un codice in cui il padre sa che tipo di terminazione è avvenuta. Utilizzo nel programma: exit(int n); parametro intero, e non ritorna nulla.
Contenuta nella libreria: #include <stdlib.h>
Wait() è una funzione che viene utilizzata per sincronizzare il processo padre con il figlio, la wait fa sospendere l'esecuzione del padre fino a quando il figlio non è terminato. Questa funzione restituisce un numero intero che è il PID del figlio, inoltre tramite passaggio per indirizzo fa si che la variabile passata come parametro venga inizializzata con il valore della exit del figlio.
Utilizzo nel programma: *wait(int stuts); parametro ind. intero, ritorna il PID del figlio terminato.
Contenuta nelle librerie: #include <sys/types.h> #include <sys/wait.h>
NB [La variabile status dovrà essere divisa per 256 poiché i primi otto bit sono di controllo]
Pagina 13
Il costrutto Cobegin-Coend è molto simile al Fork-Join con la differenza che quando si richiama l'istruzione Cobegin si possono creare N processi contemporaneamente che sospendono il padre fino a che non terminano con la Coend.
Figura 3 Pseudo codifica di Cobegin-Coend
Pagina 14
Virtualizzazione è la possibilità di astrarre le componenti hardware, degli elaboratori, al fine di renderle disponibili al software in forma di risorsa virtuale.
Tramite questo processo è quindi possibile emulare un S.O. su hardware virtuale, ovvero assegnare risorse HW, ad un S.O. non installato sulla macchina.
Attraverso un processo di virtualizzazione che avviene tramite un programma, si crea una macchina virtuale che assegna l'HW al S.O. da emulare per renderlo funzionante e utilizzabile. Il S.O. emulato diventa un processo del sistema operativo che lo ha lanciato.
Pagina 16
Esercizio 2 : Creare un programma che a partire da un processo padre crei un processo figlio per poi far terminare prima il padre cosi da rendere orfano il figlio(non utilizzare la sleep).
#include <stdio.h> #include <unistd.h> //unix standard library int main(){ bool n=1; pid_t pid,mioPid,ppid; system("clear"); pid=fork(); //PADRE if(pid!=0){ mioPid=getpid(); //cattura il pid del padre printf("A1)SONO IL PADRE IL MIO PID E' %d \n",mioPid); sleep(3); //il processo padre si ferma per 3 secondi e passa al figlio printf("A2)SONO TERMINATO \n"); exit(0); } //FIGLIO else{ //controllo infinito fino a quando il ppid diventi 1 cioè il padre è morto, finiti i tre // secondi di sleep il padre riparte e trmina. do{ ppid=getppid(); //cattura il pid del padre //se entra nel controllo if vuol dire che il padre è morto if(ppid==1){ printf("SONO ORFANO, FIGLIO DI INIT %d\n",ppid); exit(0); } printf("B1)PID DI MIO PADRE %d \n",ppid); sleep(1); }while(n); } }
Pagina 17
I modelli di ambiente sono dei modelli che rappresentano l'utilizzo di una risorsa SW, HW o Dati da parte di un processo che ne fa richiesta.
Esistono due tipi di modelli:
Modello ad ambiente locale (scambio di messaggi): un singolo processo dispone di una o più risorse private a cui solo lui può accedere e utilizzar, processo indipendete, che per comunicare con altri processi deve scambiare dei messaggi tramite costrutti.
Modello ad ambiente globale (memoria comune): più processi interagiscono esclusivamente operando su risorse comuni, quindi saranno dei processi competitivi o cooperanti.
Ogni applicazione concorrente è composta da due componenti, processi(componenti attivi) e risorse(componenti passivi).
Risorsa è un qualunque oggetto, fisico o logico, di cui un processo necessita per portare a termine il suo compito è una struttura dati allocata nella memoria primaria. Risorsa privata (modello locale) quando un singolo processo può eseguire operazioni su di essa. Risorsa comune (modello globale): è una risorsa su cui più processi possono operare.
Esistono meccanismi di controllo degli accessi alle risorse poiché è necessario definire quali processi ed in quali istanti essi possono accedere correttamente alla risorsa. Il gestore può accettare, ritardare o rifiutare la richiesta.
Schema logico seguito da ogni processo che vuole accedere a una risorsa:
richiesta della risorsa, uso, rilascio del diritto di accedere.
Deadlock è un fenomeno che avviene quando più processi competono per l'utilizzo di risorse comuni, e si viene a creare una situazioni di stallo detta deadlock o blocco critico.