Das Dekrementieren eines Elements eines (flüchtigen) Einzelbyte-Arrays in C++ ist nicht atomar! WIESO DEN? (Auch: Wie erzwinge ich Atomarität in Atmel AVR mcus/Arduino)

Lesezeit: 8 Minuten

Benutzer-Avatar
Gabriel Staples

Ich habe gerade Tage verloren, buchstäblich ~25 Stunden Arbeit, weil ich versucht habe, meinen Code wegen etwas Einfachem zu debuggen, das ich nicht kannte.

Es stellt sich heraus, dass das Dekrementieren eines Elements eines Einzelbyte-Arrays in C++ auf einem AVR ATmega328 8-Bit-Mikrocontroller (Arduino) keine atomare Operation ist und atomare Zugriffsschutzfunktionen erfordert (nämlich das Ausschalten von Interrupts). Warum ist das??? Was sind alle C-Techniken, um den atomaren Zugriff auf Variablen auf einem Atmel AVR-Mikrocontroller sicherzustellen?

Hier ist eine verdummte Version dessen, was ich getan habe:

//global vars:
const uint8_t NUM_INPUT_PORTS = 3;
volatile uint8_t numElementsInBuf[NUM_INPUT_PORTS];

ISR(PCINT0_vect) //external pin change interrupt service routine on input port 0
{
  //do stuff here
  for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
    numElementsInBuf[i]++;
}

loop()
{
  for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
  {
    //do stuff here
    numElementsInBuf[i]--; //<--THIS CAUSES ERRORS!!!!! THE COUNTER GETS CORRUPTED.
  }
}

Hier ist die Version von Schleife, die in Ordnung ist:

loop()
{
  for (uint8_t i=0; i<NUM_INPUT_PORTS; i++)
  {
    //do stuff here
    noInterrupts(); //globally disable interrupts 
    numElementsInBuf[i]--; //now it's ok...30 hrs of debugging....
    interrupts(); //globally re-enable interrupts 
  }
}

Beachten Sie die “atomaren Zugriffsschutzvorrichtungen”, dh: Deaktivieren von Interrupts vor dem Dekrementieren und anschließendes erneutes Aktivieren.

Da ich es hier mit einem einzelnen Byte zu tun hatte, wusste ich nicht, dass ich atomare Zugriffsschutzvorrichtungen benötigen würde. Warum brauche ich sie für diesen Fall? Ist das typisches Verhalten? Ich weiß, dass ich sie brauchen würde, wenn dies ein Array von 2-Byte-Werten wäre, aber warum für 1-Byte-Werte???? Normalerweise sind hier für 1-Byte-Werte keine atomaren Zugriffsschutzvorrichtungen erforderlich …


Aktualisieren: Lesen Sie hier den Abschnitt “Atomic Access”: http://www.gammon.com.au/interrupts. Dies ist eine großartige Quelle.


Verwandte (Antwort für STM32 mcus):

Wir kennen also das Lesen von oder das Schreiben zu jede Single-Byte-Variable Auf AVR ist 8-Bit-MCU eine atomare Operation, aber was ist mit STM32 32-Bit-MCU? Welche Variablen haben automatische atomare Lese- und Schreibvorgänge auf STM32? Die Antwort ist hier: Welche Variablentypen/-größen sind auf STM32-Mikrocontrollern atomar?.

  • Wieso den? Weil es die Norm vorschreibt. Der Mythos, dass auf einzelne Ints oder Bytes automatisch atomar zugegriffen wird, stammt von Intel-Prozessoren, die sehr nachsichtig sind. C++ nicht.

    – nwp

    3. April 2016 um 5:42 Uhr

  • kannst du mir eine Quelle nennen? Ich werde mich auch umsehen.

    – Gabriel Staples

    3. April 2016 um 5:43 Uhr

  • Inkrement/Dekrement-Operationen auf vielen (den meisten?) Plattformen sind nicht atomar, da sie auf unterschiedlichen Lese-/Änderungs-/Schreib-Primitiven beruhen.

    – Michael Burr

    3. April 2016 um 5:54 Uhr

  • Zusamenfassend, volatile in C oder C++ hat nichts mit Atomizität, Synchronisation zwischen Threads oder ähnlichem zu tun. Die Leute denken immer wieder, dass es so ist, aber es ist nicht so. Dies unterscheidet sich zumindest von Java, wo volatile tut Lese-/Schreib-Atomizität und Thread-Synchronisationsgarantien haben (aber still keine Atomitätsgarantien für Inkrement/Dekrement-Operationen usw.). volatile in C/C++ ist ein Schlüsselwort zum Deaktivieren der Optimierung, mehr nicht.

    – Hyde

    3. April 2016 um 6:23 Uhr

  • @hyde, mir ist klar, dass mein Titel die Leute dazu bringen könnte, das zu denken Ich glaube das volatile garantiert Atomarität, aber ich weiß, dass dies nicht der Fall ist. ich benutze volatile einfach, um Optimierungen zu verhindern, die den Compiler sonst glauben machen würden, er kenne den Variablenzustand bereits, ohne ihn erneut aus dem Register zu lesen, wenn er zwischenzeitlich in einem Interrupt geändert wurde.

    – Gabriel Staples

    3. April 2016 um 13:30 Uhr


Das ATmega328 Datenblatt weist darauf hin, dass:

Die ALU unterstützt arithmetische und logische Operationen zwischen Registern oder zwischen einer Konstante und einem Register

Es wird nicht erwähnt, dass die ALU direkt auf Speicherstellen arbeiten kann. Um einen Wert zu dekrementieren, bedeutet dies, dass der Prozessor mehrere Operationen ausführen muss:

  • Lade den Wert in ein Register
  • dekrementiere das Register
  • speichern Sie den Wert zurück

Daher ist die Dekrementoperation nicht atomar, es sei denn, Sie tun etwas Besonderes, um sie atomar zu machen, wie z. B. das Deaktivieren von Interrupts. Diese Art von Lese-/Änderungs-/Schreibanforderungen ist wahrscheinlich häufiger als nicht zum Aktualisieren des Speichers.

Die Details, wie eine Operation atomar gemacht werden kann, sind plattformabhängig. Neuere Versionen der C- und C++-Standards unterstützen ausdrücklich atomare Operationen; Ich habe keine Ahnung, ob eine Toolchain für den ATmega diese neueren Standards unterstützt.

  • Danke für die Verständnishilfe warum Das Inkrementieren/Dekrementieren eines einzelnen Bytes ist keine atomare Operation. An alle Leser, die es jetzt wissen wollen wie Um den atomaren Zugriff auf Variablen auf einem Atmel AVR-Mikrocontroller zu erzwingen, habe ich am Ende meiner Antwort drei Möglichkeiten hinzugefügt, da ich das herausgefunden habe wie man Information ist die Antwort auf die erste logische Frage, die auf meine folgt warum Frage.

    – Gabriel Staples

    11. September 2016 um 21:32 Uhr

  • Michael, leider könnte ich wieder deine Hilfe gebrauchen: stackoverflow.com/questions/52784613/…

    – Gabriel Staples

    12. Oktober 2018 um 18:42 Uhr

Benutzer-Avatar
Ismael

Ich weiß nicht viel über Arduino und Interrupts, daher beantworte ich Ihre spezielle Frage hier möglicherweise nicht, sondern dekrementiere und inkrementiere in einer Multithread-Umgebung -- und ++ ist niemals atomar. Darüber hinaus, volatile bedeutet auch nicht atomic in C++ allgemein (nachweisen). Obwohl ich das weiß volatile ist sinnvoll, wenn Sie Mikrocontroller programmieren, daher vermute ich, dass meine Antwort möglicherweise nicht auf Ihren Fall zutrifft.

Funktioniert es, wenn Sie ein Array von ersetzen volatile uint8_ts mit drei separaten volatile uint8_ts?

  • das beantwortet es: behandeln Sie dies hier wie eine Multithread-Umgebung. Inkrementieren und Dekrementieren ist niemals atomar. das wusste ich nicht. Auf einem Arduino ist int 2 Bytes groß, also nein, keine Operation an einer 2- oder mehr-Byte-Variablen in Arduino ist atomar, und das ist richtig, volatile macht es nicht atomar … das ist es, was noInterrupts() und interrupts( ) sind für … um Atomarität zu erzwingen.

    – Gabriel Staples

    3. April 2016 um 5:35 Uhr


  • Haben Sie Quellen, die darüber sprechen -- und ++ sind in C++ im Allgemeinen keine atomaren Operationen?

    – Gabriel Staples

    3. April 2016 um 5:39 Uhr

  • ich meinte uint8_ts, nicht ints. Schnelles Googeln hat keine zuverlässigen Quellen gefunden, daher habe ich keine Links zur Hand, aber im Allgemeinen sollten Sie beide verwenden, wenn Sie atomare Inkremente wünschen std::atomicverwenden Sie Sperren oder verwenden Sie entsprechende plattformabhängige Funktionen wie InterlockedIncrement unter Windows.

    – Ismael

    3. April 2016 um 5:43 Uhr


  • @GabrielStaples Der Grund, warum es nicht atomar ist, liegt darin, dass der für ein Inkrement generierte Assemblercode letztendlich aus mehreren Anweisungen besteht. Das Ausführen mehrerer Anweisungen ohne Sperrmechanismus ist von Natur aus nicht Thread-sicher.

    – OpenUserX03

    3. April 2016 um 5:50 Uhr

  • Danke für die Verständnishilfe warum Das Inkrementieren/Dekrementieren eines einzelnen Bytes ist keine atomare Operation. An alle Leser, die es jetzt wissen wollen wie Um den atomaren Zugriff auf Variablen auf einem Atmel AVR-Mikrocontroller zu erzwingen, habe ich am Ende meiner Antwort drei Möglichkeiten hinzugefügt, da ich das herausgefunden habe wie man Information ist die Antwort auf die erste logische Frage, die auf meine folgt warum Frage.

    – Gabriel Staples

    11. September 2016 um 21:32 Uhr

Benutzer-Avatar
Gabriel Staples

Ok, die Antwort auf “Warum ist das Inkrementieren / Dekrementieren einer Einzelbyte-Variablen NICHT atomar?” wird hier sehr gut von Ishamael hier und Michael Burr hier beantwortet.

Jetzt wo ich meine Antwort darauf bekommen habe -- dekrementieren und ++ Inkrementoperationen sind niemals atomar, selbst wenn sie für Bytewerte ausgeführt werden (siehe Antworten oben und Nick Gammons Link hier), möchte ich die Folgefrage von sicherstellen wie erzwinge ich Atomizität auf Atmel AVR-Mikrocontrollern wird ebenfalls beantwortet, sodass diese Frage zu einer Art Ressource wird:

Hier sind alle Techniken, die mir bekannt sind, um Atomizität in Atmel AVR-Mikrocontrollern wie Arduino zu erzwingen:

1) Option 1 (die bevorzugte Methode):

uint8_t SREG_bak = SREG; //save global interrupt state
noInterrupts(); //disable interrupts (for Arduino only; this is an alias of AVR's "cli()")
//atomic variable-access code here
SREG = SREG_bak; //restore interrupt state

2) Option 2 (die weniger sichere, nicht empfohlene Methode, da sie dazu führen kann, dass Sie versehentlich verschachtelte Interrupts aktivieren, wenn Sie diesen Ansatz versehentlich in einem Codeblock oder einer Bibliothek verwenden, die innerhalb einer ISR aufgerufen wird):

noInterrupts(); //disable interrupts (Arduino only; is an alias to AVR's "cli()" call)
//atomic variable-access code here
interrupts(); //enable interrupts (Arduino only; is an alias to AVR's "sei()" call)

Alternativmöglichkeit 2:

cli(); //clear (disable) interrupts flag; noInterrupts() is simply a macro for this
//atomic variable-access code here
sei(); //set (enable) interrupts flag; interrupts() is simply a macro for this

3) Option 3 (im Wesentlichen die gleiche wie Option 1; stattdessen wird nur ein Makro verwendet, das in einer avr-libc-Bibliothek gespeichert ist, und natürlich mit variablem Gültigkeitsbereich, der innerhalb der geschweiften Klammern angewendet wird)

Quelle: http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

#include <util/atomic.h> //(place at top of code)
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
  //atomic access code here
}

Verwandt:

  1. [My Q&A] Welche Variablentypen/-größen sind auf STM32-Mikrocontrollern atomar?
  2. https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/
  3. ***** [My answer] Welche Arduinos unterstützen ATOMIC_BLOCK? [and how can I duplicate this concept in C with __attribute__((__cleanup__(func_to_call_when_x_exits_scope))) and in C++ with class constructors and destructors?]
  4. Wie das geht, erfahren Sie in STM32-Mikrocontroller siehe stattdessen meine Antwort hier: Was sind die verschiedenen Möglichkeiten zum Deaktivieren und erneuten Aktivieren von Interrupts in STM32-Mikrocontrollern, um atomare Zugriffsschutzmaßnahmen zu implementieren?

1012510cookie-checkDas Dekrementieren eines Elements eines (flüchtigen) Einzelbyte-Arrays in C++ ist nicht atomar! WIESO DEN? (Auch: Wie erzwinge ich Atomarität in Atmel AVR mcus/Arduino)

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

Privacy policy