当std::atomic<T>::is_always_lock_free为false时,std::atomic<T>在中断下是否安全?

8
在没有操作系统的嵌入式(ARM)环境中,如果使用中断,那么使用std::atomic<T>是否会出现死锁?如果会,如何避免?
一般而言,在任何时候,控制流都可能被中断以处理中断。特别地,如果想要使用互斥锁对变量进行“安全”写操作,一个简单的方法是先锁住,然后写入并解锁,其他位置再锁住、读取并解锁。但如果读取在中断中进行,则可能导致锁定、中断、锁定 => 死锁。
具体来说,我有一个std::atomic<int>,其中is_always_lock_freefalse。我应该担心死锁的情况吗?当我查看生成的汇编代码时,写入42的样子如下:
bl __sync_synchronize
mov r3, #42
str r3, [sp, #4]
bl __sync_synchronize

似乎没有出现锁定。读取值的汇编代码类似。那么对于像exchange这样的复杂操作,是否存在(可能的)锁定呢?


如果__sync_synchronize只是一个内存屏障,那么这段代码只能防止多个核心重新排序或预取指令。据我所知,它不能防止中断,并且生成的汇编代码不是中断安全的。为了使此代码安全,__sync_synchronize必须禁用所有可屏蔽中断。如果没有这样做,我会称其为不符合规范的实现。 - Lundin
为了排除这是否像以前那样是C++的小问题,您可以尝试使用C11的_Atomic int并查看它是否生成相同的汇编。 - Lundin
我相信在我的处理器上,内存读写是原子性的,并且我相信它不会进行重排序,因此我认为至少对于我的目的,同步是不必要的。 - Ben
这是几条指令,因此根据定义它不是原子的。 - Lundin
1个回答

3

__sync_synchronize是一个完整内存屏障的内建函数。它没有涉及到锁定,因此与互斥锁和中断处理程序可能发生死锁的情况不同。

你使用的是什么ARM核心?在ARM Cortex-A7上,以下内容对于两者都会打印true

#include <iostream>
#include <atomic>

int main()
{
   std::atomic<int> x;
   std::cout << std::boolalpha << x.is_lock_free() << std::endl;
   std::cout << std::atomic<int>::is_always_lock_free << std::endl;
}

我希望 std::atomic<int> 能够在 ARM 上实现无锁操作,至少大部分时间都是如此,从您提供的汇编代码来看,它似乎没有使用锁。

这是关于嵌入式的内容。 "主流" 意味着从 S08 到 x64 的任何东西。后者不太可能裸机使用(除非我们谈论 BIOS)。你不能假设任何事情。几乎所有的8/16位MCU,以及一些更大的MCU,都必须使用某种方式锁定 int 操作。其余的可以用重试循环来处理,但这可能会更糟,这取决于项目。 - too honest for this site
@Olaf 在S08及其类似芯片中,它可以在一定程度上执行原子int操作。但是你不能简单地相信像int a,b; ... a = b;这样的C代码是原子的。你必须要么禁用中断,要么编写内联汇编代码才能100%确定。 - Lundin
@Lundin:这应该是针对OP而不是我发出的,我认为这基本上就是我写的。(而且,对于S08它取决于具体的操作等)。尽管如此,不要仅仅依靠编译器发出此代码。但这不是重点,S08只是一个小型MCU。无论如何,感谢提供额外的细节,OP在此期间已编辑答案。它仍然有问题。ARM不会使用锁定,但可能需要使用独占式加载/存储进行多次迭代,这可能比短中断锁定更昂贵。 - too honest for this site
据我所知,你使用std::atomic的示例应该是安全的,这就是为什么你发布的反汇编代码对我来说看起来不符合要求。确实,如果您暂时禁用中断,您可能会延迟它们或者如果有多个来自同一来源的中断,则可能会错过其中一些。 - Lundin
汇编代码有什么不符合标准的地方吗?按照我的理解,它进行同步以防止重新排序,然后进行存储,这是原子性的,最后再次进行同步以防止重新排序。你看到了什么问题? - Ben
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接