Mein Verständnis der Semantik von volatile
in C und C ++ ist, dass es den Speicherzugriff in (beobachtbar ) Nebenwirkungen . Beim Lesen oder Schreiben in eine Memory-Mapped-Datei (oder Shared Memory) würde ich erwarten, dass der Zeiger flüchtig ist, um anzuzeigen, dass es sich tatsächlich um I / O handelt. (John Regehr hat einen sehr guten Artikel über die Semantik von volatile
geschrieben.)
Außerdem würde ich erwarten, Funktionen wie memcpy()
für den Zugriff auf den gemeinsamen Speicher als inkorrekt zu verwenden, da die Signatur darauf hinweist, dass die flüchtige Qualifizierung weggeworfen wird und der Speicherzugriff nicht als E / A behandelt wird.
In meinen Augen ist dies ein Argument zugunsten von std::copy()
, wo das flüchtige Qualifikationsmerkmal nicht weggeworfen wird und Speicherzugriffe korrekt als I / O behandelt werden.
Meine Erfahrung mit dem Verwenden von Zeigern auf flüchtige Objekte und std::copy()
für den Zugriff auf im Speicher abgebildete Dateien ist jedoch, dass es um Größenordnungen langsamer ist als nur die Verwendung von memcpy()
. Ich bin zu der Schlussfolgerung gelangt, dass Clamb und GCC bei ihrer Behandlung von volatile
vielleicht zu konservativ sind. Ist das der Fall?
Welche Anleitung gibt es für den Zugriff auf den geteilten Speicher in Bezug auf volatile
, wenn ich dem Buchstaben des Standards folgen und die Semantik, auf die ich mich verlasse, zurückbekomme?
Relevantes Zitat aus dem Standard [intro.execution] §14 :
Lesen eines Objekts, das durch einen flüchtigen glvalue gekennzeichnet ist, Ändern eines Objekt, Aufruf einer Bibliothek I / O-Funktion oder Aufruf einer Funktion, die ist eine dieser Operationen alle Nebenwirkungen, die Änderungen sind im Zustand der Ausführungsumgebung. Bewertung eines Ausdrucks (oder ein Teilausdruck) umfasst im Allgemeinen beide Wertberechnungen (einschließlich der Bestimmung der Identität eines Objekts für glvalue Auswerten und Abrufen eines zuvor einem Objekt zugewiesenen Wertes für Prvalue-Evaluierung) und Einleitung von Nebenwirkungen. Wenn ein Anruf an a Bibliotheks-I / O-Funktion gibt zurück oder einen Zugriff durch einen flüchtigen glvalue bewertet wird, wird die Nebenwirkung als abgeschlossen betrachtet, obwohl einige externe Aktionen, die durch den Aufruf (wie die E / A selbst) oder durch der flüchtige Zugriff wurde möglicherweise noch nicht abgeschlossen.
Mein Verständnis der Semantik von flüchtigen in C und C ++ ist, dass es den Speicherzugriff in I / O
verwandelt
Nein, das tut es nicht. All volatile
bewirkt, dass vom Programmierer an den Compiler kommuniziert wird, dass ein bestimmter Speicherbereich jederzeit durch "etwas anderes" geändert werden kann.
"Etwas anderes" könnte eine Menge verschiedener Dinge sein. Beispiele:
Da der Standard (5.1.2.3) garantiert, dass ein Zugriff (Lesen / Schreiben) auf ein flüchtiges Objekt nicht wegoptimiert werden kann, kann volatile
auch verwendet werden, um bestimmte Compiler-Optimierungen zu blockieren, was vor allem in Hardware nützlich ist. verwandte Programmierung.
Beim Lesen oder Schreiben in eine Memory-Mapped-Datei (oder Shared Memory) würde ich erwarten, dass der Zeiger flüchtig ist.
Nicht unbedingt, nein. Die Art der Daten spielt keine Rolle, nur wie sie aktualisiert wird.
Ich würde erwarten, Funktionen wie memcpy () zu verwenden, um auf den gemeinsamen Speicher als inkorrekt zuzugreifen
Insgesamt kommt es auf Ihre Definition von "Shared Memory" an. Das ist ein Problem mit Ihrer ganzen Frage, weil Sie immer wieder von "shared memory" sprechen, was kein formeller, wohldefinierter Begriff ist. Speicher geteilt mit einem anderen ISR / Thread / Prozess?
Ja, Speicher, der mit einem anderen ISR / Thread / Prozess geteilt wird könnte je nach Compiler als volatile
deklariert werden. Aber das ist only becaue volatile
kann verhindern, dass ein Compiler inkorrekte Annahmen trifft, und den Code, der auf solche "shared" Variablen zugreift, auf die falsche Weise optimieren. Etwas, das besonders bei älteren Compilern für eingebettete Systeme vorkommt. Es sollte nicht auf modernen Hosted-System-Compilern erforderlich sein.
volatile
führt nicht zu einem Speicherbarrierenverhalten. Es erzwingt (notwendigerweise) nicht, dass Ausdrücke in einer bestimmten Reihenfolge ausgeführt werden.
volatile
garantiert sicherlich keine Form von Atomizität. Aus diesem Grund wurde das Qualifikationsmerkmal _Atomic
type zur Sprache C hinzugefügt.
Also zurück zum Kopierproblem - wenn der Speicherbereich zwischen mehreren ISRs / Threads / Prozessen "geteilt" wird, wird volatile
überhaupt nicht helfen. Stattdessen benötigen Sie einige Mittel zur Synchronisation, wie zum Beispiel einen Mutex, Semaphor oder einen kritischen Abschnitt.
In meinen Augen ist dies ein Argument zugunsten von std :: copy (), wo das flüchtige Qualifikationsmerkmal nicht weggeworfen wird und Speicherzugriffe korrekt als I / O behandelt werden.
Nein, das ist nur falsch, aus den bereits erwähnten Gründen.
Welche Anleitung gibt es, um auf flüchtigen Speicher zuzugreifen, wenn ich dem Buchstaben des Standards folgen und die Semantik, auf die ich mich verlasse, zurückbekommen möchte?
Verwenden Sie systemspezifische API: s, um den Speicherzugriff durch Mutex / Semaphor / kritischen Abschnitt zu schützen.
Ich denke, dass Sie das überdenken. Ich sehe keinen Grund dafür, dass mmap
oder der entsprechende Speicher (ich verwende hier die POSIX-Terminologie) volatil ist.
Aus der Sicht des Compilers gibt mmap
ein Objekt zurück, das geändert und dann an msync
oder munmap
oder die implizierte unmap während _Exit
übergeben wird. Diese Funktionen müssen als I / O behandelt werden, sonst nichts.
Sie könnten mmap
durch malloc
+ read
und munmap
durch write
+ free
ersetzen und Sie erhalten die meisten Garantien, wann und wie I / O durchgeführt wird.
Beachten Sie, dass dies nicht einmal erfordert, dass die Daten an munmap
zurückgegeben werden, es war einfach einfacher, dies auf diese Weise zu demonstrieren. Sie können mmap
ein Stück Speicher zurückgeben und es intern in einer Liste speichern, dann eine Funktion (nennen wir msyncall
), die keine Argumente hat, die den gesamten Speicher ausgibt alle Aufrufe an mmap
zuvor zurückgegeben. Wir können daraus dann aufbauen und sagen, dass jede Funktion, die I / O ausführt, eine implizite msyncall
hat. Wir müssen jedoch nicht so weit gehen. Aus der Sicht eines Compilers ist libc eine schwarze Box, in der einige Funktionen etwas Speicher zurückliefern, der Speicher muss vor jedem anderen Aufruf in libc synchronisiert sein, da der Compiler nicht wissen kann, welche Speicherbits zuvor von libc zurückgegeben wurden sind immer noch referenziert und im aktiven Gebrauch innerhalb.
Der obige Absatz funktioniert so in der Praxis, aber wie können wir es aus Sicht eines Standards angehen? Sehen wir uns zuerst ein ähnliches Problem an. Bei Threads wird der Shared Memory nur bei einigen sehr spezifischen Funktionsaufrufen synchronisiert. Dies ist sehr wichtig, da moderne CPUs Lese- und Schreibvorgänge neu ordnen und Speicherbarrieren teuer sind und alte CPUs explizite Cache-Flushs benötigen, bevor geschriebene Daten von anderen sichtbar sind (sei es andere Threads, Prozesse oder I / O). Die Spezifikation für mmap
sagt:
Die Anwendung muss eine korrekte Synchronisierung sicherstellen, wenn mmap () in Verbindung mit einer anderen Dateizugriffsmethode verwendet wird
, aber es gibt nicht an, wie diese Synchronisation durchgeführt wird. Ich weiß in der Praxis, dass die Synchronisation ziemlich viel msync
sein muss, da es immer noch Systeme gibt, bei denen Lesen / Schreiben nicht denselben Seitencache wie mmap verwendet.
Mein Verständnis der Semantik von flüchtigen in C und C ++ ist, dass es wandelt den Speicherzugriff in E / A um
Ihr Verständnis ist falsch. Flüchtiges Objekt ist nebensächlich flüchtig - und sein Wert kann durch etwas geändert werden, das für den Compiler während der Kompilierung nicht sichtbar ist
so volatile Objekt muss einen permanenten (in seinem Umfang natürlich) Speicherplatz haben, muss vor jeder Verwendung von ihm gelesen und nach jeder Änderung gespeichert werden
Siehe das Beispiel: Ссылка
BTW IMO dieser Artikel ist Quatsch - es geht davon aus, dass Programmierer denken, dass Volatilität auch Atomizität und Kohärenz bedeutet, was nicht die Wahrheit ist. Dieser Artikel sollte einen Titel haben - "Warum mein Verständnis von Volatilität falsch war und warum ich immer noch in der Welt der Mythen lebe"
Tags und Links c c++ language-lawyer volatile mmap