Os programadores C geralmente consideram volátil o significado de que a variável pode ser alterada fora do encadeamento atual de execução; como resultado, às vezes são tentados a usá-lo no código do kernel quando estruturas de dados compartilhadas estão sendo usadas. Em outras palavras, eles são conhecidos por tratar tipos voláteis como uma espécie de variável atômica fácil, o que não é. O uso de volátil no código do kernel quase nunca é correto; este documento descreve o porquê.
O ponto principal a ser entendido em relação ao volátil é que seu objetivo é suprimir a otimização, que quase nunca é o que realmente se deseja fazer. No kernel, é necessário proteger as estruturas de dados compartilhadas contra acesso simultâneo indesejado, o que é uma tarefa muito diferente. O processo de proteção contra concorrência indesejada também evitará quase todos os problemas relacionados à otimização de maneira mais eficiente.
Como voláteis, as primitivas do kernel que tornam seguro o acesso simultâneo aos dados (spinlocks, mutexes, barreiras de memória etc.) são projetadas para impedir a otimização indesejada. Se eles estiverem sendo usados corretamente, não haverá necessidade de usar voláteis também. Se o volátil ainda for necessário, há quase certamente um bug no código em algum lugar. No código do kernel corretamente escrito, o volátil pode servir apenas para retardar as coisas.
Considere um bloco típico de código do kernel:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Se todo o código seguir as regras de bloqueio, o valor de shared_data não poderá ser alterado inesperadamente enquanto o bloqueio for mantido. Qualquer outro código que queira jogar com esses dados estará aguardando o bloqueio. As primitivas spinlock agem como barreiras de memória - elas foram escritas explicitamente para isso - o que significa que o acesso a dados não será otimizado entre eles. Portanto, o compilador pode pensar que sabe o que haverá em shared_data, mas a chamada spin_lock (), uma vez que atua como uma barreira de memória, forçará a esquecer qualquer coisa que ele saiba. Não haverá problemas de otimização no acesso a esses dados.
Se shared_data fosse declarado volátil, o bloqueio ainda seria necessário. Mas o compilador também seria impedido de otimizar o acesso a shared_data na seção crítica, quando sabemos que ninguém mais pode trabalhar com ele. Enquanto o bloqueio é mantido, shared_data não é volátil. Ao lidar com dados compartilhados, o bloqueio adequado torna volátil desnecessário - e potencialmente prejudicial.
A classe de armazenamento volátil foi originalmente criada para registros de E / S mapeados na memória. Dentro do kernel, os acessos ao registro também devem ser protegidos por bloqueios, mas também não se deseja que o compilador "otimize" os acessos ao registro em uma seção crítica. Mas, dentro do kernel, os acessos à memória de E / S são sempre feitos através das funções do acessador; acessar a memória de E / S diretamente através de ponteiros é desaprovado e não funciona em todas as arquiteturas. Esses acessadores são gravados para impedir a otimização indesejada; portanto, mais uma vez, a volatilidade é desnecessária.
Outra situação em que alguém pode ficar tentado a usar volátil é quando o processador está ocupado aguardando o valor de uma variável. A maneira correta de executar uma espera ocupada é:
while (my_variable != what_i_want)
cpu_relax();
A chamada cpu_relax () pode diminuir o consumo de energia da CPU ou render a um processador gêmeo com hyperthread; isso também serve como barreira à memória; portanto, mais uma vez, volátil é desnecessário. Obviamente, a espera ocupada é geralmente um ato anti-social, para começar.
Ainda existem algumas situações raras em que volátil faz sentido no kernel:
As funções do acessador acima mencionadas podem ser voláteis em arquiteturas nas quais o acesso direto à memória de E / S funciona. Essencialmente, cada chamada do acessador se torna uma seção crítica por si só e garante que o acesso ocorra conforme o esperado pelo programador.
O código de montagem embutido que altera a memória, mas que não possui outros efeitos colaterais visíveis, corre o risco de ser excluído pelo GCC. Adicionar a palavra-chave volátil às instruções asm impedirá essa remoção.
A variável jiffies é especial, pois pode ter um valor diferente toda vez que é referenciada, mas pode ser lida sem nenhum bloqueio especial. Portanto, os instantes podem ser voláteis, mas a adição de outras variáveis desse tipo é fortemente desaprovada. Jiffies é considerado uma questão de "legado estúpido" (palavras de Linus) a esse respeito; consertá-lo seria mais complicado do que vale a pena.
Ponteiros para estruturas de dados na memória coerente que podem ser modificados por dispositivos de E / S podem, às vezes, ser legitimamente voláteis. Um buffer de anel usado por um adaptador de rede, em que esse adaptador altera os ponteiros para indicar quais descritores foram processados, é um exemplo desse tipo de situação.
Para a maioria dos códigos, nenhuma das justificativas acima para volatilidade se aplica. Como resultado, é provável que o uso de volátil seja visto como um bug e trará um exame adicional ao código. Os desenvolvedores que são tentados a usar o volátil devem dar um passo atrás e pensar no que estão realmente tentando realizar.