[英]Is the following “flag” variable access safe between interrupt and user code?
我们继承了一个针对瑞萨RX231微控制器的项目,我一直在关注它。
该uC只有一条指令锁定总线的原子性(XCHG)。
因为处理器是访问RAM存储器的唯一组件(没有使用DMA或DTC),要操纵用户代码中与中断共享的变量,所以中断被禁用(在处理器状态字寄存器中)以获取访问时间,即
disable_interrupts(); /* set_psw(get_psw() & ~(1 << 16)); */
/* access or modify shared variables */
enable_interrupts(); /* set_psw(get_psw() | (1 << 16)); */
但是,还有“标志”在没有保护的情况下共享,这些标志在中断中设置并以下列方式在用户代码中轮询:
volatile unsigned char event_request_message = 0;
unsigned char condition_sending_message = 0;
#pragma interrupt
void on_request_message()
{
...
event_request_message = 1; // mov.l #0x3df5, r14
// mov.b #1, [r14]
...
}
void user_code()
{
for(;;)
{
...
/* might be evaluated multiple times before transmit message is completed */
if(event_request_message && !condition_sending_message) // mov.l #0x3df5, r14
// movu.b [r14], r14
// cmp #0, r14
// beq.b 0xfff8e17b <user_code+185>
// mov.l #0x5990, r14
// movu.b [r14], r14
// cmp #0, r14
// bne.b 0xfff8e16f <user_code+173>
{
event_request_message = 0; // mov.l #0x3df5, r14
// mov.b #0, [r14]
condition_sending_message = 1; // mov.l #0x5990, r14
// mov.b #1, [r14]
/* transmit message */
...
}
...
}
}
在这种情况下,我对无保护(通过禁用用户代码中的中断)的理解是:
问题是:我的理解是否正确? 在这种情况下,这样的“标志”变量是否可以访问和操作?
或者可能存在任何可能的错误/错误?
如果我们需要升级编译器或更改编译器选项怎么办?
这样简单的操作能不仅仅产生“一条指令”?
在没有防护的情况下使用这种“标志”的理由限制了中断被禁用的时间量。
查看代码逻辑,预期的行为是您可以请求一次或多次消息,但只能获得一个答案。
PS。 我尝试使用以下附加标签:“cc-rx”,“rxv2-instruction-set”,“rx231”。
根据您的目标,即您是仅为特定平台编写还是想确保可移植性,您需要记住以下几点:
启用优化后,只要操作的最终结果与单线程方案无法区分,许多编译器就会很乐意通过访问非易失性变量来重新排序对volatile变量的访问。 这意味着代码如下:
int a = 0; volatile int b = 0; void interrupt_a(void) { a = b + 1; b = 0; // set b to zero when done }
可以由编译器重新排列为:
load acc from [b] store 0 into [b] // set b to zero *before* updating a, to mess with you a bit add 1 to acc store acc into [a]
阻止优化编译器重新排序的方法是使两个变量都是易变的。 (如果可用,使用C11 _Atomic
与memory_order_release
商店和memory_order_acquire
负载相对于非原子变量的操作来订购吧。)
如果您使用的是多核uC,它可以对内存操作进行重新排序,因此这并不能解决问题,而实际的解决方案是为编译器和CPU发出一个栅栏,如果您关心其他人的观察者核心(或甚至在单核uC上的MMIO)。 单核或单线程上不需要硬件围栏指令,因为即使是无序执行CPU也会看到它自己的操作按程序顺序发生。
同样,如果您使用特定嵌入式系统的工具链获得的编译器对栅栏一无所知,那么它很可能不会做这样的事情。 因此,您需要检查文档并检查已编译的程序集。
例如, ARM文档声明处理器“允许”重新排序指令,程序员应注意添加内存屏障,但在此之后它还指出(在“实现细节”下)Cortex M处理器不重新排序指令。 但是,他们仍然坚持应该插入适当的障碍,因为它将简化移植到更新版本的处理器。
根据您的管道长度,在您发出请求后,可能需要几个指令才能完全启用或禁用中断。 同样,您需要检查此特定uC /编译器的文档,但有时在写入寄存器后需要有某种类型的栅栏。 例如,在ARM Cortex上,您需要在禁用中断后发出DSB和ISB指令,以确保中断不会在下几条指令中输入
// you would have to do this on an ARM Cortex uC DisableIRQ(device_IRQn); // Disable certain interrupt by writing to NVIC_CLRENA DSB(); // data memory barrier ISB(); // instruction synchronization barrier // <-- this is where the interrupt is "really disabled"
当然,当你调用disable_interrupts();
时,你的库本身可能包含所有必需的fence指令disable_interrupts();
,或者对于这种架构可能根本不需要它们。
增值业务( x++
), 不应该被认为是原子的,甚至可能“不小心”变成是一定的单核CPU上的原子。 正如您所注意到的,它在您的特定uC上不是原子的,并且保证原子性的唯一方法是禁用此操作的中断。
因此,最终,您应该确保阅读此平台的文档,并了解编译器可以做什么和不可以做什么。 如果编译器在您添加了看似微小的更改之后决定重新排序指令,那么今天有用的东西可能明天不起作用,特别是因为竞争条件可能不够频繁而无法立即检测到它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.