使用volatile
经常被引用为解决方案,但这并不完全正确。它通常会掩盖问题,因为volatile
总是会使代码变慢。如果您的唯一用途如图所示,那么volatile
可能会起作用。
单个读取器和单个写入使用内存屏障可能会更好。这将是你的代码,
主线:
volatile int *p = &flag;
while (*p == false); /* You must use volatile if you poll */
flag = false;
asm volatile ("" : : : "memory"); /* gcc barrier */
伊斯尔:
/* do something */
flag=true
asm volatile ("" : : : "memory"); /* gcc barrier */
在这里,屏障只是强制编译器在该点执行ARM str
指令。优化器不会在之前或之后移动任何代码。您还可以根据您的ARM CPU使用swp
orldrex
和。同样,环形缓冲区通常与ISR和主线一起使用,因为它们不需要任何特殊的 CPU 支持。只有编译器内存屏障。strex
查看lock-free并专门搜索lock-free和arm。
编辑:对于补充,
有没有办法让我完全不会错过中断?
这取决于中断源。如果它是一个定时器,并且你知道定时器源永远不会比XX指令快,并且系统中没有其他中断处于活动状态,那么你当前的代码将工作。但是,如果中断来自外部源,如以太网控制器、非去抖键盘等。多个中断可能很快出现。有时甚至在中断处理程序期间会发生新的中断。根据 ISR 来源,有不同的解决方案。环形缓冲区通常用于将来自ISR的工作项排队等待主线。对于UART,环可能包含实际的字符数据。可以是指针列表等。当通信变得更复杂时,很难从主线同步ISR ;所以我相信答案取决于中断源。这就是为什么每个操作系统都有这么多的原语和基础设施来解决这个问题。
内存屏障如何解决问题,当代码在单个 cpu 上运行时是否有效?
内存屏障并不能完全解决错过中断的问题;就像volatile
没有。他们只是让窗户小得多。它们强制编译器提前安排加载或存储。例如主线循环,
1: ldr r0, [r1]
cmp r0, #0 ; xxx
bne 1b ; xxx
mov r0,#1 ; xxx
str r0, [r1]
如果在xxx行期间发生第二次中断,那么您flag
应该设置两次并且您错过了一次中断。障碍只是确保编译器将和ldr
放在str
一起。
在不同上下文之间使用障碍时的预期行为是什么?
我展示的编译器内存屏障只是让编译器更快地完成工作。它在上下文之间没有影响。有不同的障碍;但它们大多用于多 CPU 设计。
while 循环中的睡眠可以解决同步问题吗?
不是真的,这只是更有效的使用。ARMWFI
指令可以暂时停止CPU,这样可以节省电力。这通常是sleep()在 ARM 上所做的。如果这是一个问题,我认为您需要更改ISR和mainline之间的通信。这取决于ISR来源。