这个C ++ AtomicInt实现正确吗?


9

前提:我正在使用甚至没有C ++ 11(带有std::atomic<int>)的ARM嵌入式(几乎是裸机)环境,因此请避免回答“ 仅使用标准C ++”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()method arm/atomic_int.cpp)的原因。

atomic_int.h真正便携,因为它是在不同的平台/体系结构/编译器?这种方法可行吗?(可行的意思是,每个平台通过仅实施add()方法)。

是相同功能的相应ARM GCC 8.3.1实现。显然,唯一真正的区别是dmb之前和之后的存在。就我而言,他们真的需要吗?为什么?您是否有我AtomicInt(没有dmb)失败的例子?

更新:修复了实现,删除get()了解决原子性和对齐问题的方法。现在,add()行为就像一个标准fetchAndAdd()


volatileC ++中的关键字表示不要通过变量进行优化。因此get()方法从中受益。虽然,总的来说,volatile将在C ++中逐渐消失。如果您的系统无法内置同步32位数据,那么您别无选择,只能使用互斥锁-至少是自旋锁。
ALX23z

您正在使用哪个版本的手臂架构?armv-7?
迈克·范·戴克

1
这没有解决问题,但是包含两个连续下划线(__ATOMIC_INT_H_)的名称以及以下划线后跟大写字母开头的名称保留给实现使用。不要在代码中使用它们。
皮特·贝克尔,

atomic最好不要使用成员名称来避免与混淆std::atomic,尽管它提出了一个问题,为什么无论如何都不会使用它。
克利福德,

添加了ARM体系结构,重命名了__ATOMIC_INT_H_标识符。
gentooise

Answers:


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,无论如何我都不想将实现绑定到任何特定的编译器。无论如何,谢谢您的提示,至少它告诉我我的ARM add()部分应该正确。ldxr和之间有什么区别ldrex
gentooise

这是ARM8(例如64位)而不是32位版本之一。
marko

我设法通过指定目标体系结构来获取相应的代码:link。看来GCC实际上放在/ 循环dmb之前和之后。ldrexstrex
gentooise

2
我认为这是个好方法,但是要使其独立于编译器,只需在要使用gcc内置函数的函数中转到godbolt.org/z/WB8rxw类型,然后复制相应的程序集输出即可。确保将-march参数与特定版本的ARM匹配。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.