eingebettet C - mit "flüchtig", um Konsistenz zu behaupten

8

Betrachten Sie den folgenden Code:

%Vor%

Meine Absicht ist, dass PrevSample , CurrentSample und NextSample konsistent sind, auch wenn gSampleIndex während des Aufrufs von Process() aktualisiert wird.

Wird die Zuweisung zum localSampleIndex den Trick machen, oder besteht die Möglichkeit, dass es weg optimiert wird, obwohl gSampleIndex flüchtig ist?

    
bavaza 12.11.2014, 11:33
quelle

2 Antworten

0

In Ihrer Funktion greifen Sie nur einmal auf die volatile -Variable zu (und es ist die einzige volatile one in dieser Funktion), so dass Sie sich keine Gedanken über Code-Reorganisation , die der Compiler ausführen kann (und volatile verhindert). Was Standard für diese Optimierungen in §5.1.2.3 sagt, ist:

  

In der abstrakten Maschine werden alle Ausdrücke wie in der Semantik angegeben ausgewertet. Eine tatsächliche Implementierung muss einen Teil eines Ausdrucks nicht auswerten, wenn er daraus schließen kann, dass sein Wert nicht verwendet wird und keine erforderlichen Nebenwirkungen erzeugt werden (einschließlich solcher, die durch Aufruf einer Funktion oder Zugriff auf ein flüchtiges Objekt verursacht werden).

Beachten Sie den letzten Satz: "... keine benötigten Nebenwirkungen werden erzeugt (... auf ein flüchtiges Objekt zugreifen)" .

Simply volatile verhindert, dass ein Optimierungscompiler diesen Code ausführen kann. Um nur einige zu nennen: keine Instruktions-Neuordnung bezüglich anderer volatile Variablen. kein Ausdruck entfernen, kein Caching, keine Wertweiterleitung über Funktionen.

BTW: Ich bezweifle, dass irgendein Compiler den Code brechen könnte (mit oder ohne volatile ). Möglicherweise wird die lokale Stapelvariable entfernt, aber der Wert wird in einer Registry gespeichert (sicher wird nicht wiederholt auf einen Speicherort zugegriffen). Was Sie brauchen volatile für ist Wert Sichtbarkeit.

BEARBEITEN

Ich denke, es ist eine Klärung erforderlich.

Lassen Sie mich sicher annehmen, dass Sie wissen, was Sie tun (Sie arbeiten mit Interrupt-Handlern, so sollte dies nicht Ihr erstes C-Programm sein): CPU-Wort stimmt mit Ihrem Variablentyp überein und der Speicher ist richtig ausgerichtet.

Lassen Sie mich auch annehmen, dass Ihr Interrupt nicht reentrant ist (einige magic cli / sti sachen oder was auch immer Ihre CPU dafür verwendet), außer Sie planen ein schweres Debugging und Tuning .

Wenn diese Annahmen erfüllt sind, brauchen Sie keine atomaren Operationen. Warum? Weil localSampleIndex = gSampleIndex atomar ist (weil es richtig ausgerichtet ist, Wortgröße stimmt und es ist volatile ), gibt es mit ++gSampleIndex keine Wettlaufbedingung ( HandleSomeIrq wird nicht erneut aufgerufen, solange es noch ausgeführt wird). Mehr als nutzlos sie sind falsch .

Man könnte denken: "OK, ich brauche vielleicht keinen atomaren, aber warum kann ich sie nicht benutzen? Auch wenn solche Annahmen erfüllt sind, ist das ein * extra * und es wird dasselbe Ziel erreichen." . Nein, tut es nicht . Atomic hat nicht die gleiche Semantik von volatile Variablen (und selten volatile ist / sollte außerhalb der Speicherabbildung von E / A und Signalverarbeitung verwendet werden). Flüchtig (normalerweise) ist nutzlos mit atomaren (es sei denn, eine bestimmte Architektur sagt es), aber es hat einen großen Unterschied: Sichtbarkeit . Wenn Sie gSampleIndex in HandleSomeIrq standard aktualisieren, wird garantiert, dass der Wert sofort für alle Threads (und Geräte) sichtbar ist. mit atomic_uint Standard garantiert es in einer angemessenen Zeit .

Um es kurz und klar zu machen: flüchtig und atomar sind nicht dasselbe . Atomare Operationen sind nützlich für den Parallelbetrieb, flüchtige sind nützlich für die unteren Ebenen (Interrupts, Geräte). Wenn du immer noch denkst "hey sie tun * genau * was ich brauche" lies bitte einige nützliche Links, die aus Kommentaren ausgewählt wurden: Cache-Kohärenz und eine nette Lektüre über Atomics.

Zusammengefasst:
In Ihrem Fall können Sie eine atomare Variable mit einer Sperre verwenden (um sowohl atomaren Zugriff als auch Wertsichtbarkeit zu haben), aber niemand auf dieser Erde würde eine Sperre in einen Interrupt-Handler setzen (es sei denn, absolut zweifelsfrei zweifellos erforderlich, und aus Code, den Sie gepostet haben) nicht dein Fall).

    
Adriano Repetti 12.11.2014, 11:48
quelle
8

Im Prinzip ist volatile nicht genug, um sicherzustellen, dass Process nur konsistente Werte von gSampleIndex sieht. In der Praxis sollten jedoch keine Probleme auftreten, wenn uinit32_t direkt von der Hardware unterstützt wird. Die richtige Lösung wäre die Verwendung atomarer Zugriffe.

Das Problem

Angenommen, Sie verwenden eine 16-Bit-Architektur, so dass der Befehl

lautet %Vor%

wird in zwei Anweisungen kompiliert (Laden der oberen Hälfte, Laden der unteren Hälfte). Dann kann der Interrupt zwischen den beiden Anweisungen aufgerufen werden, und Sie erhalten die Hälfte des alten Werts mit der Hälfte des neuen Werts kombiniert.

Die Lösung

Die Lösung besteht darin, nur mit atomaren Operationen auf gSampleCounter zuzugreifen. Ich kenne drei Möglichkeiten, dies zu tun.

C11 Atomics

In C11 (unterstützt seit GCC 4.9) deklarieren Sie Ihre Variable als atomar:

%Vor%

Sie achten darauf, dass Sie immer nur über die dokumentierten atomaren Schnittstellen auf die Variable zugreifen. Im IRQ-Handler:

%Vor%

und in der Funktion Process :

%Vor%

Machen Sie sich nicht die _explicit -Varianten der atomaren Funktionen zunutze, es sei denn, Sie versuchen, Ihr Programm über eine große Anzahl von Kernen skalieren zu lassen.

GCC-Atom

Selbst wenn Ihr Compiler C11 noch nicht unterstützt, hat er wahrscheinlich einige Unterstützung für atomare Operationen. Zum Beispiel können Sie in GCC sagen:

%Vor%

Wie oben sollten Sie sich nicht mit einer schwachen Konsistenz befassen, es sei denn, Sie versuchen ein gutes Skalierungsverhalten zu erreichen.

Implementieren von atomaren Operationen selbst

Da Sie nicht versuchen, sich gegen den gleichzeitigen Zugriff von mehreren Kernen zu schützen, sondern nur durch Bedingungen mit einem Interrupt-Handler, ist es möglich, ein Konsistenzprotokoll nur unter Verwendung von Standard-C-Primitiven zu implementieren. Der Dekker-Algorithmus ist das älteste bekannte Protokoll.

    
jch 12.11.2014 14:10
quelle

Tags und Links