以下是“标志"吗?中断和用户代码之间的变量访问安全? [英] Is the following "flag" variable access safe between interrupt and user code?

查看:87
本文介绍了以下是“标志"吗?中断和用户代码之间的变量访问安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们继承了一个针对我一直在研究的瑞萨RX231微控制器的项目.

We have inherited a project which targets Renesas RX231 microcontroller that I have been looking at.

此uC只有一条指令可锁定总线以实现原子性(XCHG).

This uC has only one instruction that locks the bus for atomicity (XCHG).

由于处理器是唯一访问RAM存储器的组件(未使用DMA或DTC),因此要操作与中断共享的用户代码中的变量,因此将禁用中断(在处理器状态字寄存器中)以进行访问时间,即

Because the processor is the only component that accesses RAM memory (no DMA or DTC being used), to manipulate variables in user code that are shared with interrupts, the interrupts are disabled (in the processor status word register) for the access time i.e.

disable_interrupts(); /* set_psw(get_psw() & ~(1 << 16)); */
/* access or modify shared variables */
enable_interrupts();  /* set_psw(get_psw() | (1 << 16)); */

但是,也有一些标志"在没有防护的情况下共享,这些标志在中断中设置,并通过以下方式在用户代码中进行轮询:

However, there are also "flags" being shared without guarding, which are set in interrupts and polled in user code in the following way:

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 */ 
              ... 
         }
         ...
     }
}

在这种情况下,我对无防护(通过禁用用户代码中的中断)的理解是:

My understanding of no guarding (by disabling interrupts in user code) in this case would be:

  • 要读取,设置或清除标志",始终使用两条指令,一条将存储器地址放入寄存器,一条用于读取/设置/清除
  • 内存地址始终相同,因此可以从考虑中丢弃
  • 每个读取/设置/清除操作都是一条指令,因此访问/操作是原子的

问题是:我的理解正确吗?在这种情况下,这样的标志"变量访问和操作安全吗?
还是可能存在任何错误/错误?

The question is: is my understanding correct? Is such "flag" variables access and manipulation safe in this case?
Or could there be any possible bugs/errors?

  • 假设使用的编译器和编译器选项始终相同.
  • 假设所描述的操作是访问/操作(设置为0或1,读取(全部在汇编代码中显示))标志"的唯一方式(没有加法,乘法等)

如果我们需要升级编译器或更改编译器选项怎么办?
这样简单的操作会导致不止一个指令"吗?

What if we needed to upgrade the compiler or change compiler options?
Could such simple operations result in more than "one instruction"?

在没有防护的情况下使用此类标志"的理由太限制了禁用中断的时间.

The justification of using such "flags" without guarding is too limit the amount time interrupts are disabled.

从代码逻辑上看,预期的行为是您可以请求一条消息多次,但只能得到一个答案.

Looking at the code logic, the expected behaviour is that you can request a message one or more times but you only get one answer.

PS.我尝试使用以下附加标签:"cc-rx","rxv2-指令集","rx231".

PS. I tried to use the following additional tags: "cc-rx", "rxv2-instruction-set", "rx231".

推荐答案

取决于您的目标,即是只为特定平台编写还是要确保可移植性,您需要牢记其他几点:

Depending on your goals, i.e. whether you're writing for a specific platform only or want to ensure portability, you need to keep several additional things in mind:

  1. 启用了优化功能后,只要操作的最终结果与单线程方案没有区别,许多编译器就会愉快地对对易失性变量的访问与对非易失性变量的访问进行重新排序.这意味着这样的代码:

  1. With optimizations enabled, many compilers will happily reorder accesses to volatile variables with accesses to non volatile variables, as long as the final result of the operation is indistinguishable to a single-thread scenario. This means that code like this:

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 _Atomicmemory_order_release存储区配合使用,并且memory_order_acquire加载以针对非原子变量的操作对其进行排序.)

The way to prevent an optimizing compiler from reordering would be to make both variables volatile. (Or if available, to use C11 _Atomic with memory_order_release stores and memory_order_acquire loads to order it with respect to operations on non-atomic variables.)

如果您使用的是多核uC,它可以对内存操作进行重新排序,所以这不能解决问题,并且如果您关心的话,实际的解决方案是为编译器和CPU发出信号.其他内核(甚至在MMIO中,甚至在单内核uC中)的观察者.不需要在单个核或单个线程上使用硬件隔离指令,因为即使是乱序的执行CPU也会看到它自己的操作是按程序顺序进行的.

If you're on a multi-core uC, it can reorder memory operations, so this doesn't solve the problem, and the actual solution is to emit a fence for both the compiler and the CPU, if you care about observers on other cores (or in MMIO even on a single-core uC). Hardware fence instructions aren't needed on a single core or single thread, because even an out-of-order execution CPU sees it's own operations happen in program order.

同样,如果您为特定嵌入式系统使用的工具链附带的编译器对篱笆一无所知,那么很可能将避免执行此类操作.因此,您需要检查文档并检查已编译的程序集.

Again, if the compiler you got with the toolchain for your particular embedded system doesn't know anything about fences, then it's quite likely it will refrain from doing stuff like this. So you need to examine the documentation and check the compiled assembly.

例如, ARM文档声明允许"处理器对指令进行重新排序,程序员应注意添加内存屏障,但紧接着,它还声明(在实现细节"下)Cortex M处理器不对指令进行重新排序. .但是,他们仍然坚持应该插入适当的屏障,因为这将简化移植到较新版本的处理器的过程.

For example, the ARM docs state that the processor is "allowed" to reorder instructions and the programmer should take care to add memory barriers, but right after that it also states (under "implementation detail") that Cortex M processors do not reorder instructions. However, they still insist that proper barriers should be inserted, since it will simplify porting to a newer version of the processor.

根据您的管道长度,在您发出请求之后,直到完全启用或禁用中断为止,可能需要执行两条指令.同样,您需要检查该特定uC/编译器的文档,但有时在写入寄存器后需要某种形式的保护.例如,在ARM Cortex上,您需要在禁用中断后同时发布DSB和ISB指令,以确保该中断不会在接下来的几条指令中输入

Depending on your pipeline length, it might take a couple of instructions until the interrupt is fully enabled or disabled, after you make the request. Again, you need to check the docs for this particular uC/compiler, but sometimes there needs to be a fence of some sort after writing to the register. For example, on ARM Cortex you need to issue both DSB and ISB instructions after disabling the interrupt, to make sure that the interrupt doesn't enter in the next several instructions

// 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();时,您的库本身可能包含所有必需的篱笆指令,或者对于该体系结构可能根本不需要它们.

Of course, your library might itself contain all required fence instructions when you call disable_interrupts();, or they might not be needed at all for this architecture.

增量操作(x++)应该被视为原子操作,即使在某些单核CPU上它可能偶然地"变成了原子操作.正如您所注意到的,它不是特定uC上的原子,并且保证原子性的唯一方法是禁用此操作周围的中断.

Increment operation (x++) should not be considered atomic, even it might "accidentally" turn out to be atomic on a certain single-core CPU. As you've noticed, it's not atomic on your particular uC, and the only way to guarantee atomicity is to disable interrupts around this operation.

因此,最终,您应该确保阅读该平台的文档,并了解编译器可以做什么和不能做什么.如果您在添加了看似很小的更改后编译器决定重新排序指令,那么今天可能行之有效的明天可能无法正常工作,尤其是因为竞争条件可能不足以立即检测到它.

So, ultimately, you should make sure you read the documentation for this platform and understand what the compiler can and cannot do. Something that works today might not work tomorrow if the compiler decides to reorder instructions after you've added a seemingly minor change, especially since a race condition might not be frequent enough to detect it immediately.

这篇关于以下是“标志"吗?中断和用户代码之间的变量访问安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆