Ist diese C ++ AtomicInt-Implementierung korrekt?

9

Voraussetzung: Ich arbeite mit einer in ARM eingebetteten (fast Bare-Metal-) Umgebung, in der nicht einmal C ++ 11 (mit std::atomic<int>) verfügbar ist. Vermeiden Sie daher Antworten wie " Verwendenstd::atomic<int> Sie einfach Standard-C ++ ": Ich kann nicht .

Ist diese ARM- Implementierung von AtomicInt korrekt? (Angenommen, die ARM-Architektur ist ARMv7-A )

Sehen Sie ein Synchronisationsproblem? Ist es volatileerforderlich / nützlich?

// File: atomic_int.h

#ifndef ATOMIC_INT_H_
#define ATOMIC_INT_H_

#include <stdint.h>

class AtomicInt
{
public:
    AtomicInt(int32_t init = 0) : atom(init) { }
    ~AtomicInt() {}

    int32_t add(int32_t value); // Implement 'add' method in platform-specific file

    int32_t sub(int32_t value) { return add(-value); }
    int32_t inc(void)          { return add(1);      }
    int32_t dec(void)          { return add(-1);     }

private:
    volatile int32_t atom;
};

#endif
// File: arm/atomic_int.cpp

#include "atomic_int.h"

int32_t AtomicInt::add(int32_t value)
{
    int32_t res, prev, tmp;

    asm volatile(

    "try:    ldrex   %1, [%3]\n"     // prev = atom;
    "        add     %0, %1, %4\n"   // res = prev + value;
    "        strex   %2, %0, [%3]\n" // tmp = outcome(atom = res); // may fail
    "        teq     %2, #0\n"       // if (tmp)
    "        bne     try"            //     goto try; /* add failed: someone else modified atom -> retry */

    : "=&r" (res), "=&r" (prev), "=&r" (tmp), "+mo" (atom)  // output (atom is both in-out)
    : "r" (value)                                           // input
    : "cc");                                                // clobbers (condition code register [CPSR] changed)

    return prev; // safe return (local variable cannot be changed by other execution contexts)
}

Außerdem versuche ich, eine gewisse Wiederverwendung von Code zu erreichen. Deshalb habe ich nur eine Grundfunktion isoliert, die in plattformspezifischem Code implementiert werden soll ( add()Methode im Inneren arm/atomic_int.cpp).

Ist es atomic_int.hwirklich portabel, da es über verschiedene Plattformen / Architekturen / Compiler hinweg ist? Ist dieser Ansatz machbar ? (Mit machbar meine ich machbar für jede Plattform, um Atomizität zu gewährleisten, indem nur die add()Methode implementiert wird ).

Hier ist die entsprechende ARM GCC 8.3.1-Implementierung derselben Funktion. Anscheinend ist der einzige wirkliche Unterschied das Vorhandensein von dmbvorher und nachher. Werden sie in meinem Fall wirklich gebraucht? Warum? Haben Sie ein Beispiel, bei dem mein AtomicInt(ohne dmb) fehlschlägt?

UPDATE: Feste Implementierung, entfernte get()Methode zur Lösung von Atomizitäts- und Ausrichtungsproblemen. Jetzt add()verhält sich das wie ein Standard fetchAndAdd().

Gentooise
quelle
volatileSchlüsselwort in C ++ bedeutet, nicht über Variable zu optimieren. Die get()Methode profitiert also davon. Im Allgemeinen ist flüchtig, in C ++ zu entziehen. Wenn Ihr System 32-Bit-Daten nicht synchronisieren kann, haben Sie keine andere Wahl, als Mutexe zu verwenden - zumindest Spinlock.
ALX23z
Welche Version der Armarchitektur verwenden Sie? Armv-7?
Mike van Dyke
1
Damit wird die Frage nicht beantwortet, aber Namen, die zwei aufeinanderfolgende Unterstriche ( __ATOMIC_INT_H_) enthalten, und Namen, die mit einem Unterstrich gefolgt von einem Großbuchstaben beginnen, sind für die Verwendung durch die Implementierung reserviert. Verwenden Sie sie nicht in Ihrem Code.
Pete Becker
Der Mitgliedsname atomicwird wahrscheinlich am besten nicht verwendet, um Verwechslungen mit zu vermeiden std::atomic, obwohl sich die Frage stellt, warum Sie das auf keinen Fall einfach verwenden würden.
Clifford
ARM-Architektur hinzugefügt, __ATOMIC_INT_H_Bezeichner umbenannt .
Gentooise

Antworten:

2

Wenn Sie gccmöglicherweise verwenden, können Sie die integrierten Legacy __sync-Funktionen für den atomaren Speicherzugriff verwenden :

void add(int volatile& a, int value) {
    __sync_fetch_and_add(&a, value);
}

Erzeugt :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret
Maxim Egorushkin
quelle
Leider verwende ich nicht gccund möchte die Implementierung auf keinen Fall an einen bestimmten Compiler binden. Trotzdem vielen Dank für Ihren Hinweis, zumindest sagt er mir, dass mein ARM- add()Teil korrekt sein sollte. Was ist der Unterschied zwischen ldxrund ldrex?
Gentooise
Dies ist ARM8 (zB 64-Bit) und keine der 32-Bit-Versionen.
Marko
Ich habe es geschafft, den entsprechenden Code durch Angabe der Zielarchitektur zu erhalten: link . Es scheint, dass GCC tatsächlich dmbvor und nach der ldrex/ strex-Schleife setzt.
Gentooise
2
Ich denke , das der gute Ansatz, aber es Compiler unabhängig nur gehen zu machen godbolt.org/z/WB8rxw Typ in der Funktion , die Sie gcc builtins verwenden möchten , und kopieren Sie die entsprechende Baugruppe ausgegeben. Stellen Sie sicher, dass der Parameter -march mit der spezifischen Version von ARM übereinstimmt.