Los programadores de C a menudo han tomado volátil para significar que la variable podría cambiarse fuera del hilo actual de ejecución; Como resultado, a veces se sienten tentados a usarlo en el código del núcleo cuando se utilizan estructuras de datos compartidos. En otras palabras, se sabe que tratan los tipos volátiles como una especie de variable atómica fácil, que no lo son. El uso de volátiles en el código del núcleo casi nunca es correcto; Este documento describe por qué.
El punto clave a entender con respecto a los volátiles es que su propósito es suprimir la optimización, que casi nunca es lo que uno realmente quiere hacer. En el núcleo, uno debe proteger las estructuras de datos compartidos contra el acceso concurrente no deseado, que es una tarea muy diferente. El proceso de protección contra la concurrencia no deseada también evitará casi todos los problemas relacionados con la optimización de una manera más eficiente.
Al igual que los volátiles, las primitivas del kernel que hacen que el acceso concurrente a los datos sea seguro (spinlocks, mutexes, barreras de memoria, etc.) están diseñados para evitar la optimización no deseada. Si se usan correctamente, no será necesario usar volátiles también. Si todavía es necesario volátil, es casi seguro que haya un error en el código en alguna parte. En el código de kernel escrito correctamente, volátil solo puede servir para ralentizar las cosas.
Considere un bloque típico de código del núcleo:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Si todo el código sigue las reglas de bloqueo, el valor de shared_data no puede cambiar inesperadamente mientras se mantiene the_lock. Cualquier otro código que quiera jugar con esos datos estará esperando en la cerradura. Las primitivas de spinlock actúan como barreras de memoria, están escritas explícitamente para hacerlo, lo que significa que los accesos a datos no se optimizarán en ellas. Por lo tanto, el compilador podría pensar que sabe lo que estará en shared_data, pero la llamada spin_lock (), ya que actúa como una barrera de memoria, lo obligará a olvidar todo lo que sabe. No habrá problemas de optimización con los accesos a esos datos.
Si shared_data se declarara volátil, el bloqueo seguiría siendo necesario. Pero también se evitaría que el compilador optimice el acceso a shared_data dentro de la sección crítica, cuando sabemos que nadie más puede trabajar con él. Mientras se mantiene el bloqueo, shared_data no es volátil. Cuando se trata de datos compartidos, el bloqueo adecuado hace que la volatilidad sea innecesaria y potencialmente dañina.
La clase de almacenamiento volátil se diseñó originalmente para registros de E / S mapeados en memoria. Dentro del núcleo, los accesos de registro también deben estar protegidos por bloqueos, pero tampoco se quiere que el compilador "optimice" los accesos de registro dentro de una sección crítica. Pero, dentro del núcleo, los accesos a la memoria de E / S siempre se realizan a través de funciones de acceso; acceder a la memoria de E / S directamente a través de punteros está mal visto y no funciona en todas las arquitecturas. Esos accesorios están escritos para evitar la optimización no deseada, por lo que, una vez más, la volatilidad es innecesaria.
Otra situación en la que uno podría verse tentado a usar volátiles es cuando el procesador está ocupado esperando el valor de una variable. La forma correcta de realizar una espera ocupada es:
while (my_variable != what_i_want)
cpu_relax();
La llamada cpu_relax () puede reducir el consumo de energía de la CPU o ceder el paso a un procesador gemelo con hiperprocesamiento; También sirve como barrera de memoria, por lo que, una vez más, lo volátil es innecesario. Por supuesto, la espera ocupada es generalmente un acto antisocial para empezar.
Todavía hay algunas situaciones raras en las que volátil tiene sentido en el núcleo:
Las funciones de acceso mencionadas anteriormente pueden usar volátiles en arquitecturas donde el acceso directo a la memoria de E / S sí funciona. Esencialmente, cada llamada de acceso se convierte en una pequeña sección crítica por sí misma y asegura que el acceso se realice como lo espera el programador.
El código de ensamblaje en línea que cambia la memoria, pero que no tiene otros efectos secundarios visibles, corre el riesgo de ser eliminado por GCC. Agregar la palabra clave volátil a las declaraciones asm evitará esta eliminación.
La variable jiffies es especial porque puede tener un valor diferente cada vez que se hace referencia, pero se puede leer sin ningún bloqueo especial. Por lo tanto, los jiffies pueden ser volátiles, pero la adición de otras variables de este tipo está muy mal vista. Jiffies se considera un tema de "legado estúpido" (palabras de Linus) a este respecto; arreglarlo sería más problemático de lo que vale.
Los punteros a estructuras de datos en memoria coherente que pueden ser modificados por dispositivos de E / S pueden, a veces, legítimamente ser volátiles. Un buffer de anillo utilizado por un adaptador de red, donde ese adaptador cambia los punteros para indicar qué descriptores se han procesado, es un ejemplo de este tipo de situación.
Para la mayoría de los códigos, no se aplica ninguna de las justificaciones anteriores para volátiles. Como resultado, es probable que el uso de volátiles se vea como un error y traerá un escrutinio adicional al código. Los desarrolladores que se sienten tentados a usar volátiles deben dar un paso atrás y pensar en lo que realmente están tratando de lograr.