如何在 Linux 上执行适用于 x86、arm、GCC 和 icc 的原子操作?

Posted

技术标签:

【中文标题】如何在 Linux 上执行适用于 x86、arm、GCC 和 icc 的原子操作?【英文标题】:How to perform atomic operations on Linux that work on x86, arm, GCC and icc? 【发布时间】:2011-01-18 06:00:08 【问题描述】:

如今,每个现代操作系统都提供了一些原子操作:

Windows 有Interlocked* API FreeBSD 有<machine/atomic.h> Solaris 有<atomic.h> Mac OS X 有<libkern/OSAtomic.h>

对于 Linux 有什么类似的吗?

我需要它在大多数 Linux 支持的平台上工作,包括:x86、x86_64 和 arm。 我需要它至少在 GCC 和 Intel 编译器上工作。 我不需要使用像 glib 或 qt 这样的 3rd par 库。 我需要它在 C++ 中工作(不需要 C)

问题:

GCC atomic builtins __sync_* 并非在所有平台 (ARM) 上均受支持,英特尔编译器也不支持。 AFAIK <asm/atomic.h> 不应该在用户空间中使用,我根本没有成功使用它。另外,我不确定它是否适用于英特尔编译器。

有什么建议吗?

我知道有很多相关问题,但其中一些指向 __sync*,这对我 (ARM) 来说是不可行的,有些指向 asm/atomic.h

也许有一个内联汇编库可以为 GCC 执行此操作(ICC 支持 gcc 汇编)?

编辑:

有一个仅针对添加操作的部分解决方案(允许实现原子计数器但不能实现需要 CAS 的无锁结构):

如果您使用libstc++(英特尔编译器使用libstdc++),那么您可以使用在<ext/atomicity.h><bits/atomicity.h> 中定义的__gnu_cxx::__exchange_and_add。取决于编译器版本。

不过我还是希望看到支持 CAS 的东西。

【问题讨论】:

@KennyTM 好问题。 AFAIK ll/sc 仅适用于 arm6 及更高版本?所以它可能只适用于 arm6 及更高版本 所以让我们澄清一下 - ARM 不支持 GCC 内在函数是因为 GCC 工具链的缺点还是因为 ARM 的缺点?您能否确认您不能将它们与支持这些原子操作的 GCC for ARM 一起使用? ARM 的缺点。一些相关支持说明: ARM v5: SWP(atomic swap word size and byte size) ARM v6: LDREX/STREX(32 bit chained possible atomic load/store)+SWP ARM v7: LDREX/STREX(与v6相同,但有大小8/16 位的选项)V5 不能在不禁用中断的情况下进行原子增量,用户空间也不能这样做。但是,它可以做的就是调用操作系统,比如一个软件中断,然后让操作系统来处理它。 与 C 类似,但不需要太多可移植性:***.com/questions/2353371/atomic-increment-fetch 【参考方案1】:

C 和 C++ 的最新标准(从 2011 年开始)现在指定原子操作:

C11:stdatomic.h C++11:std::atomic

无论如何,您的平台或编译器可能不支持这些较新的标头和功能。

【讨论】:

IIRC,对于 GCC,这可能需要使用 libatomic。 @Hasturkun:哪个版本的 GCC?最近的 GCC 版本仍然是这种情况吗? @Isaac:根据GCC Wiki on the C++11 memory model,从 GCC 4.7 开始,当无锁指令不可用时,可能需要 libatomic。 GCC 4.8 在其源代码树中包含 libatomic。【参考方案2】:

请参阅:kernel_user_helpers.txt 或 entry-arm.c 并查找 __kuser_cmpxchg。在其他 ARM Linux 版本的 cmets 中可以看到,

kuser_cmpxchg

位置:0xffff0fc0 参考原型: int __kuser_cmpxchg(int32_t oldval, int32_t newval, volatile int32_t *ptr); 输入: r0 = 旧值 r1 = 新值 r2 = 指针 lr = 返回地址 输出: r0 = 成功代码(零或非零) C 标志 = 如果 r0 == 0 则设置,如果 r0 != 0 则清除 破坏寄存器: r3,IP,标志 定义: 仅当 *ptr 等于 oldval 时,以原子方式将 newval 存储在 *ptr 中。 如果 *ptr 已更改,则返回零,如果没有发生交换,则返回非零。 如果 *ptr 已更改为允许汇编,则也会设置 C 标志 调用代码中的优化。 使用示例:
 typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
 #define __kuser_cmpxchg (*(__kuser_cmpxchg_t *)0xffff0fc0)

 int atomic_add(volatile int *ptr, int val)
 
        int old, new;

        do 
                old = *ptr;
                new = old + val;
         while(__kuser_cmpxchg(old, new, ptr));

        return new;

注意事项:

此例程已根据需要包含内存屏障。 仅当 __kuser_helper_version >= 2 时有效(来自内核版本 2.6.12)。

这适用于使用swp 原语的带有ARMv3 的Linux。你必须有一个非常古老的 ARM 不支持这个。只有 data abortinterrupt 会导致旋转失败,因此内核监视此地址 ~0xffff0fc0 并执行 user空间 PC 在发生数据中止中断 时进行修复。所有支持 ARMv5 及更低版本的用户空间库都将使用此工具。

例如,QtConcurrent 使用这个。

【讨论】:

【参考方案3】:

在 Debian/Ubuntu 上推荐...

sudo apt-get install libatomic-ops-dev

示例:http://www.hpl.hp.com/research/linux/atomic_ops/example.php4

GCC 和 ICC 兼容。

与 Intel Thread Building Blocks (TBB) 相比,使用 atomic,libatomic-ops-dev 的速度是两倍多! (英特尔编译器)

在 Ubuntu i7 生产者-消费者线程上测试 1000 万个整数在 0.5 秒内通过环形缓冲区连接,而 TBB 需要 1.2 秒

并且易于使用,例如

易失性AO_t头;

AO_fetch_and_add1(&head);

【讨论】:

【参考方案4】:

__sync* 肯定(并且已经)受到英特尔编译器的支持,因为 GCC 从那里采用了这些内置组件。阅读第一段on this page。另请参阅“Intel® C++ Compiler for Linux* Intrinsics Reference”,第 198 页。它来自 2006,并准确描述了这些内置插件。

关于 ARM 支持,对于较旧的 ARM CPU:它不能完全在用户空间中完成,但可以在内核空间中完成(通过在操作期间禁用中断),我想我在某处读到它支持很长一段时间现在。

根据this PHP bug,日期为 2011-10-08,__sync_* 只会失败

PA-RISC 与 Linux 以外的任何东西 SPARCv7 及更低版本 ARM 与 GCC ARMv5 及更低版本与 Linux 以外的任何版本 MIPS1

因此,使用 GCC > 4.3(当前版本为 4.7),您应该不会对 ARMv6 和更新版本有任何问题。只要为 Linux 编译,你对 ARMv5 应该没有问题。

【讨论】:

【参考方案5】:

这里有一个 GCC 补丁来支持 ARM 原子操作。在 Intel 上对您没有帮助,但您可以检查代码 - 最近内核支持较旧的 ARM 架构,而较新的内核支持内置指令,因此您应该能够构建一些可行的东西。

http://gcc.gnu.org/ml/gcc-patches/2011-07/msg00050.html

【讨论】:

【参考方案6】:

项目正在使用这个:

http://packages.debian.org/source/sid/libatomic-ops

如果你想要简单的操作,比如 CAS,难道你不能只使用内核中特定于架构的实现,并使用 autotools/cmake 在用户空间进行架构检查吗?就许可而言,虽然内核是 GPL,但我认为这些操作的内联程序集是由 Intel/AMD 提供的,而不是内核对它们有许可是有争议的。它们恰好在内核源代码中以易于访问的形式出现。

【讨论】:

【参考方案7】:

我最近做了一个这样的事情,我遇到了和你一样的困难。我的解决方案基本上如下:

尝试使用以下命令检测 gcc 内置函数 功能宏 如果不可用,只需执行 对于其他架构,类似于cmpxch__asm__(ARM 比这更复杂一些)。只需为一种可能的大小执行此操作,例如 sizeof(int)。 实现所有其他功能 在那一两个基元之上 带有inline 函数

【讨论】:

【参考方案8】:

Boost 具有非侵入式许可证,而其他框架已经提供便携式原子计数器——只要它们在目标平台上受支持。

第三方库对我们有好处。如果您的公司出于奇怪的原因禁止您使用它们,您仍然可以查看它们如何进行(只要许可证允许您使用)来实现您正在寻找的东西。

【讨论】:

哪个 Boost 库包含这些? 这是常见细节的一部分:boost.cvs.sourceforge.net/viewvc/boost/boost/boost/detail/… (a) Boost 经常回退到 pthreads,事实上对于 ARM,它会使用 pthreads。 (b) atomic_counters 仅提供对引用计数很好的 inc/dec 操作,但它们太弱了。如果你需要像原子链表这样的东西,你需要像 CAS 这样的操作。 “如果你的公司出于奇怪的原因禁止你使用它们”我不禁止我只是不想使用第三部分库来减少构建某些项目的依赖关系。【参考方案9】:

该死。我打算建议 GCC 原语,然后你说它们是禁区。 :-)

在这种情况下,我会为您关心的每个架构/编译器组合创建一个#ifdef,并编写内联汇编。并且可能检查__GNUC__ 或一些类似的宏,如果它们可用,则使用 GCC 原语,因为使用它们感觉更合适。 :-)

你会有很多重复,可能很难验证正确性,但这似乎是很多项目这样做的方式,我已经取得了很好的结果。

过去让我感到困扰的一些问题:在使用 GCC 时,不要忘记“asm <b>volatile</b>”和"memory""cc" 等的clobbers。

【讨论】:

我最终发现 asm volatile 不是易变的,尽管我怀疑这种现象不仅限于内联汇编。 gcc 原语的问题在于,在某些版本中它们实际上是可用的,但是没有功能测试宏来测试它们。

以上是关于如何在 Linux 上执行适用于 x86、arm、GCC 和 icc 的原子操作?的主要内容,如果未能解决你的问题,请参考以下文章

在Linux PC上查看arm交叉编译的可执行文件依赖的动态库

在 x86 上引导 Linux 与在 ARM 上引导 Linux 有何不同

安装交叉编译器

如何在x86上使用gcc强制执行内存排序

Linux系统下x86和ARM的区别有哪些?

qt-4.8.4安装和环境变量配置