C flüchtige Variablen und Cache-Speicher

Lesezeit: 10 Minuten

Benutzeravatar von Microkernel
Mikrokern

Der Cache wird von der Cache-Hardware transparent für den Prozessor gesteuert. Wenn wir also flüchtige Variablen im C-Programm verwenden, wie wird garantiert, dass mein Programm jedes Mal Daten von der tatsächlich angegebenen Speicheradresse liest, aber nicht vom Cache?

Mein Verständnis ist, dass

  1. Das Schlüsselwort Volatile teilt dem Compiler mit, dass die Variablenreferenzen nicht optimiert und wie im Code programmiert gelesen werden sollten.

  2. Der Cache wird transparent von der Cache-Hardware gesteuert. Wenn der Prozessor eine Adresse ausgibt, weiß er daher nicht, ob die Daten aus dem Cache oder dem Speicher stammen.

Wenn ich also jedes Mal eine Speicheradresse lesen muss, wie kann ich sicherstellen, dass sie nicht vom Cache, sondern von der erforderlichen Adresse stammt?

Irgendwie passen diese beiden Konzepte nicht gut zusammen. Bitte klären Sie, wie es gemacht wird.

(Stellen Sie sich vor, wir haben eine Write-Back-Richtlinie im Cache (falls für die Analyse des Problems erforderlich))

Danke Microkernel 🙂

Benutzeravatar von Andrew Cottrell
Andreas Cottrell

Firmware-Entwickler hier. Dies ist ein Standardproblem in der Embedded-Programmierung und eines, das viele (selbst sehr erfahrene) Entwickler ins Straucheln bringt.

Ich gehe davon aus, dass Sie versuchen, auf ein Hardwareregister zuzugreifen, und dass sich dieser Registerwert im Laufe der Zeit ändern kann (sei es Interrupt-Status, Timer, GPIO-Anzeigen usw.).

Das volatile Schlüsselwort ist nur ein Teil der Lösung und in vielen Fällen möglicherweise nicht erforderlich. Dadurch wird die Variable erneut gelesen Erinnerung jedes Mal, wenn es verwendet wird (im Gegensatz zu einer Optimierung durch den Compiler oder einer Speicherung in einem Prozessorregister über mehrere Verwendungen hinweg), aber ob die “Erinnerung” gelesen wird, ist ein tatsächliches Hardwareregister im Gegensatz zu einem zwischengespeicherten Speicherort, der Ihrem Code unbekannt ist und von dem nicht beeinflusst wird volatile Stichwort. Wenn Ihre Funktion das Register nur einmal liest, können Sie wahrscheinlich aufhören volatileaber als allgemeine Regel schlage ich vor, dass die meisten Hardware-Register als definiert werden sollten volatile.

Das größere Problem ist das Caching und die Cache-Kohärenz. Der einfachste Ansatz besteht hier darin, sicherzustellen, dass sich Ihr Register im nicht zwischengespeicherten Adressraum befindet. Das bedeutet, dass Sie bei jedem Zugriff auf das Register garantiert das tatsächliche Hardwareregister und nicht den Cache-Speicher lesen / schreiben. Ein komplexerer, aber möglicherweise leistungsstärkerer Ansatz besteht darin, zwischengespeicherten Adressraum zu verwenden und Ihren Code Cache-Updates für bestimmte Situationen wie diese manuell zu erzwingen. Wie dies bewerkstelligt wird, ist für beide Ansätze architekturabhängig und geht über den Rahmen der Frage hinaus. Es könnte MTRRs (für x86), MMU, Seitentabellenänderungen usw. beinhalten.

Ich hoffe, das hilft. Wenn ich etwas verpasst habe, lassen Sie es mich wissen und ich werde meine Antwort erweitern.

  • Der Zweck von volatileWenn Sie einen guten Compiler verwenden, sollten Sie sicherstellen, dass der generierte Code den Prozessor über alles informiert, was vor einem bestimmten Punkt geschrieben werden muss, und den Prozessor erst danach auffordert, Informationen zu lesen. Ein Programmierer muss möglicherweise auch systeminterne oder andere Mittel verwenden, um Hardware-Cache-Flushes zu erzwingen, aber das Erzwingen eines Hardware-Cache-Flushens wäre nutzlos, wenn ein Compiler Dinge auf eine Weise zwischenspeichern würde, von der die Hardware nichts wusste.

    – Superkatze

    18. Februar 2017 um 23:33 Uhr

Aus Ihrer Frage geht ein Missverständnis Ihrerseits hervor.
Volatile Das Schlüsselwort bezieht sich nicht auf den Cache, wie Sie ihn beschreiben.

Beim Stichwort volatile für eine Variable angegeben ist, gibt dies dem Compiler einen Hinweis, bestimmte Optimierungen nicht vorzunehmen, da sich diese Variable von anderen Teilen des Programms unerwartet ändern kann.

Gemeint ist hier, dass der Compiler den Wert nicht wiederverwenden soll bereits in ein Register geladenaber greifen Sie erneut auf den Speicher zu, da nicht garantiert ist, dass der Wert im Register mit dem im Speicher gespeicherten Wert übereinstimmt.

Der Rest bezüglich des Cache-Speichers ist nicht direkt mit dem Programmierer verbunden.

Ich meine, die Synchronisierung eines beliebigen Cache-Speichers der CPU mit dem RAM ist ein ganz anderes Thema.

  • Wenn also, wie ich einen Fall genommen hatte, eine Variable von einem anderen Thread oder Treiber aktualisiert wird, der vom Eingabegerät liest, was ist die Garantie, dass ich den richtigen Wert lese und nicht etwas zwischengespeichert? Wie vermeidet man ein solches Szenario in einem Code?

    – Mikrokern

    24. Oktober 2011 um 8:14 Uhr

  • Wenn du benutzt volatile Es ist garantiert, dass Sie immer das neueste Update lesen, das im Speicher von einem anderen Thread durchgeführt wurde. Aber ich habe das Gefühl, dass Ihre Bedenken eher auf der Ebene des Betriebssystems liegen, dh Cache- vs. Speichersynchronisierung

    – Kratylos

    24. Oktober 2011 um 8:28 Uhr


  • @Cratylus Wenn Sie Threads verwenden, sind “neueste”, “vergangene” … zwischen Threads, die auf Diff-Kernen ausgeführt werden, nicht klar definiert.

    – Neugieriger

    13. November 2019 um 3:24 Uhr

Mein Vorschlag ist, die Seite vom virtuellen Speichermanager als nicht zwischengespeichert zu markieren.
Unter Windows erfolgt dies über die Einstellung PAGE_NOCACHE beim Anrufen VirtualProtect.

Für einen etwas anderen Zweck, die SSE2-Anweisungen habe den _mm_stream_xyz Anweisungen zur Vermeidung von Cache-Verschmutzung, obwohl ich glaube, dass sie hier nicht auf Ihren Fall zutreffen.

In beiden Fällen gibt es keine tragbar Art, in C zu tun, was Sie wollen; Sie müssen die Betriebssystemfunktionalität verwenden.

  • Es kommt also auf die Plattform an? Daher wird der Cache nicht von der Cache-Hardware gesteuert? (Wenn die Hardware den Cache vollständig verwaltet, würde sie nicht nach dem Flag PAGE_NOCACHE suchen, oder?)

    – Mikrokern

    24. Oktober 2011 um 7:02 Uhr

  • @Microkernel: Es ist von der Hardware verwaltet. Aber das Betriebssystem sagt der Hardware, was sie tun soll (schließlich hat die Hardware keine Ahnung, wie das Betriebssystem den Speicher verwalten möchte), und Sie fordern das Betriebssystem auf, das zu tun, was Sie wollen. Und all diese Informationen werden gespeichert in – raten Sie mal wo? — Erinnerung selbst. Es ist jedoch ein passiver Prozess – das Betriebssystem greift nur ein, wenn etwas schief geht (z. B. Seitenfehler). Abgesehen davon fährt die Hardware einfach fort, das zu tun, worum das Betriebssystem sie gebeten hat, ohne dass das Betriebssystem eingreift.

    – Benutzer541686

    24. Oktober 2011 um 7:03 Uhr


  • Hmm, OK … Irgendwo scheint mein Verständnis falsch zu sein, ich habe immer geglaubt, dass der CPU-Cache für alle anderen als die Cache-Hardware transparent ist! Irgendwelche Referenzen, die ich lesen muss, um meine Konzepte richtig zu machen? ! Vielen Dank für die Aufklärung 🙂

    – Mikrokern

    24. Oktober 2011 um 7:11 Uhr


  • @Microkernel: Sicher! 🙂 Grundsätzlich speichert das Betriebssystem alle seine Speicherverwaltungsinformationen in “Seitentabellen” im Speicher und teilt der CPU mit, wo sie nach den Informationen suchen soll. Die CPU verwaltet dann alles und bittet das Betriebssystem um “Hilfe”, wenn es sich nicht entscheiden kann, was zu tun ist. Sie können über Paging lesen hier und über das Caching hier; lassen Sie mich wissen, wenn Sie noch Fragen haben. (Deshalb sagen sie, dass das Betriebssystem zwischen Hardware und Software sitzt – das tut es wirklich!)

    – Benutzer541686

    24. Oktober 2011 um 7:14 Uhr


Wikipedia hat einen ziemlich guten Artikel über MTRR (Memory Type Range Registers) die für die x86-CPU-Familie gelten.

Zusammenfassend lässt sich sagen, dass ab dem Pentium Pro von Intel (und von AMD kopiert) diese MTR-Register vorhanden waren, die ungecachte, Write-Through-, Write-Combining-, Write-Protect- oder Write-Back-Attribute für Speicherbereiche setzen konnten.

Beginnend mit dem Pentium III, aber meines Wissens nur mit 64-Bit-Prozessoren wirklich nützlich, berücksichtigen sie die MTRRs, können jedoch durch die Seitenattributtabellen überschrieben werden, mit denen die CPU einen Speichertyp für jede Speicherseite festlegen kann.

Eine Hauptverwendung der MTRRs, die ich kenne, ist Grafik-RAM. Es ist viel effizienter, es als Write-Combining zu markieren. Dadurch kann der Cache die Schreibvorgänge speichern und alle Schreibreihenfolgeregeln des Speichers werden gelockert, um Burst-Schreibvorgänge mit sehr hoher Geschwindigkeit auf einer Grafikkarte zu ermöglichen.

Aber für Ihre Zwecke möchten Sie entweder eine MTRR- oder eine PAT-Einstellung von entweder ungecacht oder Write-Through.

Wie Sie sagen, ist der Cache für den Programmierer transparent. Das System garantiert, dass Sie immer den zuletzt geschriebenen Wert sehen, wenn Sie auf ein Objekt über dessen Adresse zugreifen. Das “einzige”, was Ihnen passieren kann, wenn sich ein veralteter Wert in Ihrem Cache befindet, ist eine Laufzeitstrafe.

  • Nur wenn die Maschine nur eine CPU hat.

    – JeremyP

    24. Oktober 2011 um 7:53 Uhr

  • @JeremyP, ich denke, die Frage hier wurde über den Rahmen des gleichzeitigen Zugriffs auf gemeinsam genutzten Speicher hinaus gestellt. Wenn man das zusätzlich hat, ja, wird alles viel komplizierter. Sie müssten dann die geeigneten Tools anwenden, um die Datenkonsistenz sicherzustellen. Aber das ist ein allgemeineres Problem, es aus dem Blickwinkel von Caches zu betrachten, ist wahrscheinlich auch nicht die richtige Ansicht.

    – Jens Gustedt

    24. Oktober 2011 um 7:58 Uhr

  • Ich glaube nicht, dass es den Rahmen des gleichzeitigen Zugriffs auf den Speicher sprengte. Die Prämisse der Frage ist, dass dort ist gleichzeitiger Zugriff auf den Speicher, ansonsten ist der Cache, wie Sie betonen, transparent.

    – JeremyP

    24. Oktober 2011 um 8:05 Uhr

  • Die Maschine muss nicht mehr als eine CPU haben. Speicherabgebildete Gerätesteuerregister können den gleichen Effekt haben (bei Hard-MCUs kann der Designer darauf achten, diesen Adressraum nicht zwischenzuspeichern, bei Softcores auf FPGAs/PLDs nicht unbedingt). Siehe Seite 4 von altera.com/ja_JP/pdfs/literature/hb/nios2/n2sw_nii52007.pdf

    – Dimitri

    8. Oktober 2015 um 15:37 Uhr

  • @JeremyP “Nur wenn die Maschine nur eine CPU hat” Das ist nicht immer falsch, aber extrem irreführend. Es sollte lauten: nur wenn die Maschine nicht über mehrere Recheneinheiten verfügt, die nicht für Gewindeunterstützungen vorgesehen sind. Wenn die CPU für die Unterstützung von Threads ausgelegt ist, ist dies garantiert.

    – Neugieriger

    13. November 2019 um 3:10 Uhr

Benutzeravatar von mouviciel
mouviciel

volatile stellt sicher, dass Daten jedes Mal gelesen werden, wenn sie benötigt werden, ohne sich um einen Cache zwischen CPU und Speicher zu kümmern. Wenn Sie jedoch tatsächliche Daten aus dem Speicher und keine zwischengespeicherten Daten lesen müssen, haben Sie zwei Möglichkeiten:

  • Erstellen Sie ein Board, auf dem die Daten nicht zwischengespeichert werden. Dies kann bereits der Fall sein, wenn Sie ein I/O-Gerät adressieren,
  • Verwenden Sie bestimmte CPU-Anweisungen, die den Cache umgehen. Dies wird verwendet, wenn Sie den Speicher löschen müssen, um mögliche SEU-Fehler zu aktivieren.

Die Details der zweiten Option hängen vom Betriebssystem und/oder der CPU ab.

  • Nur wenn die Maschine nur eine CPU hat.

    – JeremyP

    24. Oktober 2011 um 7:53 Uhr

  • @JeremyP, ich denke, die Frage hier wurde über den Rahmen des gleichzeitigen Zugriffs auf gemeinsam genutzten Speicher hinaus gestellt. Wenn man das zusätzlich hat, ja, wird alles viel komplizierter. Sie müssten dann die geeigneten Tools anwenden, um die Datenkonsistenz sicherzustellen. Aber das ist ein allgemeineres Problem, es aus dem Blickwinkel von Caches zu betrachten, ist wahrscheinlich auch nicht die richtige Ansicht.

    – Jens Gustedt

    24. Oktober 2011 um 7:58 Uhr

  • Ich glaube nicht, dass es den Rahmen des gleichzeitigen Zugriffs auf den Speicher sprengte. Die Prämisse der Frage ist, dass dort ist gleichzeitiger Zugriff auf den Speicher, ansonsten ist der Cache, wie Sie betonen, transparent.

    – JeremyP

    24. Oktober 2011 um 8:05 Uhr

  • Die Maschine muss nicht mehr als eine CPU haben. Speicherabgebildete Gerätesteuerregister können den gleichen Effekt haben (bei Hard-MCUs kann der Designer darauf achten, diesen Adressraum nicht zwischenzuspeichern, bei Softcores auf FPGAs/PLDs nicht unbedingt). Siehe Seite 4 von altera.com/ja_JP/pdfs/literature/hb/nios2/n2sw_nii52007.pdf

    – Dimitri

    8. Oktober 2015 um 15:37 Uhr

  • @JeremyP “Nur wenn die Maschine nur eine CPU hat” Das ist nicht immer falsch, aber extrem irreführend. Es sollte lauten: nur wenn die Maschine nicht über mehrere Recheneinheiten verfügt, die nicht für Gewindeunterstützungen vorgesehen sind. Wenn die CPU für die Unterstützung von Threads ausgelegt ist, ist dies garantiert.

    – Neugieriger

    13. November 2019 um 3:10 Uhr

Benutzeravatar von Scott Roepnack
Scott Röpnack

Die Verwendung des Schlüsselworts _Uncached kann in eingebetteten Betriebssystemen wie MQX hilfreich sein

#define MEM_READ(addr)       (*((volatile _Uncached unsigned int *)(addr)))
#define MEM_WRITE(addr,data) (*((volatile _Uncached unsigned int *)(addr)) = data)

  • Der Code-Button ist aus einem bestimmten Grund da. Bitte missbrauchen Sie die Formatierung nicht.

    – A–C

    17. Dezember 2012 um 22:19 Uhr

  • Welcher Compiler unterstützt die _Uncached Stichwort? Wenn Sie nach “_Uncached” googeln, erhalten Sie Ihre Antwort als erstes Ergebnis.

    – Manuel Jakob

    24. Mai 2016 um 20:16 Uhr

1395080cookie-checkC flüchtige Variablen und Cache-Speicher

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy