Welche Variablentypen/-größen sind auf STM32-Mikrocontrollern atomar?

Lesezeit: 4 Minuten

Benutzer-Avatar
Gabriel Staples

Hier sind die Datentypen auf STM32-Mikrocontrollern: http://www.keil.com/support/man/docs/armcc/armcc_chr1359125009502.htm.

Diese Mikrocontroller verwenden 32-Bit-ARM-Core-Prozessoren.

Welche Datentypen haben automatischen atomaren Lese- und atomaren Schreibzugriff?

Ich bin mir ziemlich sicher, dass alle 32-Bit-Datentypen dies tun (da der Prozessor 32-Bit ist) und alle 64-Bit-Datentypen NICHT (da mindestens 2 Prozessoroperationen erforderlich wären, um ein 64-Bit-Wort zu lesen oder zu schreiben ), aber was ist mit bool (1 Byte) und uint16_t/int16_t (2 Byte)?

Kontext: Ich teile Variablen zwischen mehreren Threads (einzelner Kern, aber mehrere Threads oder “Aufgaben”, wie sie genannt werden, in FreeRTOS) auf dem STM32 und muss wissen, ob ich den atomaren Zugriff erzwingen muss, indem ich Interrupts ausschalte, Mutexe verwende usw.

AKTUALISIEREN:

Bezugnehmend auf diesen Beispielcode:

volatile bool shared_bool;
volatile uint8_t shared u8;
volatile uint16_t shared_u16;
volatile uint32_t shared_u32;
volatile uint64_t shared_u64;
volatile float shared_f; // 32-bits
volatile double shared_d; // 64-bits

// Task (thread) 1
while (true)
{
    // Write to the values in this thread.
    //
    // What I write to each variable will vary. Since other threads are reading
    // these values, I need to ensure my *writes* are atomic, or else I must
    // use a mutex to prevent another thread from reading a variable in the
    // middle of this thread's writing.
    shared_bool = true;
    shared_u8 = 129;
    shared_u16 = 10108;
    shared_u32 = 130890;
    shared_f = 1083.108;
    shared_d = 382.10830;
}

// Task (thread) 2
while (true)
{
    // Read from the values in this thread.
    //
    // What thread 1 writes into these values can change at any time, so I need
    // to ensure my *reads* are atomic, or else I'll need to use a mutex to
    // prevent the other thread from writing to a variable in the midst of
    // reading it in this thread.
    if (shared_bool == whatever)
    {
        // do something
    }
    if (shared_u8 == whatever)
    {
        // do something
    }
    if (shared_u16 == whatever)
    {
        // do something
    }
    if (shared_u32 == whatever)
    {
        // do something
    }
    if (shared_u64 == whatever)
    {
        // do something
    }
    if (shared_f == whatever)
    {
        // do something
    }
    if (shared_d == whatever)
    {
        // do something
    }
}

Für welche Variablen kann ich das im obigen Code tun, ohne einen Mutex zu verwenden? Mein Verdacht ist folgender:

  1. volatile bool: sicher – kein Mutex erforderlich
  2. volatile uint8_t: sicher – kein Mutex erforderlich
  3. volatile uint16_t: sicher – kein Mutex erforderlich
  4. volatile uint32_t: sicher – kein Mutex erforderlich
  5. volatile uint64_t: UNSICHER – SIE MÜSSEN EINE kritische Sektion oder MUTEX VERWENDEN!
  6. volatile float: sicher – kein Mutex erforderlich
  7. volatile double: UNSICHER – SIE MÜSSEN EINE kritische Sektion oder MUTEX VERWENDEN!

Beispiel kritischer Abschnitt mit FreeRTOS:

Verwandt, beantwortet aber nicht meine Frage:

  1. Atomare Operationen in ARM
  2. ARM: Ist das Schreiben/Lesen von int atomar?
  3. (Meine eigene Frage und Antwort zur Atomizität in 8-Bit-AVR [and Arduino] Mikrocontroller): https://stackoverflow.com/a/39693278/4561887
  4. https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/

  • Dafür ist das ARM-Befehlssatzhandbuch für Ihren speziellen Chip gedacht?

    – Benutzer268396

    12. Oktober 2018 um 18:01 Uhr

  • Mögliches Duplikat von ARM: Ist das Schreiben/Lesen von int atomar?

    – zneak

    12. Oktober 2018 um 18:28 Uhr

  • Sie müssen sich den Assemblercode ansehen.

    – Fiddle Bits

    12. Oktober 2018 um 18:35 Uhr

  • Versuchen Sie sich dagegen zu wehren, dass zwei Kerne mit denselben Daten arbeiten oder mitten in einem Schreibvorgang unterbrochen werden, um dem anderen Thread auf demselben Kern nachzugeben?

    – zneak

    12. Oktober 2018 um 18:39 Uhr

  • Letzteres: “Während eines Schreibvorgangs unterbrochen, um dem anderen Thread auf demselben Kern nachzugeben”.

    – Gabriel Staples

    12. Oktober 2018 um 18:44 Uhr

Benutzer-Avatar
Gabriel Staples

Um die endgültige, endgültige Antwort auf diese Frage zu erhalten, springen Sie direkt zum Abschnitt unten mit dem Titel “Abschließende Antwort auf meine Frage“.

UPDATE 30. Okt. 2018: Ich habe versehentlich auf die (leicht) falschen Dokumente verwiesen (die aber genau dasselbe sagten), also habe ich sie in meiner Antwort hier korrigiert. Einzelheiten finden Sie unter „Hinweise zu den Änderungen vom 30. Oktober 2018“ am Ende dieser Antwort.

Ich verstehe hier sicher nicht jedes Wort, aber die Referenzhandbuch für die ARM v7-M-Architektur (Online-Quelle; PDF-Datei direkt herunterladen) (NICHT das Technische Referenzhandbuch [TRM]da es nicht um Atomizität geht) bestätigt meine Annahmen:

Geben Sie hier die Bildbeschreibung ein

Also … ich denke, meine 7 Annahmen am Ende meiner Frage sind alle richtig. [30 Oct. 2018: Yes, that is correct. See below for details.]


UPDATE 29. Okt. 2018:

Noch eine Kleinigkeit:

Richard Barry, Gründer, Experte und Kernentwickler von FreeRTOS, erklärt in tasks.c

/* Ein kritischer Abschnitt ist nicht erforderlich, da die Variablen vom Typ BaseType_t sind. */

… beim Lesen einer “unsigned long” (4-Byte) flüchtigen Variable auf STM32. Das bedeutet, dass er zumindest zu 100 % sicher ist, dass 4-Byte-Lese- und -Schreibvorgänge auf STM32 atomar sind. Er erwähnt keine kleineren Byte-Lesevorgänge, aber für 4-Byte-Lesevorgänge ist er sich absolut sicher. Ich muss davon ausgehen, dass 4-Byte-Variablen die native Prozessorbreite sind, und außerdem Wort ausgerichtetist entscheidend dafür, dass dies wahr ist.

Aus tasks.cZeilen 2173-2178 in FreeRTOS v9.0.0, zum Beispiel:

UBaseType_t uxTaskGetNumberOfTasks( void )
{
    /* A critical section is not required because the variables are of type
    BaseType_t. */
    return uxCurrentNumberOfTasks;
}

Er verwendet genau diesen Ausdruck von …

/* Ein kritischer Abschnitt ist nicht erforderlich, da die Variablen vom Typ BaseType_t sind. */

…an zwei verschiedenen Stellen in dieser Datei.

Endgültige Antwort auf meine Frage: alle Typen <= 4 Bytes (all fett Typen in der Liste der 9 Zeilen unten) sind atomar.

Darüber hinaus möchte ich bei näherer Betrachtung des TRM auf p141, wie in meinem Screenshot oben gezeigt, die wichtigsten Sätze hervorheben, die ich hervorheben möchte:

In ARMv7-M ist die Einzelkopie atomar Prozessorzugriffe sind:
• alle Byte Zugriffe.
• alle Halbwort Zugriffe auf Halbwort-ausgerichtete Stellen.
• alle Wort Zugriffe auf wortausgerichtete Orte.

Und, über diesen Linkgilt Folgendes für “grundlegende Datentypen, die in ARM C und C++ implementiert sind” (dh: auf STM32):

  1. bool/_Bool ist “byte-aligned” (1-byte-aligned)
  2. int8_t/uint8_t ist “byte-aligned” (1-byte-aligned)
  3. int16_t/uint16_t ist “halfword-aligned” (2-byte-aligned)
  4. int32_t/uint32_t ist “word-aligned” (4-byte-aligned)
  5. int64_t/uint64_t ist “doubleword-aligned” (8-byte-aligned) <-- NICHT GARANTIERT ATOMIC
  6. float ist “word-aligned” (4-byte-aligned)
  7. double ist “doubleword-aligned” (8-byte-aligned) <-- NICHT GARANTIERT ATOMIC
  8. long double ist “doubleword-aligned” (8-byte-aligned) <-- NICHT GARANTIERT ATOMIC
  9. alle Zeiger sind “word-aligned” (4-byte-aligned)

Das bedeutet, dass ich jetzt die Beweise habe und verstehe, die ich brauche abschließend feststellen, dass alle fettgedruckten Zeilen direkt darüber automatischen atomaren Lese- und Schreibzugriff haben (aber natürlich NICHT inkrementieren / dekrementieren, was mehrere Operationen sind). Dies ist die endgültige Antwort auf meine Frage. Die einzige Ausnahme von dieser Atomarität könnte meiner Meinung nach in gepackten Strukturen liegen, in diesem Fall sind diese ansonsten natürlich ausgerichteten Datentypen möglicherweise nicht natürlich ausgerichtet.

Beachten Sie auch, dass beim Lesen des Technischen Referenzhandbuchs „Einzelkopie-Atomizität“ anscheinend nur „Single-Core-CPU-Atomizität“ oder „Atomizität auf einer Single-CPU-Core-Architektur“ bedeutet. Dies steht im Gegensatz zu “Multi-Copy-Atomicity”, das sich auf ein “Multiprocessing-System” oder eine Multi-Core-CPU-Architektur bezieht. Wikipedia sagt: „Multiprocessing ist die Verwendung von zwei oder mehr Zentraleinheiten (CPUs) innerhalb eines einzigen Computersystems“ (https://en.wikipedia.org/wiki/Multiprocessing).

Meine fragliche Architektur, STM32F767ZI (mit ARM Cortex-M7-Kern), ist eine Single-Core-Architektur, also gilt anscheinend “Single-Copy-Atomicity”, wie ich oben aus dem TRM zitiert habe.

Weiterführende Literatur:

  • ARM: Ist das Schreiben/Lesen von int atomar?
  • Was ist der Unterschied zwischen atomar / flüchtig / synchronisiert?
  • Können Variablen in gepackten Strukturen atomar gelesen werden?

Hinweise zu den Änderungen vom 30. Okt. 2018:

Zu schaffen Atomic Access Guards (normalerweise von Unterbrechungen ausschalten wenn Lese- und Schreibvorgänge sind nicht atomar) siehe:

  1. [my Q&A] Welche Möglichkeiten gibt es, Interrupts in STM32-Mikrocontrollern zu deaktivieren und wieder zu aktivieren, um atomare Zugriffsschutzmechanismen zu implementieren?
  2. Mein doAtomicRead() func hier, die atomare Lesevorgänge ausführen kann, ohne Interrupts auszuschalten

  • In einer Single-Core-Umgebung kann die Ausführung nicht mitten in einer Anweisung unterbrochen werden, daher ist jedes C-Konstrukt, das sich zu einer Anweisung zusammensetzt, atomar. 32-Bit-ARM verfügt nicht über einzelne Anweisungen, die mehr als 32 Bit Speicher gleichzeitig manipulieren können, sodass eine offensichtliche Obergrenze für das festgelegt ist, was atomar sein kann: Insbesondere 64-Bit-Manipulationen können dies nicht.

    – zneak

    12. Oktober 2018 um 19:37 Uhr

  • Es gibt jedoch immer noch C-Operationen, die zu mehr als einer Anweisung kompiliert werden, selbst wenn sie 32 Bit oder weniger manipulieren (wie z a += 1 mit int a), und Sie müssen mit diesen vorsichtig sein. Ein weniger offensichtliches Beispiel ist, wenn Sie eine Struktur mit nicht ausgerichteten Feldern verwenden: Ihr Compiler muss mindestens zwei Ladevorgänge und zwei Speicher generieren, um sie lesen/schreiben zu können. Es wäre auch möglich, dass das Kopieren einer Struktur, die in 32 Bit passt, bei einigen Optimierungsstufen mehr als eine Anweisung verwenden könnte. Bei numerischen Variablen ist beides jedoch normalerweise kein Problem.

    – zneak

    12. Oktober 2018 um 19:39 Uhr


  • @zneak falsch. Einige der Anweisungen können zum Beispiel durch Division unterbrochen werden.

    – 0___________

    13. Oktober 2018 um 14:28 Uhr


  • @P__J__, bring mir etwas bei und zeig mir eine Architektur, die das tut.

    – zneak

    13. Oktober 2018 um 14:36 ​​Uhr

  • @P__J__, was genau passiert, wenn eine Teilung unterbrochen wird? Erhalten Sie einen beschädigten Zustand oder wird der Zustand so zurückgesetzt, dass eine Unterbrechung in der Mitte völlig ununterscheidbar von einer Unterbrechung kurz davor ist?

    – zneak

    13. Oktober 2018 um 17:55 Uhr

Benutzer-Avatar
0___________

Je nachdem, was Sie mit atomar meinen.

Wenn es nicht um den einfachen Lade- oder Speichervorgang geht

a += 1;

dann sind alle Typen nicht atomar.

Wenn es sich um eine einfache Speicher- oder Ladeoperation handelt, sind 32-Bit-, 16-Bit- und 8-Bit-Datentypen atomar. Wenn der Wert im Register normalisiert werden muss, sind 8- und 16-Bit-Speicherung und -Ladung möglicherweise nicht atomar.

Wenn Ihre Hardware Bitbanding unterstützt, dann sind bei Verwendung von Bitbanding die Bitoperationen (Setzen und Zurücksetzen) in den Speicherbereichen, die Bitbanding unterstützen, atomar

Notiz.

Wenn Ihr Code keine nicht ausgerichteten Operationen zulässt, sind 8- und 16-Bit-Operationen möglicherweise nicht atomar.

  • Danke für deine Antwort. Bitte sehen Sie sich meine aktualisierte Frage an und prüfen Sie, ob Sie meinen Verdacht expliziter bestätigen können.

    – Gabriel Staples

    12. Oktober 2018 um 18:24 Uhr

  • Übrigens, a += 1 besteht aus zwei Operationen und ist nicht atomar.

    – zneak

    12. Oktober 2018 um 18:28 Uhr


  • Einverstanden. Ich habe dies vor ein paar Jahren auf einem 8-Bit-AVR-Prozessor auf die harte Tour gelernt, indem ich eine ansonsten atomare Lese-Schreib-fähige 8-Bit-Variable inkrementierte (keine atomare Operation).

    – Gabriel Staples

    12. Oktober 2018 um 18:29 Uhr


  • @zneak nein, nur wenn die Operation RMW ist, sonst ist sie atomar. Es ist möglicherweise nicht kohärent (Cache), sondern atomar.

    – 0___________

    12. Oktober 2018 um 18:30 Uhr


  • Wenn diese Operation atomar wäre, würden mehrere Kerne, die sie gleichzeitig versuchen, erfolgreich sein. Das ist nicht der Fall, wenn Sie dies mit zwei Kernen in einer Schleife tun, werden Sie sicher einige Inkremente verlieren.

    – zneak

    12. Oktober 2018 um 18:36 Uhr

Atomare “Arithmetik” kann von CPU-Core-Registern verarbeitet werden!

Es können beliebige Typen sein ein oder vier Bytes hängen von der Architektur und dem Befehlssatz ab

ABER Änderung von irgendwelchen Variable befindet sich im Speicher Führen Sie mindestens 3 Systemschritte durch: RMW = Lesespeicher zum Register, Modifizierregister und Schreibregister zum Speicher.

Daher ist eine atomare Modifikation nur möglich, wenn Sie die Verwendung von CPU-Registern steuern. Dies bedeutet, dass Sie reinen Assembler verwenden müssen und keinen C- oder Cpp-Compiler verwenden.

Wenn Sie den C\Cpp-Compiler verwenden, wurden globale oder globale statische Variablen so im Speicher platziert C\Cpp bietet keine atomaren Aktionen und Typen

Hinweis: Sie können zum Beispiel “FPU-Register” für atomare Modifikationen verwenden (wenn Sie es wirklich brauchen), aber Sie müssen vor dem Compiler und RTOS verbergen, dass die Architektur FPU hat.

1158400cookie-checkWelche Variablentypen/-größen sind auf STM32-Mikrocontrollern atomar?

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

Privacy policy