C-Programmierer haben häufig als volatil angesehen, dass die Variable außerhalb des aktuellen Ausführungsthreads geändert werden kann. Infolgedessen sind sie manchmal versucht, es im Kernel-Code zu verwenden, wenn gemeinsam genutzte Datenstrukturen verwendet werden. Mit anderen Worten, es ist bekannt, dass sie flüchtige Typen als eine Art einfache atomare Variable behandeln, was sie nicht sind. Die Verwendung von flüchtig im Kernel-Code ist fast nie korrekt. Dieses Dokument beschreibt warum.
Der wichtigste Punkt, den man in Bezug auf Volatilität verstehen muss, ist, dass sein Zweck darin besteht, die Optimierung zu unterdrücken, was fast nie das ist, was man wirklich tun möchte. Im Kernel muss man gemeinsam genutzte Datenstrukturen vor unerwünschtem gleichzeitigen Zugriff schützen, was eine ganz andere Aufgabe ist. Durch den Schutz vor unerwünschter Parallelität werden außerdem fast alle Optimierungsprobleme effizienter vermieden.
Die Kernel-Grundelemente, die den gleichzeitigen Zugriff auf Daten sicher machen (Spinlocks, Mutexe, Speicherbarrieren usw.), sind wie flüchtig, um unerwünschte Optimierungen zu verhindern. Wenn sie ordnungsgemäß verwendet werden, müssen sie auch nicht flüchtig verwendet werden. Wenn immer noch flüchtig ist, gibt es mit ziemlicher Sicherheit irgendwo einen Fehler im Code. In richtig geschriebenem Kernel-Code kann flüchtig nur dazu dienen, die Dinge zu verlangsamen.
Betrachten Sie einen typischen Block von Kernel-Code:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Wenn der gesamte Code den Sperrregeln entspricht, kann sich der Wert von shared_data nicht unerwartet ändern, während the_lock gehalten wird. Jeder andere Code, der mit diesen Daten spielen möchte, wartet auf das Schloss. Die Spinlock-Grundelemente fungieren als Speicherbarrieren - sie sind ausdrücklich dafür geschrieben -, was bedeutet, dass Datenzugriffe nicht über sie hinweg optimiert werden. Der Compiler könnte also denken, dass er weiß, was in shared_data enthalten sein wird, aber der Aufruf von spin_lock () zwingt ihn, alles zu vergessen, was er weiß, da er als Speicherbarriere fungiert. Beim Zugriff auf diese Daten treten keine Optimierungsprobleme auf.
Wenn shared_data als flüchtig deklariert würde, wäre die Sperrung weiterhin erforderlich. Der Compiler würde jedoch auch daran gehindert, den Zugriff auf shared_data innerhalb des kritischen Abschnitts zu optimieren , wenn wir wissen, dass niemand anderes damit arbeiten kann. Während die Sperre gehalten wird, sind shared_data nicht flüchtig. Beim Umgang mit gemeinsam genutzten Daten macht eine ordnungsgemäße Sperrung die Flüchtigkeit unnötig - und möglicherweise schädlich.
Die flüchtige Speicherklasse war ursprünglich für speicherabgebildete E / A-Register gedacht. Innerhalb des Kernels sollten auch Registerzugriffe durch Sperren geschützt werden, aber man möchte auch nicht, dass der Compiler die Registerzugriffe innerhalb eines kritischen Abschnitts "optimiert". Innerhalb des Kernels werden E / A-Speicherzugriffe jedoch immer über Zugriffsfunktionen ausgeführt. Der direkte Zugriff auf den E / A-Speicher über Zeiger ist verpönt und funktioniert nicht auf allen Architekturen. Diese Accessoren sind so geschrieben, dass unerwünschte Optimierungen verhindert werden. Daher ist erneut keine Volatilität erforderlich.
Eine andere Situation, in der man versucht sein könnte, flüchtig zu verwenden, besteht darin, dass der Prozessor beschäftigt ist und auf den Wert einer Variablen wartet. Der richtige Weg, um ein geschäftiges Warten durchzuführen, ist:
while (my_variable != what_i_want)
cpu_relax();
Der Aufruf von cpu_relax () kann den CPU-Stromverbrauch senken oder einem Hyperprozessor-Doppelprozessor nachgeben. Es dient auch als Speicherbarriere, so dass wiederum keine Flüchtigkeit erforderlich ist. Natürlich ist das Warten im Allgemeinen in der Regel zunächst ein unsozialer Akt.
Es gibt immer noch einige seltene Situationen, in denen Volatilität im Kernel Sinn macht:
Die oben genannten Accessor-Funktionen können auf Architekturen, auf denen der direkte E / A-Speicherzugriff funktioniert, flüchtig verwendet werden. Im Wesentlichen wird jeder Accessor-Aufruf für sich genommen zu einem kleinen kritischen Abschnitt und stellt sicher, dass der Zugriff wie vom Programmierer erwartet erfolgt.
Inline-Assembler-Code, der den Speicher ändert, aber keine anderen sichtbaren Nebenwirkungen hat, kann von GCC gelöscht werden. Durch Hinzufügen des flüchtigen Schlüsselworts zu asm-Anweisungen wird dieses Entfernen verhindert.
Die Variable jiffies ist insofern besonders, als sie bei jedem Verweis einen anderen Wert haben kann, aber ohne spezielle Sperre gelesen werden kann. So können Jiffies volatil sein, aber die Hinzufügung anderer Variablen dieses Typs ist stark verpönt. Jiffies wird in dieser Hinsicht als "dummes Vermächtnis" (Linus 'Worte) angesehen; es zu reparieren wäre mehr Mühe als es wert ist.
Zeiger auf Datenstrukturen im kohärenten Speicher, die möglicherweise von E / A-Geräten geändert werden, können manchmal rechtmäßig flüchtig sein. Ein von einem Netzwerkadapter verwendeter Ringpuffer, bei dem dieser Adapter die Zeiger ändert, um anzuzeigen, welche Deskriptoren verarbeitet wurden, ist ein Beispiel für diese Art von Situation.
Für den meisten Code gilt keine der oben genannten Begründungen für flüchtig. Infolgedessen wird die Verwendung von volatile wahrscheinlich als Fehler angesehen und bringt zusätzliche Kontrolle in den Code. Entwickler, die versucht sind, volatile zu verwenden, sollten einen Schritt zurücktreten und darüber nachdenken, was sie wirklich erreichen wollen.