C'è molta varietà di programmatori perché c'è una grande varietà di esigenze, dai sistemi in tempo reale in cui l'intero sistema è scritto in una volta sola e l'ordine preciso e i tempi della pianificazione sono noti quando il dispositivo è costruito, per sistemi di fascia alta in cui tutta la filettatura è preventiva tranne per alcune sezioni molto brevi nel kernel.
È certamente possibile che un thread dichiari che si trova in una sezione critica e non vuole essere interrotto. Per definizione, questo non è più uno scheduler completamente preventivo, ma una sorta di modalità ibrida basata sulla prelazione, ma in cui la prevenzione può essere disabilitata.
In effetti, praticamente ogni sistema contiene almeno un codice di basso livello che non deve essere prevenuto perché sta facendo qualcosa all'hardware, come nell'esempio che si dà di modificare registri multipli che devono essere eseguiti in un colpo solo. Questo non è limitato ai microcontrollori. I gestori di interrupt e il codice di commutazione del contesto contengono quasi sempre una sezione critica, anche se si tratta di poche istruzioni. A livello di metallo nudo, una sezione critica significa semplicemente disabilitare gli interrupt. I gestori di interrupt iniziano con gli interrupt disabilitati o, in caso contrario, esiste almeno un meccanismo che impedisce allo stesso interrupt di interrompere un'istanza precedente di se stesso.
I sistemi operativi progettati per hardware single-core possono utilizzare sezioni critiche per implementare meccanismi di trasmissione dei messaggi, poiché il passaggio di un messaggio di solito comporta la modifica di più strutture di dati (associate al mittente e al destinatario) in modo coerente. Sui sistemi multi-core, questo non può essere fatto perché il mittente e il ricevitore potrebbero essere in esecuzione su diversi core.
A un livello superiore, esistono sistemi operativi in cui i thread possono garantire che non verranno interrotti e l'API offre chiamate di sistema come start_critical_section
/ end_critical_section
. Questo può essere fatto solo se si ritiene che il thread non entri in un loop infinito o che curi la CPU. Una tale sezione critica potrebbe infatti essere interrotta da interrupt, ma lo scheduler ritorna allo stesso thread dopo l'interruzione.
Sono possibili sezioni critiche che sono interrompibili dopo un timeout. Non so quanto siano comuni; Avrebbero senso in sistemi "soft real-time" come l'elaborazione multimediale in cui i thread hanno bisogno di elaborare fotogrammi audio o video in modo tempestivo, e la caduta di un fotogramma è disapprovata ma non esclusa.
Informare un thread che è stato preventivato è difficile da fare in modo utile. Impostare un po 'in una posizione ben nota è facile. Il problema è come reagisce il thread. È stato indicato che non voleva essere interrotto, quindi presumibilmente non controllerà quel bit. Un possibile progetto prevede che il thread abbia i checkpoint; ad esempio, in un ciclo esterno durante l'elaborazione dei frame, un thread di elaborazione dei media può controllare se il frame corrente è stato eliminato. Un altro possibile progetto è quello di uccidere il thread (ma eventualmente lasciare lo stack dietro per l'analisi) ed eseguire un altro thread come gestore di eccezioni. Ancora un altro design è di sollevare un'eccezione all'interno del thread; ciò richiede un accoppiamento stretto tra il meccanismo di pianificazione e l'ambiente di esecuzione del thread.
Esistono altri modi per limitare i modi in cui i thread possono essere anticipati ad un livello elevato. Uno semplice è prioritario: uno scheduler predisporrà solo un thread per eseguire un thread con priorità più alta. Se il thread A non vuole essere interrotto dal thread B, A dichiara una priorità più alta di B.
In un sistema multi-applicazione in cui ogni applicazione può avere più thread, può avere senso avere uno scheduler ibrido che preveda preventivamente le applicazioni, ma cooperando tra i thread della stessa applicazione. In questo modo, un thread di applicazione che non restituisce mai impedirà l'esecuzione solo dei thread nella stessa applicazione. Le applicazioni possono utilizzare la memoria condivisa con gli switch di contesto solo in momenti prevedibili per i suoi meccanismi interni, mentre le comunicazioni tra le applicazioni avvengono solo tramite primitive di messaggistica del sistema operativo.