16 Jul 2023, 8:46 AM
16 Jul 2023, 8:46 AM

6. Scheduling della CPU

Con scheduling, si intende l'assegnazione di un'attività nel tempo. Esso è necessario per regolare

È importante ricordare che possono esserci N processi nello stato pronto, 1 processo in esecuzione e M processi in attesa in RAM.

Tipi di scheduler

Come accennato in 4. Gestione dei processi e threads#Scheduling, ci sono più tipi di scheduler:

Dispatcher

L'operazione più critica è quella fatta dal dispatcher, ovvero assegna la CPU ad un certo processo e fa passare il processo da ready a running. Come avviene nel dettaglio?

  1. Cambio di contesto: viene salvato il PCB del processo che esce e viene caricato il PCB (precedentemente registrato) del processo che entra
  2. Passaggio alla modalità utente
  3. Salto all'istruzione da eseguire del processo appena arrivato nella CPU

Ovviamente per fare questo, è presente una latenza di dispatch, ossia il tempo necessario al dispatcher per fermare un processo e farne ripartire un altro. E ovviamente deve essere la più bassa possibile.

Prelazione

Ottimizzare lo short-term scheduler è fondamentale per massimizzare l'utilizzo della CPU, pertanto sono state studiate molte tecniche e algoritmi per migliorare le prestazioni.
Dal punto di vista della CPU, ogni processo può essere visto come una serie di CPU burst e I/O burst (dove per burst si intende un intervallo di utilizzo necessario al completamento della task). A partire da questi, si definisce una prima classificazione delle politiche di scheduling, in base alla prelazione(preemption), ossia al rilascio forzato della CPU.

Metriche di scheduling

Per valutare un algoritmo di scheduling si utilizzano apposite metriche di scheduling:

Nota: Negli algoritmi non-preemptive, tempo di attesa e di risposta sono uguali. Inoltre, l'obiettivo è massimizzare l'utilizzo della CPU e il throughput, e minimizzare il tempo di turnaround, il tempo di attesa e il tempo di risposta. A seconda del sistema si può decidere di bilanciare tali metriche o focalizzarsi sulla minimizzazione/massimizzazione di una o più. Ad esempio, nei sistemi interattivi si preferisce minimizzare la varianza del tempo di risposta piuttosto che la media.

Algoritmi di scheduling

Di seguito sono riportati i principali algoritmi di scheduling:

First Come First Served (FCFS)

È un algoritmo di base, semplice da implementare e leggero. La coda dei processi funziona come una FIFO, si eseguono quindi i processi in ordine di arrivo.
Svantaggi:

Esempio

Sono dati i seguenti tre processi, con i relativi tempi di arrivo e CPU burst.

Processo Tempo di arrivo CPU burst
P1 0 24
P2 2 3
P3 4 3

Per capirci meglio, ecco quello che accade:

Tempo 024 2427 2730
P1 X
P2 X
P3 X

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 0 0 24
P2 22 22 25
P3 23 23 26

Analizziamo P1:

Analizziamo P2:

Analizziamo P3:

Il tempo di attesa medio è pari a 0+22+233=15

Esempio 2

Sono dati i seguenti tre processi, con i relativi tempi di arrivo e CPU burst.

Processo Tempo di arrivo CPU burst
P1 4 24
P2 0 3
P3 2 3

Per capirci meglio, ecco quello che accade:

Tempo 03 36 630
P1 X
P2 X
P3 X

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 64=2 2 2+24=26
P2 0 0 3
P3 1 1 1+3=4

Il tempo di attesa medio è pari a 2+0+13=1

Shortest Job First (SJF)

Tn+1=αtn+(1α)Tn

dove Tn+1 è il valore stimato per il prossimo burst, Tn è la media dei burst passati e tn la durata dell'ultimo burst registrato, α è un valore compreso tra 0 e 1. In questo modo si ottiene una buona stima del prossimo burst di CPU.

Esempio 1 - non-preemptive

Sono dati i seguenti quattro processi, con i relativi tempi di arrivo e CPU burst.

Processo Tempo di arrivo CPU burst
P1 0 7
P2 2 4
P3 4 1
P4 5 4

Per capirci meglio, ecco quello che accade:

Tempo 07 78 812 1216
P1 X
P2 X
P3 X
P4 X

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 0 0 7
P2 82=6 6 6+4=10
P3 74=3 3 3+1=4
P4 125=7 7 7+4=11

Quindi quello che succede è che una volta che un processo termina, viene eseguito quello con CPU burst minore. Essendo però non-preemptive, se entra un processo con CPU burst minore quello in esecuzione non viene killato.

Esempio 2 - preemptive

I dati sono uguali al precedente esempio.

Processo Tempo di arrivo CPU burst
P1 0 7
P2 2 4
P3 4 1
P4 5 4

Per capirci meglio, ecco quello che accade:

Tempo 02 24 45 57 711 1116
P1 X X
P2 X X
P3 X
P4 X

Più precisamente:

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 0 112=9 9+7=16
P2 0 1 1+4=5
P3 0 0 0+1=1
P4 2 2 2+4=6

In questo caso, per i processi P1 e P2, il tempo di risposta non coincide col tempo di attesa. Essi coincidono solo quando un processo, una volta iniziata la sua esecuzione, non torna più nella coda dei processi ad aspettare (algoritmi non-preemptive), cosa che nel SJF preemptive raramente accade (P3 e P4).

Come prima, quando un processo termina, viene eseguito quello con CPU burst minore. Essendo però preemptive, se entra un processo con CPU burst minore quello in esecuzione viene killato, e viene fatto entrare il nuovo processo.

Scheduling a priorità

Con lo SJF è stato introdotto lo scheduling a priorità (inversamente proporzionale al burst di CPU), in cui viene associata una priorità a ogni processo.

Può essere sia preemptive, sia non-preemptive. In Linux si utilizza il comando “nice” per cambiare la priorità.
Esempio: SJF è uno scheduling a priorità (priorità = 1/lunghezza del burst successivo). Più piccolo è il burst, più alta la priorità

L'implementazione base dello scheduling a priorità richiede una serie di code FCFS, ciascuna per ogni priorità specificata, questo comporta dei problemi tipici.
Primo tra tutti la starvation: alcuni processi non saranno mai eseguiti (o comunque con tempi di risposta troppo lunghi) poichè altri processi con priorità più alta li precedono costantemente. Per ovviare a tale problema si utilizza la tecnica di aging, tramite la quale la priorità viene periodicamente aggiornata e aumenta col passare del tempo in ready queue.

Esempio

Ecco un esempio di un generico algoritmo di scheduling a priorità.
Sono dati i seguenti cinque processi, con i relativi tempi di arrivo, priorità e CPU burst. In questo caso più è basso il numero della priorità, più la priorità è alta.

Processo Tempo di arrivo Priorità CPU burst
P1 1 3 10
P2 0 1 1
P3 2 3 2
P4 0 4 1
P5 1 2 5

Per capirci meglio, ecco quello che accade:

Tempo 01 16 616 1618 1819
P1 X
P2 X
P3 X
P4 X
P5 X

Più precisamente:

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 61=5 5 5+10=15
P2 0 0 0+1=1
P3 162=14 14 2+14=16
P4 180=18 18 18+1=19
P5 0 0 0+5=5

Vediamo ora due algoritmi di scheduling a prorità, uno preemptive ed uno non-preemptive.

Highest Response Ratio First (HRRN)

Algoritmo a priorità non pre-emptive, sviluppato come variante dello SJF. Per ogni processo la priorità viene calcolata come $$P = 1 + \frac {\text{Waiting Time}} {\text{BurstTime}}$$ ed è aggiornata al termine di ogni processo. Si supera con HRRN il favoritismo per i job corti, dato che sono favoriti i processi che completano in poco tempo (come SJF) oppure quelli che hanno atteso molto. La stima del CPU burst è fatta con la media esponenziale come per SJF.

Esempio

Sono dati i seguenti cinque processi, con i relativi tempi di arrivo e CPU burst. In questo caso più è alto il numero della priorità, più la priorità è alta.

Processo Tempo di arrivo CPU burst
P1 1 10
P2 0 2
P3 2 2
P4 2 1
P5 1 5

Per capirci meglio, ecco quello che accade:

Tempo 02 27 78 810 1019
P1 X
P2 X
P3 X
P4 X
P5 X

Come detto sopra, al termine di ogni processo la priorità di ogni altro processo viene ricalcolata come P=1+Waiting TimeBurstTime.
Costruiamoci quindi la tabella con le varie priorità.

Processo t=0 t=2 t=7 t=8
P1 - 1+110=1110 1+7110=1610 1+8110=1710
P2 1+02=1 - - -
P3 - 1+02=1 1+722=72 1+822=4
P4 - 1+01=1 1+721=6 -
P5 - 1+15=65 - -

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 101=9 9 9+10=19
P2 0 0 0+2
P3 82=6 6 6+2=8
P4 72=5 5 5+1=6
P5 21=1 1 1+5=6

Round Robin (RR)

Algortmo pre-emptive, che cerca di distribuire il tempo della CPU il più equamente possibile fra i processi. Viene definita un'unità (quanto, va dai 10 ai 100 ms), che è il tempo massimo di esecuzione di un processo prima che venga rimesso nella ready queue, gestita come una coda circolare.
Dunque, se ci sono n processi nella coda e il quanto è q:

Circolarmente viene fornito un quanto di tempo a disposizione per i processi, inserendo ad inizio coda di solito i processi appena arrivati, per minimizzare i tempi di risposta.

Il problema principale si basa sulla scelta del quanto di tempo:

Allora qual è un valore ragionevole di q? Bisogna fare in modo che l'80% dei burst di CPU siano <q.

In generale con RR, rispetto a SJF, si ha un tempo di turnaround medio maggiore/uguale, ma un tempo di risposta minore/uguale.

Esempio

Sono dati i seguenti quattro processi, con i relativi tempi di arrivo e CPU burst. Inoltre definiamo la lunghezza del quanto q=2.

Processo Tempo di arrivo CPU burst
P1 0 5
P2 0 1
P3 0 7
P4 0 2

Per capirci meglio, ecco quello che accade:

Tempo (q=2) 02 23 35 57 79 911 1112 1215
In esecuzione P1 P2 P3 P4 P1 P3 P1 P3
Nella ready queue P2 P3 P4 P1 P3 P1 P3
P3 P4 P1 P3
P4 P1

Calcoliamo ora il tempo di risposta (response time), di attesa (waiting time), e di completamento (turnaround time) di ogni processo.

Processo TR TW TT
P1 0 (72)+(119)=7 7+5=12
P2 2 2 2+1=3
P3 3 3+(95)+(1211)=8 7+7=15
P4 5 5 5+2=7

Code multilivello

Anzichè utilizzare un unico algoritmo ed un'unica queue, è possibile adottare un algoritmo più generale che preveda N code, ognuna con una specifica priorità e algoritmo di scheduling.
Esempio:

E’ un meccanismo più generale, ma anche più complesso. Diventa così necessario un algoritmo di scheduling fra le code. Vediamone due, uno semplice e uno più complesso.

Code multilivello con feedback

In entrambe queste tecniche, la coda di un processo è fissa, non c'è modo per un processo di cambiare coda e di conseguenza priorità. Si ottiene con uno scheduler multilivello con feedback una struttura più flessibile. I processi possono cambiare code, salendo o scendendo di priorità, in base a specifici eventi. Ad esempio, un processo in una coda con RR che non finisce la sua esecuzione nel quanto di tempo assegnatogli, può essere degradato ad una coda con priorità minore e diverso scheduling. Tale strategia può essere inoltre usata per implementare l'aging, aumentanto il waiting time, il processo può essere spostato a code con priorità maggiore. Questo modello rimane tuttavia più complesso a causa dell'ulteriore livello di scheduling aggiunto.

Esempio:
Abbiamo 3 code:

Coda multilivello.png|700

Vediamone il funzionamento:

Fair Share Scheduling (FFS)

Le politiche di scheduling fino ad ora sono state orientate ai processi, mentre con FFS si schedulano utenti o gruppi di processi. Ad esempio si possono schedulare tutti i processi di una specifica applicazione. L'obiettivo è quello di fornire a ciascun gruppo di processi/utente la stessa percentuale di utilizzo di CPU.

Valutare un algoritmo di scheduling

In quanto differenti sistemi richiedono differenti prestazioni, è opportuno applicare delle tecniche per valutare gli algoritmi di scheduling in differenti contesti.

Modello deterministico

Si definisce un workflow (insieme di processi con determinate caratteristiche) e lo si testa su carta con gli algoritmi descritti. Semplice e rapido, ma accurato solo per il workflow specifico, cioè definisce le prestazioni di ogni algoritmo per “quello” specifico carico.

Modello a reti di code

Si stimano matematicamente intensità e probabilità dei burst. Il modello è descritto come un rete di server, ognuno con una coda e rappresentante una CPU o un dispositivo di I/O. Si usano formule matematiche che permettono di ricavare utilizzo, throughput medio, tempi di attesa, ecc. Relativamente semplice ed adattabile, spesso produce risultati poco realistici.

Simulazione

Si simula lo scheduler con un apposito software e dei grandi dataset. I risultati sono molto precisi, ma l'esecuzione è lenta ed è un modello molto costoso.

Implementazione su macchina

È il metodo più preciso e l'unico che permette di avere dei risultati sicuri. Si implementa l'algoritmo in un SO e si misurano le prestazioni con un reale carico di lavoro.