use*_*113 13 c++ embedded volatile isr
我已经看到了这些问题的一些风格,也看到了不同的答案,但仍然不确定它们是否是最新的并完全适用于我的用例,所以我会在这里提问。如果它是重复的,请告诉我!
鉴于我正在使用 C++17 和 gcc-arm-none-eabi-9 工具链为 STM32 微控制器(裸机)开发:
我还需要使用volatile用于在 ISR 和 之间共享数据main()吗?
volatile std::int32_t flag = 0;
extern "C" void ISR()
{
flag = 1;
}
int main()
{
while (!flag) { ... }
}
Run Code Online (Sandbox Code Playgroud)
我很清楚,我应该始终volatile用于访问内存映射的硬件寄存器。
但是对于 ISR 用例,我不知道它是否可以被视为“多线程”的情况。在这种情况下,人们建议使用 C++11 的新线程特性(例如std::atomic)。我知道volatile(不优化)和atomic(安全访问)之间的区别,所以建议的答案std::atomic在这里让我感到困惑。
对于 x86 系统上“真正的”多线程的情况,我没有看到需要使用volatile.
换句话说:编译器flag可以知道可以在 ISR 内部更改吗?如果没有,它如何在常规多线程应用程序中知道它?
谢谢!
我认为在这种情况下,易失性和原子性很可能在 32 位 ARM 上实际工作。至少在旧版本的 STM32 工具中,我看到事实上 C 原子是使用小型类型的 volatile 实现的。
Volatile 会起作用,因为编译器可能不会优化对代码中出现的变量的任何访问。
但是,对于无法在单个指令中加载的类型,生成的代码必须有所不同。如果您使用 a volatile int64_t,编译器会很乐意将其加载到两个单独的指令中。如果 ISR 在加载变量的两半之间运行,您将加载一半旧值和一半新值。
atomic<int64_t>不幸的是,如果实现不是无锁的,则使用中断服务例程也可能会失败。对于 Cortex-M,64 位访问不一定是无锁的,因此在不检查实现的情况下不应依赖原子性。根据实现的不同,如果锁定机制不可重入并且在持有锁时发生中断,则系统可能会死锁。从 C++17 开始,可以通过检查 来查询atomic<T>::is_always_lock_free。flagA.is_lock_free()自 C++11 以来,可以通过检查获得特定原子变量的特定答案(这可能取决于对齐) 。
因此,较长的数据必须通过单独的机制进行保护(例如,通过关闭访问周围的中断并使变量原子化或易失性)。
所以正确的方法是使用std::atomic,只要访问是无锁的。如果您担心性能,选择适当的内存顺序并坚持使用可以在单个指令中加载的值可能会有所帮助。
不使用任何一个都是错误的,编译器只会检查该标志一次。
这些函数都等待标志,但它们的翻译方式不同:
#include <atomic>
#include <cstdint>
using FlagT = std::int32_t;
volatile FlagT flag = 0;
void waitV()
{
while (!flag) {}
}
std::atomic<FlagT> flagA;
void waitA()
{
while(!flagA) {}
}
void waitRelaxed()
{
while(!flagA.load(std::memory_order_relaxed)) {}
}
FlagT wrongFlag;
void waitWrong()
{
while(!wrongFlag) {}
}
Run Code Online (Sandbox Code Playgroud)
使用 易失性,你会得到一个循环,根据需要重新检查标志:
waitV():
ldr r2, .L5
.L2:
ldr r3, [r2]
cmp r3, #0
beq .L2
bx lr
.L5:
.word .LANCHOR0
Run Code Online (Sandbox Code Playgroud)
具有默认顺序一致访问的原子会产生同步访问:
waitA():
push {r4, lr}
.L8:
bl __sync_synchronize
ldr r3, .L11
ldr r4, [r3, #4]
bl __sync_synchronize
cmp r4, #0
beq .L8
pop {r4}
pop {r0}
bx r0
.L11:
.word .LANCHOR0
Run Code Online (Sandbox Code Playgroud)
如果你不关心内存顺序,你会得到一个工作循环,就像 volatile 一样:
waitRelaxed():
ldr r2, .L17
.L14:
ldr r3, [r2, #4]
cmp r3, #0
beq .L14
bx lr
.L17:
.word .LANCHOR0
Run Code Online (Sandbox Code Playgroud)
在启用优化的情况下,既不使用易失性也不使用原子性会咬你,因为该标志仅检查一次:
waitWrong():
ldr r3, .L24
ldr r3, [r3, #8]
cmp r3, #0
bne .L23
.L22: // infinite loop!
b .L22
.L23:
bx lr
.L24:
.word .LANCHOR0
flag:
flagA:
wrongFlag:
Run Code Online (Sandbox Code Playgroud)