I kernel unix tradizionali avevano un singolo blocco, che era tenuto da un thread mentre il codice del kernel era in esecuzione. Pertanto nessun altro codice del kernel potrebbe interrompere quel thread.
Ciò ha reso più semplice la progettazione del kernel, poiché sapevi che mentre un thread utilizzava le risorse del kernel, nessun altro thread lo era. Pertanto i diversi thread non possono rovinare il lavoro degli altri.
Nei sistemi a processore singolo questo non causa troppi problemi.
Tuttavia, nei sistemi multiprocessore, potresti avere una situazione in cui diversi thread su processori o core diversi desiderano eseguire tutti il codice del kernel contemporaneamente. Ciò significa che, a seconda del tipo di carico di lavoro, potresti avere molti processori, ma tutti trascorrono la maggior parte del tempo aspettandosi l'un l'altro.
In Linux 2.6, le risorse del kernel erano divise in unità molto più piccole, protette da blocchi individuali, e il codice del kernel veniva rivisto per assicurarsi che i blocchi fossero mantenuti solo mentre le risorse corrispondenti erano in uso. Quindi ora processori diversi devono solo aspettarsi l'un l'altro se vogliono accedere alla stessa risorsa (ad esempio risorsa hardware).
Prima della versione 2.5.4 del kernel Linux, il kernel Linux non era preventivo, il che significa che un processo in esecuzione in modalità kernel non può essere spostato fuori dal processore finché non lascia esso stesso il processore o inizia ad attendere il completamento di alcune operazioni di input/output.
Generalmente un processo in modalità utente può entrare in modalità kernel utilizzando le chiamate di sistema. In precedenza, quando il kernel non era preemptive, un processo con priorità inferiore poteva invertire la priorità di un processo con priorità più alta negandogli l'accesso al processore chiamando ripetutamente chiamate di sistema e rimanendo in modalità kernel. Anche se il periodo di tempo del processo con priorità inferiore fosse scaduto, continuerebbe a funzionare fino a quando non ha completato il suo lavoro nel kernel o ha ceduto volontariamente il controllo. Se il processo con priorità più alta in attesa di essere eseguito è un editor di testo in cui l'utente sta digitando o un lettore MP3 pronto a ricaricare il suo buffer audio, il risultato è una scarsa prestazione interattiva. In questo modo il kernel non preventivo era un grave svantaggio in quel momento.
Immagina la semplice visione del multitasking preventivo. Abbiamo due attività utente, entrambe sempre in esecuzione senza utilizzare alcun I/O o eseguire chiamate al kernel. Queste due attività non devono fare nulla di speciale per poter essere eseguite su un sistema operativo multi-tasking. Il kernel, in genere basato su un interrupt del timer, decide semplicemente che è il momento di mettere in pausa un'attività per farne eseguire un'altra. L'attività in questione è completamente all'oscuro che sia successo qualcosa.
Tuttavia, la maggior parte delle attività effettua richieste occasionali al kernel tramite chiamate di sistema. Quando ciò accade, esiste lo stesso contesto utente, ma la CPU sta eseguendo il codice del kernel per conto di tale attività.
I vecchi kernel Linux non consentirebbero mai la prelazione di un'attività mentre era impegnata a eseguire il codice del kernel. (Nota che le operazioni di I/O vengono sempre riprogrammate volontariamente. Sto parlando di un caso in cui il codice del kernel ha alcune operazioni ad alta intensità di CPU come l'ordinamento di un elenco.)
Se il sistema consente l'esecuzione anticipata di tale attività mentre è in esecuzione il codice del kernel poi abbiamo quello che viene chiamato un "kernel preventivo". Tale sistema è immune da ritardi imprevedibili che possono verificarsi durante le chiamate di sistema, quindi potrebbe essere più adatto per attività integrate o in tempo reale.
Ad esempio, se su una particolare CPU sono disponibili due attività e una esegue una chiamata di sistema che richiede 5 ms per essere completata e l'altra è un'applicazione di lettore MP3 che deve alimentare il tubo audio ogni 2 ms, potresti sentire un audio intermittente.
L'argomento contro la prelazione è che tutto il codice del kernel che potrebbe essere chiamato nel contesto dell'attività deve essere in grado di sopravvivere alla prelazione:ad esempio, c'è molto codice di driver di dispositivo scadente che potrebbe essere migliore se è sempre in grado di completare un'operazione prima consentendo l'esecuzione di altre attività su quel processore. (Con i sistemi multiprocessore la regola piuttosto che l'eccezione in questi giorni, tutto il codice del kernel deve essere rientrante, quindi quell'argomento non è così rilevante oggi.) Inoltre, se lo stesso obiettivo potesse essere raggiunto migliorando le chiamate di sistema con cattive latenza, forse la prelazione non è necessaria.
Un compromesso è CONFIG_PREEMPT_VOLUNTARY, che consente un cambio di attività in determinati punti all'interno del kernel, ma non ovunque. Se c'è solo un numero limitato di punti in cui il codice del kernel potrebbe impantanarsi, questo è un modo economico per ridurre la latenza mantenendo gestibile la complessità.
La prelazione consente al kernel di dare l'IMPRESSIONE del parallelismo:hai un solo processore (diciamo un decennio fa), ma ti sembra che tutti i tuoi processi funzionino simultaneamente. Questo perché il kernel anticipa (cioè toglie l'esecuzione da) l'esecuzione da un processo per darla a quello successivo (magari in base alla loro priorità).
MODIFICA I kernel non preventivi aspettano che i processi restituiscano la mano (ad esempio, durante le chiamate di sistema), quindi se il tuo processo calcola molti dati e non chiama alcun tipo di yield
funzione, gli altri processi non saranno in grado di eseguire le loro chiamate. Si dice che tali sistemi siano cooperativi perché richiedono la collaborazione dei processi per garantire l'equità dei tempi di esecuzione
MODIFICA 2 L'obiettivo principale della prelazione è migliorare la reattività del sistema tra più attività, quindi va bene per gli utenti finali, mentre d'altra parte, i server vogliono ottenere il massimo throughput, quindi non ne hanno bisogno:(da la configurazione del kernel Linux)
- Kernel prerilasciabile (desktop a bassa latenza)
- Prelazione volontaria del kernel (desktop)
- Nessuna prelazione forzata (server)