I programmatori C sono spesso diventati volatili per indicare che la variabile potrebbe essere cambiata al di fuori dell'attuale thread di esecuzione; di conseguenza, a volte sono tentati di usarlo nel codice del kernel quando vengono utilizzate strutture di dati condivise. In altre parole, sono noti per trattare i tipi volatili come una sorta di facile variabile atomica, cosa che non lo sono. L'uso di volatile nel codice del kernel non è quasi mai corretto; questo documento descrive il perché.
Il punto chiave da capire riguardo alla volatile è che il suo scopo è sopprimere l'ottimizzazione, che non è quasi mai ciò che si vuole veramente fare. Nel kernel, è necessario proteggere le strutture di dati condivise dall'accesso simultaneo indesiderato, che è un compito molto diverso. Il processo di protezione contro la concorrenza indesiderata eviterà inoltre quasi tutti i problemi relativi all'ottimizzazione in modo più efficiente.
Come volatili, le primitive del kernel che rendono sicuro l'accesso simultaneo ai dati (spinlock, mutex, barriere di memoria, ecc.) Sono progettate per prevenire l'ottimizzazione indesiderata. Se vengono utilizzati correttamente, non sarà necessario utilizzare anche volatile. Se volatile è ancora necessario, c'è quasi sicuramente un bug nel codice da qualche parte. Nel codice del kernel scritto correttamente, volatile può servire solo a rallentare le cose.
Considera un tipico blocco di codice kernel:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Se tutto il codice segue le regole di blocco, il valore di shared_data non può cambiare in modo imprevisto mentre the_lock viene mantenuto. Qualsiasi altro codice che potrebbe voler giocare con quei dati sarà in attesa sul lucchetto. Le primitive spinlock agiscono come barriere di memoria - sono esplicitamente scritte per farlo - il che significa che gli accessi ai dati non saranno ottimizzati su di loro. Quindi il compilatore potrebbe pensare di sapere cosa sarà in shared_data, ma la chiamata spin_lock (), dal momento che agisce come una barriera di memoria, lo costringerà a dimenticare tutto ciò che sa. Non ci saranno problemi di ottimizzazione con l'accesso a tali dati.
Se shared_data fosse dichiarato volatile, il blocco sarebbe comunque necessario. Ma al compilatore verrebbe anche impedito di ottimizzare l'accesso a shared_data all'interno della sezione critica, quando sappiamo che nessun altro può lavorarci. Mentre il blocco viene mantenuto, shared_data non è volatile. Quando si ha a che fare con dati condivisi, il blocco adeguato rende superflua la volatilità e potenzialmente dannosa.
La classe di archiviazione volatile era originariamente pensata per i registri I / O associati alla memoria. All'interno del kernel, anche gli accessi al registro dovrebbero essere protetti da blocchi, ma non si vuole che il compilatore "ottimizzi" gli accessi al registro all'interno di una sezione critica. Ma, all'interno del kernel, gli accessi alla memoria I / O vengono sempre eseguiti tramite le funzioni accessor; l'accesso alla memoria I / O direttamente attraverso i puntatori è mal visto e non funziona su tutte le architetture. Questi accessor sono scritti per prevenire l'ottimizzazione indesiderata, quindi, ancora una volta, la volatilità non è necessaria.
Un'altra situazione in cui si potrebbe essere tentati di usare volatile è quando il processore è impegnato ad aspettare il valore di una variabile. Il modo giusto per eseguire un'attesa impegnativa è:
while (my_variable != what_i_want)
cpu_relax();
La chiamata cpu_relax () può ridurre il consumo di energia della CPU o cedere a un doppio processore hyperthreaded; capita anche che funga da barriera di memoria, quindi, ancora una volta, la volatilità non è necessaria. Certo, l'attesa è generalmente un atto antisociale per cominciare.
Ci sono ancora alcune rare situazioni in cui volatile ha senso nel kernel:
Le funzioni di accesso sopra menzionate potrebbero utilizzare volatili su architetture in cui funziona l'accesso diretto alla memoria I / O. In sostanza, ogni chiamata all'accessorio diventa una piccola sezione critica e garantisce che l'accesso avvenga come previsto dal programmatore.
Codice assembly incorporato che modifica la memoria, ma che non ha altri effetti collaterali visibili, rischia di essere eliminato da GCC. L'aggiunta della parola chiave volatile alle istruzioni asm impedirà questa rimozione.
La variabile jiffies è speciale in quanto può avere un valore diverso ogni volta che viene referenziata, ma può essere letta senza alcun blocco speciale. Quindi i jiffies possono essere volatili, ma l'aggiunta di altre variabili di questo tipo è fortemente disapprovata. Jiffies è considerato un problema di "stupido retaggio" (le parole di Linus) al riguardo; risolverlo sarebbe più un problema di quanto valga la pena.
I puntatori a strutture di dati nella memoria coerente che potrebbero essere modificati da dispositivi I / O possono, a volte, essere legittimamente volatili. Un buffer ad anello utilizzato da una scheda di rete, in cui tale scheda cambia i puntatori per indicare quali descrittori sono stati elaborati, è un esempio di questo tipo di situazione.
Per la maggior parte del codice, non si applica nessuna delle giustificazioni di cui sopra per volatile. Di conseguenza, è probabile che l'uso di volatile sia visto come un bug e porterà ulteriore controllo al codice. Gli sviluppatori che sono tentati di usare la volatilità dovrebbero fare un passo indietro e pensare a ciò che stanno veramente cercando di realizzare.