Prémisse: je travaille avec un environnement ARM embarqué (presque bare-metal) où je n'ai même pas C ++ 11 (avec std::atomic<int>
) disponible, donc veuillez éviter les réponses comme " utilisez simplement le C ++ standardstd::atomic<int>
": je ne peux pas .
Cette implémentation ARM d'AtomicInt est-elle correcte? (supposons que l'architecture ARM est ARMv7-A )
Voyez-vous un problème de synchronisation? Est-ce volatile
nécessaire / utile?
// 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)
}
En outre, j'essaie de réutiliser du code, c'est pourquoi j'ai isolé une seule fonction de base à implémenter dans du code spécifique à la plate-forme ( add()
méthode à l'intérieur arm/atomic_int.cpp
).
Est-il atomic_int.h
vraiment portable comme sur différentes plates-formes / architectures / compilateurs? Cette approche est-elle réalisable ? (Avec faisable, je veux dire faisable pour chaque plate-forme pour garantir l'atomicité en mettant en œuvre uniquement la add()
méthode ).
voici l'implémentation ARM GCC 8.3.1 correspondante de la même fonction. Apparemment, la seule vraie différence est la présence d' dmb
avant et d'après. Sont-ils vraiment nécessaires dans mon cas? Pourquoi? Avez-vous un exemple où mon AtomicInt
(sans dmb
) échoue?
MISE À JOUR: implémentation fixe, get()
méthode supprimée pour résoudre les problèmes d'atomicité et d'alignement. Maintenant, il add()
se comporte comme un standard fetchAndAdd()
.
volatile
mot-clé en C ++ signifie ne pas optimiser via une variable. Laget()
méthode en profite donc. Bien que, en général, volatile soit sur le point de se déprécier en C ++. Si votre système ne peut pas synchroniser les données 32 bits intégrées, vous n'avez pas d'autre choix que d'utiliser des mutex - spinlock à tout le moins.__ATOMIC_INT_H_
) et les noms qui commencent par un trait de soulignement suivi d'une lettre majuscule sont réservés à l'utilisation par l'implémentation. Ne les utilisez pas dans votre code.atomic
est préférable de ne pas utiliser le nom de membre pour éviter toute confusionstd::atomic
, même si cela soulève la question de savoir pourquoi vous ne l'utiliseriez pas de toute façon.__ATOMIC_INT_H_
identifiant renommé .Réponses:
Si vous utilisez
gcc
peut - être vous pouvez utiliser héritage__sync
Fonctions intégrées pour l' accès à la mémoire atomique :Génère :
la source
gcc
, et en tout cas je ne veux pas lier l'implémentation à un compilateur spécifique. Merci quand même pour votre conseil, au moins ça me dit que maadd()
partie ARM devrait être correcte. Quelle est la différence entreldxr
etldrex
?dmb
avant et après la boucleldrex
/strex
.