135 lines
6.6 KiB
ReStructuredText
135 lines
6.6 KiB
ReStructuredText
|
.. include:: ../disclaimer-ita.rst
|
||
|
|
||
|
:Original: :ref:`Documentation/process/volatile-considered-harmful.rst <volatile_considered_harmful>`
|
||
|
:Translator: Federico Vaga <federico.vaga@vaga.pv.it>
|
||
|
|
||
|
.. _it_volatile_considered_harmful:
|
||
|
|
||
|
Perché la parola chiave "volatile" non dovrebbe essere usata
|
||
|
------------------------------------------------------------
|
||
|
|
||
|
Spesso i programmatori C considerano volatili quelle variabili che potrebbero
|
||
|
essere cambiate al di fuori dal thread di esecuzione corrente; come risultato,
|
||
|
a volte saranno tentati dall'utilizzare *volatile* nel kernel per le
|
||
|
strutture dati condivise. In altre parole, gli è stato insegnato ad usare
|
||
|
*volatile* come una variabile atomica di facile utilizzo, ma non è così.
|
||
|
L'uso di *volatile* nel kernel non è quasi mai corretto; questo documento ne
|
||
|
descrive le ragioni.
|
||
|
|
||
|
Il punto chiave da capire su *volatile* è che il suo scopo è quello di
|
||
|
sopprimere le ottimizzazioni, che non è quasi mai quello che si vuole.
|
||
|
Nel kernel si devono proteggere le strutture dati condivise contro accessi
|
||
|
concorrenti e indesiderati: questa è un'attività completamente diversa.
|
||
|
Il processo di protezione contro gli accessi concorrenti indesiderati eviterà
|
||
|
anche la maggior parte dei problemi relativi all'ottimizzazione in modo più
|
||
|
efficiente.
|
||
|
|
||
|
Come *volatile*, le primitive del kernel che rendono sicuro l'accesso ai dati
|
||
|
(spinlock, mutex, barriere di sincronizzazione, ecc) sono progettate per
|
||
|
prevenire le ottimizzazioni indesiderate. Se vengono usate opportunamente,
|
||
|
non ci sarà bisogno di utilizzare *volatile*. Se vi sembra che *volatile* sia
|
||
|
comunque necessario, ci dev'essere quasi sicuramente un baco da qualche parte.
|
||
|
In un pezzo di codice kernel scritto a dovere, *volatile* può solo servire a
|
||
|
rallentare le cose.
|
||
|
|
||
|
Considerate questo 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 seguisse le regole di sincronizzazione, il valore di un
|
||
|
dato condiviso non potrebbe cambiare inaspettatamente mentre si trattiene un
|
||
|
lock. Un qualsiasi altro blocco di codice che vorrà usare quel dato rimarrà
|
||
|
in attesa del lock. Gli spinlock agiscono come barriere di sincronizzazione
|
||
|
- sono stati esplicitamente scritti per agire così - il che significa che gli
|
||
|
accessi al dato condiviso non saranno ottimizzati. Quindi il compilatore
|
||
|
potrebbe pensare di sapere cosa ci sarà nel dato condiviso ma la chiamata
|
||
|
spin_lock(), che agisce come una barriera di sincronizzazione, gli imporrà di
|
||
|
dimenticarsi tutto ciò che sapeva su di esso.
|
||
|
|
||
|
Se il dato condiviso fosse stato dichiarato come *volatile*, la
|
||
|
sincronizzazione rimarrebbe comunque necessaria. Ma verrà impedito al
|
||
|
compilatore di ottimizzare gli accessi al dato anche _dentro_ alla sezione
|
||
|
critica, dove sappiamo che in realtà nessun altro può accedervi. Mentre si
|
||
|
trattiene un lock, il dato condiviso non è *volatile*. Quando si ha a che
|
||
|
fare con dei dati condivisi, un'opportuna sincronizzazione rende inutile
|
||
|
l'uso di *volatile* - anzi potenzialmente dannoso.
|
||
|
|
||
|
L'uso di *volatile* fu originalmente pensato per l'accesso ai registri di I/O
|
||
|
mappati in memoria. All'interno del kernel, l'accesso ai registri, dovrebbe
|
||
|
essere protetto dai lock, ma si potrebbe anche desiderare che il compilatore
|
||
|
non "ottimizzi" l'accesso ai registri all'interno di una sezione critica.
|
||
|
Ma, all'interno del kernel, l'accesso alla memoria di I/O viene sempre fatto
|
||
|
attraverso funzioni d'accesso; accedere alla memoria di I/O direttamente
|
||
|
con i puntatori è sconsigliato e non funziona su tutte le architetture.
|
||
|
Queste funzioni d'accesso sono scritte per evitare ottimizzazioni indesiderate,
|
||
|
quindi, di nuovo, *volatile* è inutile.
|
||
|
|
||
|
Un'altra situazione dove qualcuno potrebbe essere tentato dall'uso di
|
||
|
*volatile*, è nel caso in cui il processore è in un'attesa attiva sul valore
|
||
|
di una variabile. Il modo giusto di fare questo tipo di attesa è il seguente::
|
||
|
|
||
|
while (my_variable != what_i_want)
|
||
|
cpu_relax();
|
||
|
|
||
|
La chiamata cpu_relax() può ridurre il consumo di energia del processore
|
||
|
o cedere il passo ad un processore hyperthreaded gemello; funziona anche come
|
||
|
una barriera per il compilatore, quindi, ancora una volta, *volatile* non è
|
||
|
necessario. Ovviamente, tanto per puntualizzare, le attese attive sono
|
||
|
generalmente un atto antisociale.
|
||
|
|
||
|
Ci sono comunque alcune rare situazioni dove l'uso di *volatile* nel kernel
|
||
|
ha senso:
|
||
|
|
||
|
- Le funzioni d'accesso sopracitate potrebbero usare *volatile* su quelle
|
||
|
architetture che supportano l'accesso diretto alla memoria di I/O.
|
||
|
In pratica, ogni chiamata ad una funzione d'accesso diventa una piccola
|
||
|
sezione critica a se stante, e garantisce che l'accesso avvenga secondo
|
||
|
le aspettative del programmatore.
|
||
|
|
||
|
- I codice *inline assembly* che fa cambiamenti nella memoria, ma che non
|
||
|
ha altri effetti espliciti, rischia di essere rimosso da GCC. Aggiungere
|
||
|
la parola chiave *volatile* a questo codice ne previene la rimozione.
|
||
|
|
||
|
- La variabile jiffies è speciale in quanto assume un valore diverso ogni
|
||
|
volta che viene letta ma può essere lette senza alcuna sincronizzazione.
|
||
|
Quindi jiffies può essere *volatile*, ma l'aggiunta ad altre variabili di
|
||
|
questo è sconsigliata. Jiffies è considerata uno "stupido retaggio"
|
||
|
(parole di Linus) in questo contesto; correggerla non ne varrebbe la pena e
|
||
|
causerebbe più problemi.
|
||
|
|
||
|
- I puntatori a delle strutture dati in una memoria coerente che potrebbe
|
||
|
essere modificata da dispositivi di I/O può, a volte, essere legittimamente
|
||
|
*volatile*. Un esempio pratico può essere quello di un adattatore di rete
|
||
|
che utilizza un puntatore ad un buffer circolare, questo viene cambiato
|
||
|
dall'adattatore per indicare quali descrittori sono stati processati.
|
||
|
|
||
|
Per la maggior parte del codice, nessuna delle giustificazioni sopracitate può
|
||
|
essere considerata. Di conseguenza, l'uso di *volatile* è probabile che venga
|
||
|
visto come un baco e porterà a verifiche aggiuntive. Gli sviluppatori tentati
|
||
|
dall'uso di *volatile* dovrebbero fermarsi e pensare a cosa vogliono davvero
|
||
|
ottenere.
|
||
|
|
||
|
Le modifiche che rimuovono variabili *volatile* sono generalmente ben accette
|
||
|
- purché accompagnate da una giustificazione che dimostri che i problemi di
|
||
|
concorrenza siano stati opportunamente considerati.
|
||
|
|
||
|
Riferimenti
|
||
|
===========
|
||
|
|
||
|
[1] http://lwn.net/Articles/233481/
|
||
|
|
||
|
[2] http://lwn.net/Articles/233482/
|
||
|
|
||
|
Crediti
|
||
|
=======
|
||
|
|
||
|
Impulso e ricerca originale di Randy Dunlap
|
||
|
|
||
|
Scritto da Jonathan Corbet
|
||
|
|
||
|
Migliorato dai commenti di Satyam Sharma, Johannes Stezenbach, Jesper
|
||
|
Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn, e Stefan Richter.
|