Premisa: estoy trabajando con un entorno ARM integrado (casi desnudo) donde ni siquiera tengo C ++ 11 (con std::atomic<int>
) disponible, así que evite respuestas como " solo use C ++ estándarstd::atomic<int>
": no puedo .
¿Es correcta esta implementación ARM de AtomicInt? (suponga que la arquitectura ARM es ARMv7-A )
¿Ves algún problema de sincronización? ¿Es volatile
requerido / ú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)
}
Además, estoy tratando de lograr una cierta reutilización del código, es por eso que aislé solo una función básica para implementar en el código específico de la plataforma ( add()
método dentro arm/atomic_int.cpp
).
¿Es atomic_int.h
realmente portátil como lo es a través de diferentes plataformas / arquitecturas / compiladores? ¿Es factible este enfoque ? (Con factible quiero decir factible para cada plataforma para garantizar la atomicidad implementando solo el add()
método ).
Aquí está la implementación correspondiente de ARM GCC 8.3.1 de la misma función. Aparentemente, la única diferencia real es la presencia de dmb
antes y después. ¿Son realmente necesarios en mi caso? ¿Por qué? ¿Tienes un ejemplo donde mi AtomicInt
(sin dmb
) falla?
ACTUALIZACIÓN: implementación fija, get()
método eliminado para resolver problemas de atomicidad y alineación. Ahora el se add()
comporta como un estándar fetchAndAdd()
.
volatile
La palabra clave en C ++ significa no optimizar a través de la variable. Entonces elget()
método se beneficia de ello. Aunque, en general, la volatilidad está a punto de desencriptarse en C ++. Si su sistema no puede sincronizar datos de 32 bits incorporados, entonces no tiene más remedio que usar mutexes, spinlock como mínimo.__ATOMIC_INT_H_
) y nombres que comienzan con un guión bajo seguido de una letra mayúscula están reservados para su uso por la implementación. No los uses en tu código.atomic
probablemente no se utilice para evitar confusionesstd::atomic
, aunque plantea la pregunta de por qué no lo usarías en cualquier caso.__ATOMIC_INT_H_
identificador renombrado .Respuestas:
Si usa
gcc
may be puede usar las funciones integradas heredadas__sync
para el acceso a la memoria atómica :Genera :
fuente
gcc
, y en cualquier caso no quiero vincular la implementación a ningún compilador específico. Gracias de todos modos por su sugerencia, al menos me dice que miadd()
parte ARM debe ser correcta. ¿Cuál es la diferencia entreldxr
yldrex
?dmb
antes y después del bucleldrex
/strex
.