Przesłanka: Pracuję w środowisku ARM (prawie bez systemu), w którym nawet nie mam C ++ 11 (z std::atomic<int>
) dostępnym, więc proszę unikać odpowiedzi typu „ po prostu użyj standardowego C ++std::atomic<int>
”: nie mogę .
Czy ta implementacja ARM AtomicInt jest poprawna? (załóżmy, że architektura ARM to ARMv7-A )
Czy widzisz jakiś problem z synchronizacją? Czy jest to volatile
wymagane / przydatne?
// 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)
}
Staram się również ponownie wykorzystać kod, dlatego wyizolowałem tylko jedną podstawową funkcję do zaimplementowania w kodzie specyficznym dla platformy ( add()
metoda wewnątrz arm/atomic_int.cpp
).
Czy atomic_int.h
naprawdę jest przenośny, ponieważ znajduje się na różnych platformach / architekturach / kompilatorach? Czy to podejście jest wykonalne ? (Z wykonalnym mam na myśli wykonalność dla każdej platformy, aby zagwarantować atomowość poprzez wdrożenie tylko tej add()
metody ).
tutaj jest odpowiednia implementacja ARM GCC 8.3.1 tej samej funkcji. Najwyraźniej jedyną prawdziwą różnicą jest obecność dmb
przed i po. Czy są naprawdę potrzebne w moim przypadku? Dlaczego? Czy masz przykład, w którym moje AtomicInt
(bez dmb
) zawodzi?
AKTUALIZACJA: poprawiono implementację, usunięto get()
metodę rozwiązywania problemów z atomicznością i wyrównaniem. Teraz add()
zachowuje się jak standard fetchAndAdd()
.
volatile
słowo kluczowe w C ++ oznacza, że nie optymalizuj poprzez zmienną. Więcget()
metoda korzyści z niego. Chociaż, ogólnie, lotność ma wkrótce deprycates w C ++. Jeśli Twój system nie może wbudować synchronizacji danych 32-bitowych, nie masz innego wyboru, jak korzystać z muteksów - przynajmniej spinlocka.__ATOMIC_INT_H_
) i nazwy rozpoczynające się znakiem podkreślenia, po którym następuje wielka litera, są zarezerwowane do użycia przez implementację. Nie używaj ich w swoim kodzie.atomic
Prawdopodobnie najlepiej nie używać nazwy członka, aby uniknąć pomyłekstd::atomic
, choć rodzi to pytanie, dlaczego w ogóle nie używałbyś tego.__ATOMIC_INT_H_
identyfikatora.Odpowiedzi:
Jeśli używasz
gcc
, możesz użyć starszych__sync
wbudowanych funkcji dostępu do pamięci atomowej :Generuje :
źródło
gcc
, aw każdym razie nie chcę wiązać implementacji z żadnym konkretnym kompilatorem. W każdym razie dziękuję za podpowiedź, przynajmniej mówi mi, że mojaadd()
część ARM powinna być poprawna. Jaka jest różnica międzyldxr
ildrex
?dmb
przed i po pętlildrex
/strex
.