Questa implementazione AtomicInt in C ++ è corretta?


9

Premessa: sto lavorando con un ambiente ARM (quasi bare metal) in cui non ho nemmeno C ++ 11 (con std::atomic<int>) disponibile, quindi per favore evita risposte come " usa solo C ++ standardstd::atomic<int> ": Non posso .

Questa implementazione ARM di AtomicInt è corretta? (supponiamo che l'architettura ARM sia ARMv7-A )

Vedi qualche problema di sincronizzazione? È volatilerichiesto / 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)
}

Inoltre, sto cercando di ottenere un riutilizzo del codice, ecco perché ho isolato solo una funzione di base da implementare nel codice specifico della piattaforma ( add()metodo all'interno arm/atomic_int.cpp).

È atomic_int.hdavvero portatile com'è su diverse piattaforme / architetture / compilatori? Questo approccio è fattibile ? (Con fattibile intendo fattibile per ogni piattaforma per garantire atomicità implementando solo il add()metodo ).

ecco l'implementazione corrispondente di ARM GCC 8.3.1 della stessa funzione. Apparentemente, l'unica vera differenza è la presenza di dmbprima e dopo. Sono davvero necessari nel mio caso? Perché? Hai un esempio in cui il mio AtomicInt(senza dmb) fallisce?

AGGIORNAMENTO: implementazione fissa, get()metodo rimosso per risolvere i problemi di atomicità e allineamento. Ora si add()comporta come uno standard fetchAndAdd().


volatilela parola chiave in C ++ significa non ottimizzare tramite variabile. Quindi il get()metodo ne trae beneficio. Sebbene, in generale, volatile stia per depricarsi in C ++. Se il tuo sistema non è in grado di sincronizzare i dati a 32 bit incorporati, allora hai poca scelta se non usare i mutex - spinlock almeno.
ALX23z,

Quale versione dell'architettura del braccio stai usando? ArMV-7?
Mike van Dyke,

1
Questo non affronta la domanda, ma i nomi che contengono due caratteri di sottolineatura consecutivi ( __ATOMIC_INT_H_) e i nomi che iniziano con un carattere di sottolineatura seguito da una lettera maiuscola sono riservati per l'implementazione. Non usarli nel tuo codice.
Pete Becker,

atomicProbabilmente il nome del membro non è probabilmente usato per evitare confusione std::atomic, anche se pone la domanda perché non lo useresti in ogni caso.
Clifford,

Aggiunta architettura ARM, __ATOMIC_INT_H_identificativo rinominato .
gentooise,

Risposte:


2

Se lo si utilizza, gccè possibile utilizzare le funzioni integrate legacy __syncper l'accesso alla memoria atomica :

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

Genera :

add(int volatile&, int):
.L2:
        ldxr    w2, [x0]
        add     w2, w2, w1
        stlxr   w3, w2, [x0]
        cbnz    w3, .L2
        dmb     ish
        ret

Purtroppo non sto usando gcc, e in ogni caso non voglio vincolare l'implementazione a nessun compilatore specifico. Grazie comunque per il tuo suggerimento, almeno mi dice che la mia add()parte ARM dovrebbe essere corretta. Qual è la differenza tra ldxre ldrex?
gentooise,

Questo è ARM8 (ad esempio 64 bit) piuttosto che una delle versioni a 32 bit.
marko,

Sono riuscito a ottenere il codice corrispondente specificando l'architettura di destinazione: link . Sembra che GCC inserisca effettivamente dmbprima e dopo il ciclo ldrex/ strex.
gentooise,

2
Penso che questo sia il buon approccio, ma per renderlo indipendente dal compilatore basta andare su godbolt.org/z/WB8rxw digitare la funzione che si desidera utilizzare i comandi incorporati di gcc e copiare l'output dell'assembly corrispondente. Assicurarsi di abbinare il parametro -march alla versione specifica di ARM.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.