c3. scheduling della cpu

Transcript

c3. scheduling della cpu
C3. SCHEDULING DELLA CPU
Lo scheduling della CPU è alla base dei sistemi operativi multiprogrammati e consente di
passare il controllo della CPU ai vari processi, in modo da rendere più produttivo il calcolatore.
Verranno presentati i concetti fondamentali dello scheduling, vari algoritmi di scheduling
e la scelta dell’algoritmo da impiegare per un dato sistema.
Concetti fondamentali
Nella sua vita, un processo può avere bisogno di attendere un
evento (generalmente una richiesta di I/O) per poter proseguire.
In un sistema di calcolo semplice, la CPU resterebbe inattiva e il
tempo di attesa sarebbe sprecato.
Con la multiprogrammazione, invece, sono caricati più processi
in memoria. Quando un processo deve attendere un evento, il sistema operativo gli sottrae il controllo della CPU per cederlo a un
altro processo.
Ciclicità delle fasi di elaborazione e di I/O
L’esecuzione di un processo è l’alternanza di due fasi:
• raffica di operazioni CPU (CPU burst)
• raffica di operazioni I/O (I/O burst)
Sebbene le durate varino secondo il processo e secondo il calcolatore, la curva di frequenza è simile a quella qui riportata.
Questa curva è di tipo esponenziale o iperesponenziale, in quanto sono presenti un gran
numero di brevi sequenze di operazioni CPU e una piccola quantità di lunghe sequenze
di operazioni CPU. Se il programma è a prevalenza di I/O produce molte sequenze di breve durata, mentre se il programma è a prevalenza di elaborazione produce poche sequenze di lunga durata.
Conoscere questi aspetti può essere utile per scegliere un buon algoritmo di scheduling
per la CPU.
Scheduler della CPU
Quando la CPU è inattiva, lo scheduler a breve termine (o scheduler della CPU) seleziona
uno dei processi in coda pronti per l’esecuzione e ad esso alloca la CPU.
La coda non è necessariamente FIFO. Come si noterà nell’analisi dei vari algoritmi di
schedulazione, vi possono essere diverse filosofie di ordinamento. Qualunque tipo di
ready queue, però, ha come elementi i Process Control Block PCB dei processi.
Scheduling con e senza diritto di prelazione
Le decisioni riguardanti lo scheduling della CPU si possono prendere in diverse circostanze.
Un processo...
Perchè...
Passa dallo stato di esecuzione
allo stato di attesa
- Arriva una richiesta di I/O
- Arriva una richiesta di attesa per terminare uno dei
- figli
Passa dallo stato di esecuzione
allo stato pronto
- E’ arrivato un segnale di interruzione
Passa dallo stato di attesa
allo stato pronto
- E’ stata completata un’operazione di I/O
Termina
Nei casi 1 e 4 non c’è alcuna scelta di
scheduling da fare, perché è il processo a dire che ha terminato la sua esecuzione e non ha più bisogno della
CPU: è necessario solo scegliere il
prossimo processo da eseguire. In
questo caso lo scheduling è detto senza diritto di prelazione (non-preemptive) o cooperativo. La CPU rimane assegnata al processo fin quando questo non la rilascia, in quanto terminato o in attesa. Questo metodo
è stato adottato fino a Windows 95 e non richiede dispositivi particolari.
Al contrario, nei casi 2 e 3 si ha uno schema di scheduling con diritto di prelazione (preemptive). Il processo, in altre parole, continuerebbe volentieri la sua esecuzione ma viene interrotto perché altri “scalpitano”. Questo schema di scheduling definisce dunque se
e come interrompere il processo attualmente in esecuzione, eventualmente considerando anche chi è in attesa.
Si noti che la prelazione non è fatta sempre e comunque. Vi sono ad esempio casi in cui
due processi P1 e P2 condividono dati: se P1 aggiorna i dati e nel frattempo si ha la sua
prelazione in favore di P2, quest’ultimo si trova a leggere dati lasciati in uno stato incoerente.
Ancora, durante l’elaborazione di una syscall il kernel può essere impegnato in attività in
favore di un processo che possono portare a modifiche dei dati del kernel stesso. Se si
ha la prelazione del processo durante tali modifiche e il kernel legge o modifica gli stessi
dati è un macello. Questo problema e il precedente sono risolvibili attendendo il completamento della syscall o il blocco dell’I/O prima del cambio di contesto.
Infine bisogna tenere presente che le interruzioni, per loro natura, possono verificarsi in
ogni istante. Le sezioni di codice eseguite per effetto delle interruzioni devono essere
protette da un uso simultaneo: questo è risolvibile disattivando le interruzioni all’inizio
delle sezioni di codice per riattivarle alla fine.
Dispatcher
Il dispatcher è il modulo che passa effettivamente il controllo della CPU ai processi scelti
dallo scheduler a breve termine. Questa funzione coinvolge il cambio di contesto, il passaggio alla modalità utente e il salto alla giusta posizione del programma per riavviarne
l’esecuzione.
Dato che si avvia ad ogni cambio di contesto deve agire in tempi più rapidi possibili. In
altre parole deve avere una bassa latenza di dispatch, cioè il tempo richiesto per fermare un processo e avviarne un altro.
Criteri di scheduling
Nella scelta del miglior algoritmo di scheduling per il sistema in oggetto bisogna considerare una serie di parametri. Nella maggior parte dei casi si ottimizzano i valori medi,
ma se si vuole che tutti ottengano un buon servizio si ottimizzano i valori estremi.
Parametro
Spiegazione
Utilizzo della CPU
Min/Max
Max
Throughput
Numero di processi completati nell’unità di tempo
Max
Tempo
di completamento
Tempo di esecuzione del processo stesso
Min
Tempo
di attesa
Tempo passato nella coda dei processi READY
Min
Tempo
di risposta
Tempo tra la sottomissione di una richiesta
se la prima risposta prodotta
Min
Il tempo di attesa si può ottenere misurando il tempo di completamento e sottraendogli
il tempo di esecuzione e il tempo di attesa per l’I/O.
Algoritmi di scheduling
Nel seguito si descrivono alcuni fra i tanti algoritmi di scheduling della CPU. Per motivi di
semplicità, negli esempi si considerano sequenze di soli CPU burst, trascurando i context switching. La rappresentazione è fatta con i diagrammi di Gantt, che illustrano le
pianificazioni includendo tempi di inizio e fine di ogni partecipante.
Scheduling in ordine di arrivo
Nello scheduling in ordine di arrivo (FCFS, First Come First Served) la CPU è assegnata al
processo che la richiede per primo. L’implementazione è semplice ed è fatta con una
coda FIFO.
Il tempo di attesa medio dei processi può variare sostanzialmente al variare della durata
delle sequenze di operazioni della CPU dei processi e dell’ordine con cui esse sono “sistemate”. Si supponga infatti di avere i processi
Processo
Durata sequenza
P1
24 ms
P2
3 ms
P3
3 ms
Si ha che
Schema di arrivo
Tempo medio di attesa
(0+24 +27)/3=17 ms
(0+3+ 6) /3=3 ms
Questo tipo di schedulazione è senza prelazione: una volta che la CPU è assegnata ad
un processo, questo la trattiene fino al momento del rilascio (perché il processo termina
o perché c’è bisogno di fare I/O). Nei sistemi a tempo ripartito, l’uso della schedulazione
FCFS che consente a un processo di occupare la CPU per lungo periodo porterebbe a ri sultati disastrosi.
Scheduling per brevità
Nello scheduling per brevità (SJF, Shortest Job First) si assegna la CPU, se disponibile, al
processo che ha la più breve lunghezza della successiva sequenza di operazioni della
CPU. Se due processi hanno le successive sequenze di operazioni della CPU della stessa
lunghezza si applica lo scheduling FCFS. E’ possibile dimostrare che l’algoritmo SJF, pur
essendo greedy, è ottimale perché rende minimo il tempo di attesa medio per un dato
insieme di processi.
Si supponga di avere il seguente insieme di processi
Processo
Durata sequenza
P1
6 ms
P2
8 ms
P3
7 ms
P4
3 ms
Assegnando per prima la CPU al processo con durata più breve si ha
Schema di arrivo
Tempo medio di attesa
(0+3+9+16)/4=7 ms
La difficoltà implicita nell’algoritmo SJF consiste nel conoscere la durata della successiva
richiesta della CPU. Questa è generalmente ottenibile calcolando la media esponenziale
delle effettive lunghezze delle precedenti sequenze di operazioni della CPU.
Sia quindi t n la lunghezza dell’n-esima sequenza di operazioni della CPU, τ n +1 il valore
previsto per la successiva sequenza di operazioni della CPU e α un valore compreso
tra 0 e 1 che serve per “pesare” maggiormente la storia presente o passata.
Si ha τ n +1=α t n +(1−α ) τ n , con α =1/2 nella maggior parte dei casi (la storia recente e
quella passata hanno lo stesso peso). Lo sviluppo della formula fa apprezzare a pieno
che ogni termine ha peso inferiore a quello del suo predecessore, “spegnendo” il passato man mano che ci si allontana nel tempo.
L’algoritmo SJF può essere sia con prelazione che senza prelazione. Nel primo esempio
si supponeva che tutti e quattro i processi fossero noti all’istante t 0 . Diverso è il caso in
cui un nuovo processo arrivi a un istante t 1 e abbia una sequenza di operazioni più corta del processo correntemente in esecuzione.
Con un algoritmo SJF con prelazione (SRTF, Shortest Remaining Time First) è possibile
sostituire il processo attualmente in esecuzione. Si consideri il seguente insieme di processi
Processo
Istante di arrivo
Durata sequenza
P1
0
8 ms
P2
1
4 ms
P3
2
9 ms
P4
3
5 ms
Quel che risulta con SRTF è indicato dal seguente diagramma
Schema di arrivo
Tempo medio di attesa
((10−1)+(1−1)+(17−2)+(5−3)) /4
26/ 4=6,5 ms
in cui, brevemente, all’istante 0 si avvia P1 perché è l’unico che si trova in coda.
All’istante 1 arriva anche P2 che dura 4 ms: siccome il tempo richiesto per completare P 1
è di 7 ms, si ha la prelazione su P1 che viene sostituito con P2, e così via.
Scheduling per priorità
L’algoritmo SJF è un caso particolare del più generale algoritmo di scheduling per priorità, in cui si associa una priorità ad ogni processo e si assegna la CPU al processo con
priorità “vincente” (perché qualcuno può intendere la priorità 4095 come la peggiore e
altri come la migliore).
Si consideri il seguente insieme di processi arrivati all’istante t 0
Processo
Durata sequenza
Priorità (0: più alta)
P1
10 ms
3
P2
1 ms
1
P3
2 ms
4
P4
1 ms
5
P5
5 ms
2
Si ha
Schema di arrivo
Tempo medio di attesa
(0+1+6+ 16+18)/5=8,2 ms
Supponendo che all’istante t 1 arrivi un processo a priorità superiore:
• nello scheduling con priorità e con diritto di prelazione, si sottrae la CPU al processo attualmente in esecuzione
• nello scheduling con priorità e senza diritto di prelazione, si pone l’ultimo processo arrivato in testa alla coda dei processi pronti
Un problema importante relativo agli algoritmi di scheduling per priorità è l’attesa indefinita o starvation, che si ha quando un processo è pronto per l’esecuzione ma non dispone mai della CPU perché gli viene soffiata sempre da qualcuno a priorità maggiore.
Una soluzione a questo problema è costituita dall’aging dei processi, facendo aumentare
la priorità dei processi che attendono nel sistema da parecchio tempo.
Scheduling circolare
L’algoritmo di scheduling circolare (round robin, RR) è simile allo scheduling FCFS, ma
ha in più la capacità di prelazione per la commutazione dei processi. Ciascun processo
riceve una piccola quantità fissata del tempo della CPU, chiamata time slice. Lo scheduler della CPU scorre la coda dei processi pronti e assegna la CPU a ciascun processo per
una durata massima di un time slice.
Lo scheduler della CPU individua il primo processo dalla coda dei processi pronti, imposta un timer in modo che invii un segnale di interruzione alla scadenza di un intervallo
pari a un time slice e attiva il dispatcher per l’effettiva esecuzione del processo.
Terminato il time slice, se il processo ha una sequenza di operazioni di durata minore del
time slice, rilascia volontariamente la CPU. Altrimenti viene inviato un segnale di interruzione al sistema operativo che esegue un cambio di contesto, aggiunge il processo alla
fine della coda dei processi pronti e – tramite lo scheduler della CPU – seleziona il processo successivo.
Il tempo di attesa medio per il criterio di scheduling RR è spesso abbastanza lungo.
Si supponga di avere il seguente insieme di processi
Processo
Durata sequenza
P1
24 ms
P2
3 ms
P3
3 ms
Si ha
Schema di arrivo
Tempo medio di attesa
((10−4)+ 4+ 7)/3=5,66 ms
Se nella coda dei processi pronti esistono n processi e il time slice è pari a q , ciascun
processo ottiene 1/n del tempo di elaborazione della CPU e non deve attendere più di
(n−1)×q unità di tempo.
Le prestazioni dell’algoritmo RR dipendono molto dalla dimensione del time slice. Se
questo è molto lungo, il criterio di scheduling RR si riduce al criterio di scheduling FCFS.
Al contrario, se il time slice è molto breve il criterio RR prende il nome di processor sharing e teoricamente gli utenti hanno l’impressione che ciascuno degli n processi disponga di una propria CPU in esecuzione a 1/n della velocità della CPU reale.
Riguardo alle prestazioni dello scheduling RR
bisogna tenere conto dell’effetto dei cambi di
contesto. Più il time slice è piccolo, più cambi
di contesto si hanno. Per questo il time slice
dev’essere ampio rispetto alla durata del
cambio di contesto: nei sistemi moderni un
time slice va dai 10 ai 100 ms, con un cambio di contesto che solitamente è nell’ordine
dei 10 μs.
Anche il tempo di completamento dipende dalla dimensione del time slice e non migliora necessariamente con l’aumento della dimensione del time slice.
Empiricamente si può stabilire che l’80% delle sequenze di operazioni della CPU debba
essere più breve del time slice.
Scheduling a code multiple
Vi sono situazioni in cui i processi si possono classificare facilmente in gruppi diversi.
Una distinzione diffusa è quella tra i processi che si eseguono in primo piano (foreground) o interattivi e i processi che si eseguono in sottofondo (background) o a lotti
(batch). Le due tipologie di processi possono avere diverse necessità di scheduling.
In questo senso può essere utile l’algoritmo di scheduling a code multiple (multilevel
queue scheduling algorithm) che suddivide la coda dei processi in code distinte, ciascuna con il suo algoritmo di scheduling.
Nell’esempio i processi in primo piano e i processi in sottofondo possono usare code distinte, gestite rispettivamente con gli algoritmi RR e FCS.
E’ importante notare che rispetto agli altri algoritmi di scheduling presentati, in questo
caso è necessario avere anche uno scheduling tra le varie code. Si può ad esempio imporre uno scheduling con priorità fissa e con prelazione, in cui ogni coda ha priorità assoluta su quelle a priorità più bassa.
E’ inoltre possibile impostare dei time slice per le code (es.: 80% per la coda dei processi
in primo piano, 20% per la coda dei processi in sottofondo), suddivisibile tra i vari processi contenuti che verranno schedulati con le tecniche classiche.
Scheduling a code multiple con retroazione
Nello scheduling a code multiple i processi si assegnano in modo permanente a una
coda all’entrata nel sistema e non si possono spostare tra le code. Lo scheduling a code
multiple con retroazione (multilevel feedback queue scheduling) permette ai processi,
sotto certe condizioni, di spostarsi tra le code.
Questo schema mantiene infatti i processi con prevalenza di I/O e quelli interattivi nelle code con
priorità più elevata, che può ospitare anche processi che stanno attendendo troppo a lungo. In questo
modo si attua una forma di invecchiamento che impedisce il verificarsi di un’attesa indefinita.
Nell’esempio qui riportato, un processo che entra nella coda dei processi pronti è impilato nella coda 0 e ottiene un time slice di 8 ms. Se in questo tempo non termina, viene
spostato al fondo della coda 1 e ottiene un time slice di 16 ms. Se ancora non è terminato, viene spostato al fondo della coda 2 che segue la filosofia FCFS.
Generalmente uno scheduler a code multiple con retroazione è realizzato dai seguenti
parametri:
• numero di code
• algoritmo di scheduling per ciascuna coda
• metodo usato per determinare quando upgradare un processo
• metodo usato per determinare quando downgradare un processo
• metodo usato per determinare in quale coda si deve immettere un processo
quando chiede un servizio
Dalla trattazione dovrebbe essere chiaro come questo criterio di scheduling si può adattare bene a sistemi specifici. Allo stesso tempo, però, è complicato da progettare.
Scheduling dei thread
Di seguito alcune considerazioni sulla schedulazione dei thread a livello utente e a livello
kernel, con alcuni concetti relativi allo scheduling per Pthreads.
Ambito della competizione
Nei thread utente la contesa per aggiudicarsi la CPU ha luogo tra i thread dello stesso
processo: in questo senso, si parla di ambito della competizione ristretto al processo
(PCS). Per determinare invece quale thread a livello kernel debba essere eseguito dalla
CPU, il kernel esamina il thread di tutto il sistema, nell’ottica di un ambito della competizione allargato al sistema (SCS).
Scheduling di Pthread
Per specificare l’ambito della contesa si usano i valori
• PTHREAD_SCOPE_PROCESS, che pianifica i thread con lo scheduling PCS
• PTHREAD_SCOPE_SYSTEM, che pianifica i thread con lo scheduling SCS
L’interprocess communication di Pthread offre due funzioni per appurare e impostare
l’ambito della contesa
•
•
pthread_attr_setscope(pthread_attr_t *attr, int scope)
pthread_attr_getscope(pthread_attr_t *attr, int *scope)
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
int main(int argc, char *argv[]) {
int i, scope;
pthread_t tid[NUM THREADS];
pthread_attr_t attr;
/* get the default attributes */
pthread_attr_init(&attr);
/* first inquire on the current scope */
if (pthread_attr_getscope(&attr, &scope) != 0)
fprintf(stderr, "Unable to get scope");
else {
if (scope == PTHREAD_SCOPE_PROCESS)
printf("PTHREAD_SCOPE_PROCESS");
else if (scope == PTHREAD_SCOPE_SYSTEM)
printf("PTHREAD_SCOPE_SYSTEM");
else
fprintf(stderr, "Illegal scope value.");
}
/* set the scheduling algorithm to PCS or SCS */
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
/* create the threads */
for (i = 0; i < NUM_THREADS; i++)
pthread_create(&tid[i],&attr,runner,NULL);
/* now join on each thread */
for (i = 0; i < NUM_THREADS; i++)
pthread_join(tid[i], NULL);
}
/* Each thread will begin control in this function */
void *runner(void *param){
/* do some work ... */
pthread_exit(0);
}
Scheduling per sistemi multiprocessore
Se sono disponibili più unita di elaborazione, anche il problema dello scheduling è proporzionalmente più complesso. Nel seguito verranno considerati sistemi in cui le unità di
elaborazione sono, in relazione alle loro funzioni, identiche. Ogni unità di elaborazione
può dunque essere disponibile per eseguire ogni processo nella coda (ma può comunque incorrere in limitazioni).
Soluzioni di scheduling per multiprocessori
Nella multielaborazione simmetrica ogni processore
provvede al proprio scheduling e un processo che viene
pescato dalla coda READY può finire in esecuzione su
una qualsiasi delle CPU che si sono liberate a un dato
istante di tempo t . In questo modo posso tenere il sistema il più equilibrato possibile. Prevede l’utilizzo di
strutture dati condivise.
Utilizzando strutture dati condivise c’è il problema della “vicinanza della memoria”: è
dunque plausibile che un processo che venga eseguito sulla CPU1 viaggi più velocemente rispetto allo stesso eseguito sulla CPU4. Questo porta al concetto di affinità tra processo e (insieme di) processori.
Al contrario, nella multielaborazione asimmetrica c’è un solo processore che accede alle
strutture dati per la schedulazione, rilassando la necessità di dati condivisi. Sono inoltre
possibili soluzioni asimmetriche che cercano di ribilanciare gli sbilanciamenti, fatta da
processi appositi (migrazione guidata) o autonomamente da un processore (migrazione
spontanea).
Processori multicore
Incastrando correttamente i cicli di computazione ai cicli di memoria è possibile ottenere
l’esecuzione in vera concorrenza di due thread.
***