Premissa: estou trabalhando com um ambiente ARM incorporado (quase bare-metal) em que nem tenho C ++ 11 (com std::atomic<int>
) disponível; portanto, evite respostas como " basta usar C ++ padrãostd::atomic<int>
": não posso .
Esta implementação ARM do AtomicInt está correta? (suponha que a arquitetura do ARM seja ARMv7-A )
Você vê algum problema de sincronização? É volatile
necessário / útil?
// 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)
}
Além disso, estou tentando obter uma reutilização de código, por isso isolei apenas uma função básica para implementar no código específico da plataforma ( add()
método interno arm/atomic_int.cpp
).
É atomic_int.h
realmente portátil como em diferentes plataformas / arquiteturas / compiladores? Essa abordagem é viável ? (Com viável, quero dizer viável para todas as plataformas garantir a atomicidade implementando apenas o add()
método ).
aqui está a implementação correspondente do ARM GCC 8.3.1 da mesma função. Aparentemente, a única diferença real é a presença de dmb
antes e depois. Eles são realmente necessários no meu caso? Por quê? Você tem um exemplo em que meu AtomicInt
(sem dmb
) falha?
UPDATE: implementação fixa, get()
método removido para resolver problemas de atomicidade e alinhamento. Agora o add()
comportamento se comporta como um padrão fetchAndAdd()
.
volatile
A palavra-chave em C ++ significa não otimizar via variável. Portanto, oget()
método se beneficia dele. Embora, em geral, o volátil esteja prestes a desaprovar em C ++. Se o seu sistema não puder sincronizar dados de 32 bits embutidos, você terá pouca opção a não ser usar mutexes - no mínimo, spinlock.__ATOMIC_INT_H_
) e nomes que começam com um sublinhado seguido por uma letra maiúscula são reservados para uso pela implementação. Não os use no seu código.atomic
provavelmente não deve ser usado para evitar confusõesstd::atomic
, embora implique a pergunta por que você não usaria isso em nenhum caso.__ATOMIC_INT_H_
identificador renomeado .Respostas:
Se você usar,
gcc
pode usar as Funções incorporadas herdadas__sync
para acesso à memória atômica :Gera :
fonte
gcc
e, de qualquer forma, não quero vincular a implementação a nenhum compilador específico. De qualquer forma, obrigado pela sua dica, pelo menos me diz que minhaadd()
parte do BRAÇO deve estar correta. Qual é a diferença entreldxr
eldrex
?dmb
antes e depois do loopldrex
/strex
.