In un sistema del genere, dove i task degli utenti sono separati rispetto al kernel del Sistema Operativo, è possibile eseguire un certo numero di task in modo concorrente. L’alternarsi dei task in rapida successione consente di rendere l’illusione che tali task vengano eseguiti contemporaneamente, anche se il sistema è dotato di una singola unità di calcolo (sistema con singola CPU).
Esistono svariati modi per raggiungere lo scopo, ma affrontiamo qui soltanto i due principali: multiprogrammazione cooperativa e prelativa meglio conosciuti come, rispettivamente, cooperative e preemptive multitasking. Entrambi i metodi presentano molti caratteri comuni e una sostanziale differenza. Vediamo prima le analogie.
Task Context
Con questo termine si definisce un pacchetto di informazioni utile per definire lo stato corrente di un task. Per semplicità non parleremo di Thread e Processi (che sono anch’essi dei task), perchè la loro implementazione differisce moltissimo da un sistema operativo all’altro. Pertanto ci limiteremo a considerare il task come l’unità minima di esecuzione, che su alcuni sistemi potrebbe coincidere con il processo, il thread, il fiber o altra forma di unità di esecuzione. Su alcune implementazioni, addirittura, la distinzione tra Thread e Processi è talmente sfumata da essere del tutto fuorviante.
- interrompere il task corrente
- salvare tutte le informazioni necessarie a caratterizzarlo nell’apposito Task Context
- scegliere il nuovo task da eseguire (Scheduling)
- caricare le informazioni del nuovo Task Context sostituendo quello attuale
Se il meccanismo viene ripetuto varie volte al secondo, si avrà la percezione di un sistema che esegue contemporaneamente un certo numero di task diversi. In gergo tale meccanismo prende il nome di Context Switch.
La differenza tra i due principali tipi di multitasking sta proprio nel meccanismo che interrompe il task corrente. Nel multitasking cooperativo l’interruzione avviene tramite una particolare Syscall chiamata in gergo yield, mentre nel multitasking prelativo il task viene interrotto per mezzo di un’interrupt esterno, generato solitamente da un timer programmabile (la cui frequenza di interruzione può essere fissa o variabile a runtime). Ne consegue che, nel primo caso, il task non viene interrotto finchè questo non cede volontariamente il posto (quindi coopera con gli altri task) chiamando la relativa Syscall; nel secondo caso, invece, il task viene forzatamente interrotto tramite un’interrupt generato dall’hardware, il quale viene a sua volta intercettato dal relativo interrupt handler, come accennato nell’articolo precedente.
Il secondo e l’ultimo meccanismo, cioè il salvataggio ed il caricamento del Context, sono relativamente semplici perchè si tratta di salvare e ricaricare un certo numero di registri della CPU in un determinato buffer di memoria, precedentemente allocato all’atto della creazione del task stesso.
La parte invece più complessa, e sulla quale si scervellano da anni eserciti interi di informatici teorici e matematici, è la parte relativa allo Scheduling. Esistono un’infinità di metodi diversi per scegliere il task successivo da eseguire, e larga parte dell’efficienza di un Sistema Operativo dipende da questo piccolissimo componente che è lo Scheduler.
Vedremo nel prossimo articolo come si distinguono i vari tipi di Scheduler, e quali sono le problematiche ad essi relative.