Les programmeurs C ont souvent pris volatile pour signifier que la variable pouvait être modifiée en dehors du thread d'exécution actuel; par conséquent, ils sont parfois tentés de l'utiliser dans le code du noyau lorsque des structures de données partagées sont utilisées. En d'autres termes, ils sont connus pour traiter les types volatils comme une sorte de variable atomique facile, ce qu'ils ne sont pas. L'utilisation de volatile dans le code du noyau n'est presque jamais correcte; ce document explique pourquoi.
Le point clé à comprendre en ce qui concerne la volatilité est que son but est de supprimer l'optimisation, ce qui n'est presque jamais ce que l'on veut vraiment faire. Dans le noyau, il faut protéger les structures de données partagées contre les accès simultanés indésirables, ce qui est une tâche très différente. Le processus de protection contre la concurrence indésirable permettra également d'éviter presque tous les problèmes liés à l'optimisation d'une manière plus efficace.
Comme volatile, les primitives du noyau qui sécurisent l'accès simultané aux données (verrous rotatifs, mutex, barrières mémoire, etc.) sont conçues pour empêcher une optimisation indésirable. S'ils sont utilisés correctement, il ne sera pas nécessaire d'utiliser également volatile. Si volatile est toujours nécessaire, il y a presque certainement un bogue dans le code quelque part. Dans le code du noyau correctement écrit, volatile ne peut servir qu'à ralentir les choses.
Considérez un bloc typique de code du noyau:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Si tout le code suit les règles de verrouillage, la valeur de shared_data ne peut pas changer de façon inattendue pendant que the_lock est maintenu. Tout autre code qui pourrait vouloir jouer avec ces données attendra sur le verrou. Les primitives de verrouillage tournant agissent comme des barrières de mémoire - elles sont explicitement écrites pour ce faire - ce qui signifie que les accès aux données ne seront pas optimisés entre elles. Ainsi, le compilateur peut penser qu'il sait ce qu'il y aura dans shared_data, mais l'appel spin_lock (), puisqu'il agit comme une barrière mémoire, le forcera à oublier tout ce qu'il sait. Il n'y aura aucun problème d'optimisation avec les accès à ces données.
Si shared_data était déclaré volatile, le verrouillage serait toujours nécessaire. Mais le compilateur serait également empêché d'optimiser l'accès à shared_data dans la section critique, quand nous savons que personne d'autre ne peut travailler avec. Tant que le verrou est maintenu, shared_data n'est pas volatile. Lorsqu'il s'agit de données partagées, un verrouillage approprié rend la volatilité inutile - et potentiellement dangereuse.
La classe de stockage volatile était à l'origine destinée aux registres d'E / S mappés en mémoire. Au sein du noyau, les accès aux registres doivent également être protégés par des verrous, mais on ne veut pas non plus que le compilateur «optimise» les accès aux registres dans une section critique. Mais, au sein du noyau, les accès à la mémoire d'E / S se font toujours via les fonctions d'accès; l'accès direct à la mémoire d'E / S via des pointeurs est mal vu et ne fonctionne pas sur toutes les architectures. Ces accesseurs sont écrits pour empêcher une optimisation indésirable, donc, encore une fois, volatile n'est pas nécessaire.
Une autre situation où l'on pourrait être tenté d'utiliser volatile est lorsque le processeur est occupé à attendre la valeur d'une variable. La bonne façon d'effectuer une attente occupée est:
while (my_variable != what_i_want)
cpu_relax();
L'appel cpu_relax () peut réduire la consommation d'énergie du processeur ou céder la place à un double processeur hyperthread; il se trouve également qu'il sert de barrière mémoire, donc, encore une fois, volatile n'est pas nécessaire. Bien sûr, l'attente occupée est généralement un acte antisocial pour commencer.
Il existe encore quelques rares situations où volatile a du sens dans le noyau:
Les fonctions d'accesseur mentionnées ci-dessus peuvent utiliser volatile sur les architectures où l'accès direct à la mémoire d'E / S fonctionne. Essentiellement, chaque appel d'accès devient une petite section critique en soi et garantit que l'accès se déroule comme prévu par le programmeur.
Le code d'assemblage en ligne qui change la mémoire, mais qui n'a pas d'autres effets secondaires visibles, risque d'être supprimé par GCC. L'ajout du mot-clé volatile aux instructions asm empêchera cette suppression.
La variable jiffies est spéciale en ce qu'elle peut avoir une valeur différente à chaque fois qu'elle est référencée, mais elle peut être lue sans aucun verrouillage spécial. Les jiffies peuvent donc être volatiles, mais l'ajout d'autres variables de ce type est fortement désapprouvé. Jiffies est considéré comme un problème d '«héritage stupide» (les mots de Linus) à cet égard; le réparer serait plus difficile que cela ne vaut la peine.
Les pointeurs vers des structures de données dans une mémoire cohérente qui pourraient être modifiées par des périphériques d'E / S peuvent, parfois, être légitimement volatils. Une mémoire tampon en anneau utilisée par une carte réseau, où cette carte change de pointeurs pour indiquer quels descripteurs ont été traités, est un exemple de ce type de situation.
Pour la plupart des codes, aucune des justifications ci-dessus pour volatile ne s'applique. En conséquence, l'utilisation de volatile est susceptible d'être considérée comme un bogue et apportera un examen supplémentaire au code. Les développeurs qui sont tentés d'utiliser volatile devraient prendre du recul et réfléchir à ce qu'ils essaient vraiment d'accomplir.