我们继承了一个针对我一直关注的瑞萨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”。
答案 0 :(得分:3)
根据您的目标,即您是仅为特定平台撰写文章还是想确保可移植性,您需要记住以下几点:
启用优化后,只要操作的最终结果与单线程方案无法区分,许多编译器就会很乐意通过访问非易失性变量来重新排序对volatile变量的访问。这意味着代码如下:
int a = 0;
volatile int b = 0;
void interrupt_a(void)
{
a = b + 1;
b = 0; // set b to zero when done
}
可以rearranged by the compiler进入:
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]
阻止优化编译器重新排序的方法是将两个变量设置为volatile。 (或者,如果可用的话,使用带有_Atomic
存储的C11 memory_order_release
和memory_order_acquire
加载来对非原子变量的操作进行排序。)
如果您使用的是多核uC,它可以对内存操作进行重新排序,因此这并不能解决问题,实际的解决方案是为编译器和CPU发出栅栏,如果您关心其他核心上的观察者(或者甚至在单核心uC上的MMIO中)。单核或单线程上不需要硬件围栏指令,因为即使是乱序执行CPU也会看到它自己的操作按程序顺序发生。
同样,如果您使用特定嵌入式系统的工具链编译器并不知道有关栅栏的任何信息,那么它很可能不会做这样的事情。因此,您需要检查文档并检查已编译的程序集。
例如,处理器被允许的ARM docs状态允许&#34;重新排序指令,程序员应该注意添加内存障碍,但在此之后它还指出(在&#34;实现细节&#34;)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();
时,您的图书馆本身可能包含所有必需的围栅说明,或者根据此架构可能根本不需要它们。
增量操作(x++
)应该不被认为是原子的,即使它可能会意外地&#34;结果是在某个单核CPU上是原子的。正如您所注意到的那样,它在您的特定uC上并不是原子的,保证原子性的唯一方法是禁用此操作的中断。
因此,最终,您应确保阅读此平台的文档,并了解编译器可以做什么和不能做什么。如果编译器在你添加了一个看似微小的变化之后决定重新排序指令,那么今天有效的东西可能明天不起作用,特别是因为竞争条件可能不会频繁地立即检测到它。