このC ++ AtomicInt実装は正しいですか?


9

前提:私はARM組み込み(ほとんどベアメタル)環境で作業していますが、C ++ 11(も)さえ使用できないので、標準のC ++のみを使用するstd::atomic<int>」などの回答は避けてください:できませんstd::atomic<int>

このAtomicIntのARM 実装は正しいですか?(ARMアーキテクチャがARMv7-Aであると想定)

同期の問題が発生していますか?それはvolatile必要/便利?

// 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)
}

また、私はいくつかのコードの再利用を実現しようとしています。そのため、プラットフォーム固有のコード(add()内のメソッドarm/atomic_int.cpp)に実装する基本的な関数を1つだけ分離しました。

あるatomic_int.h本当にポータブルそれは、異なるプラットフォーム/アーキテクチャ/コンパイラを越えていると?このアプローチは実行可能ですか?(実現可能とは、すべてのプラットフォームがadd()メソッドのみを実装することによって原子性を保証することが実現可能であることを意味します)。

これは、同じ関数の対応するARM GCC 8.3.1実装です。どうやら、唯一の本当の違いは、dmb前と後の存在です。私の場合は本当に必要ですか?どうして?AtomicInt(なしでdmb)私が失敗する例はありますか?

更新:修正された実装、削除 get()原子性と整列の問題を解決するためのメソッドの。これでadd()標準のように動作しますfetchAndAdd()


volatileC ++のキーワードは、変数を介して最適化しないことを意味します。したがって、get()メソッドはその恩恵を受けます。ただし、一般的に、C ++ではvolatileが廃止されようとしています。システムが32ビットデータを組み込みで同期できない場合は、ミューテックスを使用する以外に選択肢はほとんどありません-少なくともスピンロック。
ALX23z

使用しているarmアーキテクチャのバージョンは何ですか?armv-7?
マイクヴァンダイク

1
これは質問には対応していませんが、2つの連続したアンダースコア(__ATOMIC_INT_H_)を含む名前と、アンダースコアで始まり、その後に大文字が続く名前は、実装で使用するために予約されています。コードでそれらを使用しないでください。
ピートベッカー

メンバー名atomicstd::atomic、との混同を避けるためにおそらく使用しないのが最善ですが、どのような場合でもそれを単に使用しない理由を疑います。
クリフォード

ARMアーキテクチャが追加さ__ATOMIC_INT_H_れ、識別子の名前が変更されました。
gentooise

回答:


2

使用するgcc場合は、アトミックメモリアクセスにレガシー__sync組み込み関数を使用できます。

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

生成する

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

残念ながら、私はを使用していgccません。どのような場合でも、実装を特定のコンパイラーにバインドしたくありません。とにかくあなたのヒントをありがとう、少なくとも私の腕のadd()部分は正しいはずです。違いは何だldxrとはldrex
gentooise

これは、32ビットバージョンの1つではなく、ARM8(eg 64ビット)です。
marko

ターゲットアーキテクチャを指定して、対応するコードを取得できました:link。GCCは実際に/ ループのdmb前後に配置しているようです。ldrexstrex
gentooise

2
これは良いアプローチだと思いますが、コンパイラに依存しないようにするには、gcc ビルトインを使用して必要な関数のgodbolt.org/z/WB8rxwタイプに移動し、対応するアセンブリ出力をコピーします。-marchパラメータを特定のバージョンのARMと必ず一致させてください。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.