2.06 Lezione 6 - follen99/ArchitetturaDeiCalcolatori GitHub Wiki

Implementazione del canale di comunicazione (link)

Livello Fisico

Il supporto per la comunicazione può essere sicuramente la shared memory, anche se in questo caso, se la memoria condivisa è presente, conviene utilizzare direttamente la memoria stessa, e non lo scambio di messaggi. Potrebbe inoltre fare utilizzo di un bus hardware, oppure della rete stessa.

Livello Logico

  • Diretto o indiretto: ovvero una comunicazione diretta tra due processi, oppure tra i due può esserci un qualcosa che funziona come una casella postale.
  • Sincrono o asincrono
  • Buffer Automatico o esplicito

Proprietà del canale di comunicazione (link)

Per stabilire un link di comunicazione i processi devono utilizzare il cosiddetto naming esplicito, ovvero ogni processo deve nominare esattamente l'altro processo con cui vuole comunicare. Questo naming è un vero e proprio parametro da inviare con la send() e receive():

  • send(P , Message)
  • receive(Q, Message)

Proprietà generali:

  • I link sono stabiliti automaticamente
  • Un link è associato ad esattamente una sola coppia di processi.
  • Tra ogni coppia esiste un solo link.
  • Il link potrebbe essere unidirezionale, ma è solitamente bidirezionale.

Comunicazione indiretta

Nel caso di comunicazione indiretta, significa che tra i due processi viene vista un'entità logica, chiamata mailbox. Significa che il processo non comunica l'altro processo con cui vuole parlare, ma il contenitore gestito dal SO, dove andrà a finire il messaggio inviato dal processo. Il link viene stabilito mediante la condivisione di una mailbox comune ai due processi.

Proprietà generali della comunicazione indiretta:

  • Il link è stabilito solo se i processi condividono una mailbox comune.
  • Un link può essere associato con diversi processi
  • Ogni coppia di processi potrebbe condividere diversi link di comunicazione
  • i link possono essere di tipo unidirezionale o bidirezionale.

Abbiamo delle operazioni basilari per la mailbox:

  • Creare una nuova mailbox (port)
  • Inviare e ricevere messaggi attraverso una mailbox
  • Distruggere una mailbox

In questo caso la **send() e receive() ** diventano:

  • send(A, message) inviare un messaggio alla mailbox A.
  • receive(A, message) ricevere un messaggio dalla mailbox A.

Piccolo problema

Un problema abbastanza evidente è il seguente: se diversi processi inviano messaggi ad una mailbox, e diversi processi eseguono la **receive() **il messaggio a chi deve essere recapitato?

Soluzione:
  • In alcuni casi i SO chiedono che un dato link di comunicazione venga associato a solo due processi.
  • In altri casi solo un processo per volta può effettuare la receive().
  • In altri casi il receive viene selezionato arbitrariamente. In questo caso il processo che ha inviato viene notificato dell'identità del ricevente. Non una buona soluzione.

🏁 00:04 05-12

Sincronizzazione

Il message passing può essere di tipo bloccante o non bloccante.

Quando lo scambio di messaggi è bloccante, abbiamo che il tipo di comunicazione è sincrono. Nel momento in cui eseguo una primitiva di comunicazione, perdo l'utilizzo della CPU finchè non viene eseguita l'operazione sull'altro processo.

  • Blocking send: Il processo che invia è bloccato finchè il messaggio viene ricevuto.
  • **Blocking receive: ** Il processo ricevente è bloccato finchè un messaggio non è disponibile.

Quando lo scambio è non blocking, il tipo di comunicazione è asincrono. Questo tipo di comunicazione è comunemente utilizzato durante la send().

  • Non-blocking send, che è il metodo più comunemente utilizzato, permette al processo di depositare il messaggio, e continuare l'esecuzione del processo che ha inviato il messaggio.
  • Non-blocking receive: è un'operazione "strana", perchè il processo attende il messaggio, ma nel frattempo il processo continua ad essere eseguito; questo vuol dire che dietro il codice del processo è presente un loop che viene eseguito finchè non viene ricevuto il messaggio. Questo metodo non è molto utilizzato.

Esiste infine un ultimo tipo, detto Randezvous, ovvero quando sia la send che la receive sono bloccanti, ovvero i due processi si attendono a vicenda.

Buffering

Quando abbiamo una send asincrona deve essere presente una coda di messaggi gestita dal sistema in modo da tenere traccia dei messaggi inviati. Se il size del buffer è 0, vuol dire che la comunicazione deve essere sincrona, in modo da attendere quindi il ricevimento di un messaggio alla volta.

Esistono altre due size possibili del buffer, Limitato ed illimitato. Quando il buffer è limitato, il processo che invia il messaggio deve attendere (diventa una sorta di comunicazione sincrona) solo nel momento in cui il buffer è pieno. Nel caso del buffer illimitato, il processo che invia non deve mai attendere (caso puramente teorico).

Sockets (veloce)

I sockets sono una forma di comunicazione tramite scambio di messaggi, che permettono di connettersi ad un sistema esterno. Questo è il tipo di comunicazione sulla quale si basano tutte le connessioni sulla rete.

Fine capitolo 3


🏁 00:15 05-12

Capitolo 4 : I Threads

Molte delle applicazioni moderne sono multi-thread, ovvero senza che l'utente si accorga di nulla, all'interno dell'applicazione sono presenti più attività che vengono eseguite simultaneamente. Vengono eseguiti quindi compiti diversi mediante esecuzioni separate.

Questo è possibile grazie all'hardware dei moderni calcolatori, dotati di processori multicore; per sfruttare la potenza di calcolo di questi sistemi, si è sviluppato, appunto, la programmazione multi-thread.

L'idea è quella di generare un qualcosa che possa essere eseguito separatamente, che sia più leggero della creazione dei processi. Questo significa che se voglio creare dei processi figli da un processo padre, ogni volta devono essere eseguite delle syscalls come fork(), exec(), ecc, e sono quindi costretto a creare più processi. Questa è una cosa più che fattibile, ma qualora avessi a disposizione un hardware multicore, potrei sfruttarlo al meglio facendo eseguire un diverso processo per core.

Il problema è che ogni processo riceve un proprio spazio indirizzi, quando voglio saltare da un processo all'altro, il processo deve eseguire un gran numero di operazioni, andando quindi a "perdere tempo". Questo perchè il SO va a salvare lo stato del processo (ed altre operazioni come abbiamo visto nel CH 3), ogni volta che passa da un processo all'altro.

Perchè cambiare lo spazio degli indirizzi, e non far avvenire tutto all'interno di un unico spazio indirizzi?

La differenza fondamentale tra un'applicazione composta da più processi separati ed un'applicazione composta da più thread, è che i processi hanno un proprio spazio degli indirizzi, ==i thread, invece, condividono lo spazio degli indirizzi del processo==.

Questo significa che la creazione dei processi è detta light-weight, ovvero **leggera ** (a differenza della creazione dei processi che è detta heavy-weight), siccome avviene in tempi anche di 100 volte minori rispetto ai processi.

Cosa significa lavorare all'interno dello stesso spazio degli indirizzi?

parallelismo

Processo a singolo thread

In un processo a singolo thread ho a disposizione i registri della CPU ed il Program Counter (che mi ricorda dove è arrivata l'esecuzione), ho inoltre uno stack di appoggio, che mi conserva tutto ciò che le varie funzioni hanno ritornato. Nel momento in cui il processo viene interrotto (per passare ad un altro) vengono salvati PC e Registri.

Sistema multi threaded

Utilizzo lo stesso codice, quindi la stessa area text, stessa area dati inizializzata, stessi file aperti, ma i percorsi di esecuzione diventano multipli, ovvero, mentre un un thread esegue qualocosa, ne è presente un altro che esegue un'altra cosa.

È quindi necessario che ogni thread (che fa un qualcosa) abbia uno stack privato, significa che l'area stack viene partizionata, in modo che ogni thread abbia uno stack diverso dagli altri. Non si può tassativamente mischiare i diversi stack, perchè, giustamente, ogni thread ha un processo che può potenzialmente eseguire un processo diverso dagli altri.

Quindi, ricapitolando, in un sistema multi-threaded abbiamo:

  • Area Text **comune ** (stesso codice)
  • Area dati globali comune
  • File aperti comuni
  • Diversi percorsi di esecuzione, dove ogni percorso di esecuzione ha uno stack privato.

Cosa significa che l'area text (codice) è lo stesso per ogni thread?

Vuol dire che, essendo il codice uguale, ogni thread esegue la stessa operazione ? Ovviamente no.

Il concetto è che quando parte l'esecuzione, il processo è tradizionale, quindi a singolo thread, dopodiché ci saranno delle particolari chiamate che permettono di attivare ulteriori thread. Nel momento in cui questi nuovi thread vengono attivati, viene specificata la funzione che devono eseguire.

Di conseguenza, il codice è unico, ma ogni thread esegue una funzione diversa del codice comune. La funzione che il thread eseguirà viene specificata nel momento in cui esso viene attivato.

Cosa si guadagna con la programmazione multi-threaded?

Con questo sistema il guadagno evidente consiste nel fatto che diversi thread lavorano sullo stesso spazio indirizzi, e quindi la loro creazione è velocissima; il sistema non ha bisogno di riservare della memoria, siccome è già presente.

I thread vengono inoltre computati molto velocemente: Se un thread viene fermato ma ne viene avviato un altro, non viene sprecato del tempo, siccome si resta sempre alla stessa area di memoria, ho quindi la massima velocità nella computazione.

I thread, oltre ad eseguire compiti diversi, sono singolarmente schedulabili: se ho diverse CPU, ognuno di questi thread può andare in esecuzione insieme agli altri thread sui core disponibili, in modo da sfruttare l'architettura multi-core.

Diversi anni fa, la programmazione multi-thread non era molto diffusa, siccome la maggior parte dei processori non supportava più core (più CPU), ma con i sistemi attuali, praticamente tutti multi-core, la programmazione multi-thread è basilare.

La programmazione multi-thread è il futuro della programmazione.

Quali programmi sfruttano il multi-threading?

Solitamente i programmi più CPU intensive, come quelli di modellazione 3D, Matlab, programmi di grafica, sfruttano il multi-threading. Anche alcuni browser, come Chrome, utilizzano questo tipo di programmazione.

Programmi più leggeri come quelli che gestiscono le e-mail non sfruttano, quasi mai, il multi-thread.

Esempio ampiamente utilizzato per il multi-threading

L'esempio più comunemente utilizzato per spiegare il funzionamento dei thread è quello del server che deve servire delle richieste. Se ad esempio mi collego ad un server, questo riceverà delle richieste, da tutto il mondo, che deve "servire" nel minor tempo possibile.

Se questo server fosse un processo a singolo thread, potrebbe servire una singola richiesta alla volta, ignorando tutte le richieste arrivate dopo quella corrente.

L'idea è quindi quella di avere una gestione concorrente delle richieste, in modo da avere un processo che gestisce le richieste, e ad ogni nuova richiesta assegnarla a diversi agenti, in modo da poter continuare a ricevere nuove richieste.

Conviene quindi realizzare questa struttura a thread invece di quella a processi. Anche perchè avremmo bisogno di un'area di memoria comune (per la cache), un compito perfetto per i thread, siccome essi per definizione condividono la data area.

I benefits (recap)

  • Responsività: potrebbe permettere la continuazione dell'esecuzione se una parte del processo viene fermata, specialmente importante nelle interfacce grafiche.
  • Condividisione delle risorse: i thread condividono le risorse del processo, rendendo il tutto molto più semplice della memoria condivisa o dello scambio dei messaggi.
  • Economia: più "economici" della creazione dei processi, inoltre si ha un overhead (perdita di tempo) minore rispetto al context switching, ovvero lo scambio di contesto dei processi.
  • Scalabilità: i processi possono prendere vantaggio delle architetture a multi processore.

parallelismo

Distinzione fondamentale

Qual è il modo per generare i thread?

Come faccio passare da un sistema ad ambiente a singolo thread a uno a multi thread? Fino agli anni 90 i SO prevedevano, come entità minima un singolo processo. Il sistema UNIX tradizionale prevede i pid (process identifiers) ma non prevede i thread.

Quindi la domanda sorge spontanea: come si è fatto a lanciare un processo multi-thread, quando lo stesso sistema operativo non prevedeva questa possibilità?

Fondamentalmente, i programmatori, hanno "inventato" delle librerie che in qualche maniera, all'interno del processo, schedulassero separatamente i diversi thread:

  1. Scrivo il mio codice
  2. Mi linko ad una libreria che mi fornisce delle funzioni per generare thread multipli all'interno del processo. Questi thread multipli possono funzionare in diversi modi:
    1. Un thread si sospende chiamando un altro
    2. Vengono schedulati, a time sharing, da uno scheduler che è quello interno al processo, che è eseguito dalle funzioni della libreria utilizzata.

Importante: il Sistema Operativo, non ha idea di cosa stia accadendo, ma vede il tutto come un semplice processo!

Questo modo di esecuzione, si chiama implementazione del thread spazio utente (user threads). Quando il SO non aveva il concetto di threads, ma solo di processi, era l'unico modo per mandare in esecuzione dei threads.

Inoltre, questo modo di esecuzione funziona in questo modo: Se il SO concede 10ms ti tempo CPU al processo, in questo lasso di tempo vengono alternati i threads da eseguire.

Pro e contro

PRO

Il pro di utilizzare questo metodo per la programmazione multi-thread, è sicuramente il fatto che il sistema operativo, come già detto, non ha idea di cosa stia accedendo, o meglio, crede di eseguire un semplice processo. Di conseguenza, ho la possibilità di eseguire meno system calls (che utilizzano molto tempo di CPU - quindi overhead), e di conseguenza le operazioni divengono molto più veloci.

Contro

Se con questo sistema costruisco un modello simile ad un server che accetta delle richieste, il tutto non funziona. Questo perchè, come sappiamo, il SO non è a conoscenza del fatto che ci siano esecuzioni multiple (threads); infatti, quando un thread (utente) effettua una richiesta I/O (ad esempio di un'immagine) il SO sospende l'intero processo, e quindi anche gli altri thread, invalidando il tutto.

🏁 00:45 05-12

Distinzione fondamentale

Bisognerebbe distinguere tra la API, ovvero le primitive invocate dal programmatore per creare e gestire i thread, e la loro implementazione. Questo vuol dire che il programmatore può invocare queste primitive, ma non ha idea di come esse siano implementate, siccome la loro implementazione dipende dalla libreria in uso.

Threads user e kernel

User Threads: La gestione è fatta da librerie di thread a livello utente. Esistono infatti tre librerie primarie dei threads:

  • POSIX Pthreads
  • Windows threads
  • Java threads

**Kernel Threads: ** supportati dal kernel. Questo è il caso dei sistemi operativi che supportano nativamente i threads. Tra questi ci sono:

  • Windows
  • Linux - discorso ampio
  • Mac OS
  • iOS
  • Android - discendente da linux

User and Kernel Threads

Il kernel del sistema operativo, che viene attivato nel momento in cui arriva un interrupt o eccezioni, deve eseguire delle attività concorrenti. Bisogna inventare un metodo per rendere il kernel del SO più reattivo e sopratutto capace di effettuare più cose contemporaneamente.

Nel momento in cui il concetto di thread è diventato reale, ci si è posti la possibilità di realizzare anche il kernel del SO a thread, ovvero di "spezzettare" tutte le componenti del SO (kernel) in tanti thread, ognuno che facesse un task diverso, allo stesso momento.

Importantissimo: i kernel threads di cui parleremo da questo momento in poi, sono thread all'interno del kernel stesso.

Librerie di thread

Le librerie dei thread forniscono delle API, per creare e gestire dei thread; questi thread potrebbero essere implementati in spazio utente o a livello kernel.

Pthreads

disclaimer: tutte le componenti con la P iniziale, appartengono allo standard POSIX, che specifica una API per la creazione dei thread. In Linux la libreria è già inclusa.

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int sum;
void *runner(void *param);

int main(){
  pthread_t tid;
  pthread_attr_t attr;
  
  // imposta gli attributi di default del thread
  pthread_attr_init(&attr);
  
  //crea il thread
  pthread_create(&tid, &attr, runner, argv[1]);
  
  // attende che il thread finisca
  pthread_join(tid, NULL);
  
  printf("sum = %d\n" , sum);
}

Questo nuovo thread, creato con pthread_create() eseguirà una funzione chiamata runner; questo vuol dire che all'interno del mio programma avrò una funzione runner che sarà chiamata per attivare il thread:

void *runner(void *param){
  int i, upper = atoi(param);
  sum = 0;
  
  for(i = i; i <= upper; i++){
    sum += 1;
  }
  
  pthread_exit(0);
}

Dal momento in cui viene eseguito pthread_create(), esistono due thread: il main ed il nuovo thread (composto da runner). Una delle cose che il thread principale potrebbe fare (il main) è chiamare la funzione pthread_join(), che permette di attendere la terminazione dell'altro.

Il secondo thread, infatti, quando completa la sua esecuzione, chiama la funzione pthread_exit(), che consente di terminare l'esecuzione del thread (e non il programma!).

Quindi, le funzioni vengono chiamate nell'ordine:

  1. pthread_attr_init()
  2. pthread_create()
    1. pthread_exit()
  3. pthread_join()

Thread Java

Anche in java è possibile usare i thread. Questo è quanto.

Threading implicito

Esistono altri sistemi in cui l'attivazione dei thread non è esplicita, quindi non viene richiesto esplicitamente, ma viene eseguita in maniera implicita dal programma?

Il sistema più comunemente utilizzato è openMP, che è uno standard abbondantemente utilizzato, che permette di scrivere dei programmi i quali attivano automaticamente (in certi momenti) delle attività concorrenti. In particolare, openMP permette di parallelizzare i loop, in modo tale che ogni loop faccia un certo numero di iterazioni. In poche parole con openMP non è necessario attivare manualmente i thread, ma questi verranno attivati automaticamente quando serve.

Esempio:

#include <omp.h>
#include <stdio.h>

int main(){
  #pragma omp parallel
  {
    printf("sono in una regione parallela");
  }
  
  return 0;
}

anche in questo caso omp è incluso nei sistemi linux.

#pragma omp parallel for
for(i = 0; i < N; i++){
	c[i] = a[i] + b[i];
}

programma per la parallelizzazione dei loop

Nel caso della parallelizzazione dei loop, il sistema utomaticamente genera tanti thread quanti sono i core disponibili, e le varie iterazioni dei loop vengono distribuite sui processori disponibili.

Nel corso non vedremo nel dettaglio questo tipo di implementazione, ma serve solo a far capire che i thread non devono per forza essere programmati "a mano", ma esistono anche dei tool appositi per facilitarne la creazione.

Problemi del threading

Nei sistemi UNIX i threads si soprappongono a quelli che sono i processi normali, questo perchè i sistemi UNIX sono nati per gestire proprio dei processi.

Se ho un processo avente più thread attivi in quel momento, ed effettuo una fork() che succede?

Risposta: bella domanda! Quando, tanti anni fa, è stata "inventata" la fork(), la si intendeva per l'utilizzo solo con i processi, e quindi il processo veniva duplicato. Ora che parliamo di threads, si dovrebbero duplicare anche i thread oppure bisogna avere un unico thread?

Il risultato è che molte semantiche dei sistemi UNIX diventano imprecise nel momento in cui ci sono dei thread.

Infatti, la stessa indecisione avviene nel momento in cui digito ctrl+C per effettuare la kill del processo in esecuzione (sul terminale): termino solo il thread in esecuzione o tutti?

Thread windows

Windows, sotto il punto di vista dei thread, è completamente fornito, essendo un SO moderno.

Thread Linux

Linux nasce invece come sistema tradizionale, dove l'obbiettivo era quello di avere un sistema quanto più monolitico possibile. Il vantaggio diretto di questo approccio è il fatto che linux riesca a girare senza problemi su un gran numero di macchine, anche quelle meno performanti. Tra gli svantaggi, però, è il fatto di non avere assolutamente il concetto dei threads.

Nel momento in cui è divenuto necessario l'utilizzo dei threads, anche per una questione di compatibilità, gli sviluppatori hanno dovuto trovare una soluzione, per nulla convenzionale:

Cominciamo con il dire che i threads non sono presenti. Ma se nel momento in cui effettuo una fork() do la possibilità di creare un altro processo che condivide l'area dati globale, il codice (area text), i file aperti, ma non lo stack, sommariamente sto creando un processo molto simile ad un thread.

Questo perchè non si poteva assolutamente modificare il comportamento della fork() classica, quindi solo ed esclusivamente nel sistema linux, la fork() chiama una system call aggiuntiva chiamata clone(). Questa syscall può specificare cosa clonare e cosa no (non va ad esempio clonata l'area text). la clone() crea un nuovo prcesso che duplica registri, stack e program counter, ma non duplica area text, area data e files aperti. La clone(), quindi, permette al task figlio di condividere lo spazio degli indirizzi del task padre.

Quindi

Come risultato abbiamo dei processi che sembrano dei threads, ma che non lo sono. La conseguenza è che la loro computazione è più lenta, così come la loro creazione. Capiamo quindi che i threads linux hanno delle prestazioni a metà strada tra i sistemi a thread nativi e quelli che utilizzano i processi.

Ovviamente la libreria Pthreads che viene utilizzata dai sistemi linux, fa utilizzo di questo meccanismo.

È semplice programmare con i threads?

No.

I thread non sono protetti in memoria tra di loro: i thread condividono lo stesso spazio degli indirizzi. Come conseguenza si ha che se un thread "sfonda" un array (aggiunge più elementi di quanto un array può contenere) o utilizza male un puntatore, va a scrivere sullo spazio usato da un altro thread, ed il SO non si accorgerà di nulla!

Con i processi, invece, se sfondiamo un array, otteniamo un segmentation fault (Segfaults are caused by a program trying to read or write an illegal memory location).


Fine capitolo 4

🏁 1:31 05-12

Capitolo 5 : Scheduling della CPU

Ragionando in maniera astratta, il concetto fondamentale è che se voglio avere l'utilizzo massimo della CPU, devo usare la multiprogrammazione. Questo è un concetto fondamentale, e significa che nel momento in cui un processo si ferma perché attende il completamento, ad esempio, di un'operazione di I/O, qualche altro processo prende l'utilizzo della CPU, in modo che essa sia sempre utilizzata per computare qualcosa. Questo concetto è adottato da tutti i SO moderni.

Durante la vita di un generico processo, c'è una successione di fasi, in cui ci sono delle sequenze interne alla CPU, quindi senza I/O, inframezzate da sequenze di I/O. Esiste quindi un ciclo di burst, ovvero una raffica di operazioni di CPU; nel generico processo esiste un susseguirsi di fasi in cui si lavora internamente alla CPU, e fasi in cui sono richieste delle operazioni di I/O.

Abbiamo quindi, parlando in termini tecnici, un susseguirsi di

  • CPU: cicli di operazioni interne alla CPU
  • I/O burst: cicli di operazioni I/O.

L'insieme di queste operazioni è detto CPU-I/O burst cycle, e questo avviene per qualsiasi processo.

Tempistica dei CPU Burst

Quello che succede tipicamente, è che i burst di CPU sono abbastanza brevi:

Infatti notiamo che maggiore è la frequenza, e quindi la presenza di un burst, minore è il suo tempo di esecuzione (durata del burst). Di conseguenza i burst più frequenti, sono anche quelli che durano il minor tempo.

CPU scheduler

Lo scheduler è quella parte del SO che decide, tra tutti i processi presenti nella coda Ready (ovvero la coda dei processi pronti all'esecuzione), quello che deve essere eseguito. Nel caso di core multipli, vengono scelti più processi.

Qual è il momento in cui avvengono le decisioni per scegliere il processo da eseguire?

I momenti per scegliere il processo sono quattro:

  1. Un processo cambia il suo stato da running a waiting: C'è un processo nello stato running, ovvero in esecuzione nella CPU. Per cambiare lo stato in waiting, il processo dovrebbe effettuare un'operazione di I/O, o dovrebbe deschedularsi volontariamente. In questo caso il processo lascia lo stato running, e quindi la CPU diventa libera per eseguire un altro processo; lo scheduler deve entrare in funzione.

  2. Un processo termina: Il processo nello stato running effettua la system call exit(), che permette al processo di terminare. La CPU resta quindi libera, ed è anche questo il caso in cui lo scheduler dovrebbe entrare in gioco.

Scheduler nonpreemptive

Se lo scheduler entrasse in gioco solo in questi due casi, vorrebbe dire che esso assegna la CPU ad un altro processo, solo quando il processo precedente ha cambiato il suo stato volontariamente, o per l'operazione I/O, o perchè ha terminato.

In questo caso, lo scheduler, viene detto nonpreemptive, o in italiano, senza prelazione. "nonpreemptive" vuol dire che lo scheduler non toglie forzatamente la CPU ad un determinato processo, ma semplicemente lo sostituisco quando il processo precedente ha volontariamente lasciato spazio al prossimo.

Scheduler Preemptive

  1. Il processo cambia il suo stato da running a ready: Significa che al processo è arrivata un'interrupt, ed è stato momentaneamente sospeso. Un altro motivo potrebbe essere che il tempo assegnato all'esecuzione di quel determinato processo è terminato. Se per caso lo scheduler prende la decisione di mandare in esecuzione un altro processo, vuol dire che quel processo poteva continuare ad essere eseguito, ma è stato bloccato forzatamente.
  2. Il processo cambia il suo stato da waiting a ready: In questo caso il processo era in attesa di un'operazione I/O, di conseguenza lo scheduler può decidere di dare la priorità ad un altro processo, perchè, ad esempio, quest'ultimo ha una priorità maggiore.

Di conseguenza, lo scheduler che assegna la CPU, stando anche ai punti 3 e 4, è uno scheduler preemptive.

In alternativa, uno scheduler nonpreemptive, non toglierà mai la CPU ad un processo che non termina o non è in attesa; questo vuol dire che, ad esempio, un processo che è in un loop infinito (o per errore o appositamente), non si vedrà mai tolta la CPU, ma dovremo terminarlo manualmente.

Dispatcher

Il dispatcher è proprio il modulo che sospende un processo e ne manda in esecuzione un altro; esso effettua lo switch tra due processi.

Cosa deve fare?

  1. Prendere il processo in esecuzione
  2. Salvarne lo stato
  3. Ripristinare lo stato del processo che riceve la CPU
  4. Mandare in esecuzione un altro processo

Latenza del dispatch

È ovviamete il tempo necessario per effettuare le operazioni elencate precedentemente. È semplice comprendere che i punti 2 e 3, sono totalmente overhead, proprio perchè la CPU non sta eseguendo del lavoro utile (ovvero eseguire processi), ma sta "perdendo tempo". Per questo motivo queste operazioni vanno fatte nel tempo più veloce possibile.

Bisogna chiarire che la latenza di dispatch vera è propria, è calcolata a partire dopo il punto 1 e prima del punto 4, quindi il tempo per eseguire i punti 2 e 3.

fine lezione 6


⚠️ **GitHub.com Fallback** ⚠️