Scheduling in Linux (Kernel 2.4 e 2.6)
Versioni Kernel Linux
Task in Linux Il kernel Linux tratta thread e processi allo stesso modo, ossia accedendo alle risorse che durante la creazione vengono assegnate e di cui si tiene traccia in una struttura dati chiamata task_struct. Thread appartenenti allo stesso processo possono tuttavia condividere le risorse semplicemente poiche` possiedono lo stesso riferimento ad uno spazio di indirizzamento condiviso. In Linux task = thread o processo
Task List Il kernel memorizza i task in una lista doubled-linked circolare, chiamata task_list. Ciascun elemento di questa lista e` un descrittore di processo di tipo task_struct.
Scheduler Linux 2.4 Il tempo e` diviso in “epoche”, ossia degli intervalli in cui tutti i processi, a turno, eseguono per un quanto di tempo (timeslice). Il valore iniziale del quanto di un processo e’ il quanto di base = 20-N tick del clock dove N in [-20,19] e’ la priorita statica associata al processo (nice) Un valore alto di nice determina un quanto piccolo e quindi priorita’ minore
Esecuzione dei task Ogni task ha un contatore C inizialmente = quanto di base Il contatore viene decrementato in base a quanto tempo il processo rimane in esecuzione (attraverso un timer). Quando tutti i processi eseguibili hanno esaurito il quanto a loro disposizione (cioe C=0 per processi in stato TASK_RUNNING) termina un’epoca e ne inizia un’altra E’ importante notare che processi in attesa possono avere un valore residuo C >0 (quando non hanno esaurito il quanto)
Ricalcolo del quanto Alla fine dell'epoca si ricalcola il quanto di TUTTI i processi (anche quelli in attesa) prendendo il quanto di base incrementato della meta` del tempo rimanente nel contatore. I processi CPU-bound (che consumano interamente il loro quanto di tempo) riceveranno un quanto più o meno sempre pari alla loro priorità I processi IO-bound ricevono un quanto di tempo superiore ai processi CPU-bound. Questo serve perche’ il quanto viene usato anche come priorita’ dinamica
Scelta del prossimo task Dopo un context-switch, lo scheduler confronta le priorita’ dinamiche (goodness) di tutti i processi eseguibili La goodness e’ calcolata come segue: goodness=1000+priorita’ statica, per task real-time goodness=C+priorita’ statica, per task time-sharing con C>0 goodness=0, per task time-sharing con C=0 Manda in esecuzione il task con goodness maggiore
Complessita’ O(n) dello Scheduler Ad ogni cambio di contesto e` necessario iterare tra tutti i processi (per la scelta del prossimo processo) Alla fine di ogni epoca viene effettuato l'aggiornamento dei contatori di ognuno dei processi da eseguire. Queste due operazioni hanno una complessita` che dipende linearmente dal numero di processi in esecuzione, per cui l'algoritmo di scheduling di Linux 2.4 risulta O(n). Al crescere di n le performance dello scheduler peggiorano
Altri problemi scheduler 2.4 In architetture multiprocessore si usa una sola runqueue Positivo per bilanciare il carico delle diverse CPU Tuttavia viene usata in modo esclusivo (viene bloccata) dalla CPU che esegue lo scheduler Le altre CPU devono attendere che la runqueue venga rilasciata (non si frutta il parallelismo)
Kernel 2.6 Il kernel 2.6, utilizza delle nuove strutture dati rispetto alle versioni precedenti Lo scheduler e’ chiamato ``O(1) scheduler’’ perche’ opera in tempo costante O(1) indipendentemente dal numero di thread in competizione per la CPU. Motivazione: supportare al meglio il multithreading nella Java Virtual Machine Inoltre supporta in modo piu naturale sistemi multiprocessore
Strutture dati Ogni CPU ha una runqueue chiamata active runqueue con 140 code di priorita` (liste) gestite FIFO Prime 100 priorita’: real time task Ultime 40: user task Inoltre ogni CPU ha una expired runqueue con la stessa struttura della active runqueue
Ricalcolo delle priorita’ Ogni task ha a disposizione un quanto di tempo (round robin su ogni coda di priorita’) Quando un processo termina il suo quanto viene ricalcolata la sua priorita’ e il processo viene aggiunto in coda alla lista della nuova priorita’ nella expired runqueue Se non ci sono task da eseguire per una certa priorita’ nella active runqueue si scambiano active ed expired (expired diventa la nuova active runqueue)
Active/Expired Runqueue
Selezione Lo scheduler seleziona il task nella lista non vuota con maggiore priorita’ Ad ogni priorita’ viene associata una bitmask. Si possono utilizzare bit-a-bit per rendere l’operazione dipendente dal numero di priorita’ invece che dal numero di processi Questa proprieta’ rende lo scheduler 2.6 un programma con complessita’ O(1).
Active ed expired runqueue
Altre caratteristiche Lo scheduler 2.6 permette la preemption dei task a minor priorita’ Il calcolo dinamico delle priorita’ (in base all’uso della CPU e di operazioni di I/O) evita starvation Ogni runqueue ha un lock separato lock per sfruttare al meglio un sistema multiprocessore (lo scheduler su una CPU non blocca lo scheduler sulle altre) Inoltre utilizza tecniche di load balancing (es. ogni 200ms controlla il carico e se necessario sposta processi dalla runqueue di un processore ad un’altra)