中断和用户代码之间的以下"标志"变量访问是否安全?

Krz*_*cha 5 c embedded assembly renesas-rx

我们继承了一个针对瑞萨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)); */
Run Code Online (Sandbox Code Playgroud)

但是,还有"标志"在没有保护的情况下共享,这些标志在中断中设置并以下列方式在用户代码中轮询:

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 */ 
              ... 
         }
         ...
     }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

如果我们需要升级编译器或更改编译器选项怎么办?
这样简单的操作能不仅仅产生"一条指令"?

在没有防护的情况下使用这种"标志"的理由限制了中断被禁用的时间量.

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

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

Gro*_*roo 4

根据您的目标,即您是仅为特定平台编写还是想确保可移植性,您需要记住以下几点:

  1. 启用优化后,许多编译器会很乐意将对易失性变量的访问与对非易失性变量的访问重新排序,只要操作的最终结果与单线程场景无法区分。这意味着像这样的代码:

    int a = 0;
    volatile int b = 0;
    
    void interrupt_a(void) 
    {
        a = b + 1;
        b = 0;       // set b to zero when done
    }
    
    Run Code Online (Sandbox Code Playgroud)

    可以被编译器重新排列为:

    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]
    
    Run Code Online (Sandbox Code Playgroud)

    防止优化编译器重新排序的方法是使两个变量都为易失性的。_Atomic(或者如果可用的话,将 C11与memory_order_release存储和加载一起使用memory_order_acquire,以针对非原子变量的操作对其进行排序。)

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

    同样,如果您使用特定嵌入式系统的工具链获得的编译器对栅栏一无所知,那么它很可能不会做这样的事情。所以你需要检查文档并检查编译后的程序集。

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

  2. 根据您的管道长度,在发出请求后,可能需要几条指令才能完全启用或禁用中断。同样,您需要检查此特定 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"
    
    Run Code Online (Sandbox Code Playgroud)

    当然,当您调用 时,您的库本身可能包含所有必需的栅栏指令disable_interrupts();,或者此架构可能根本不需要它们。

  3. 增量操作 ( x++) 不应视为原子操作,即使它可能“意外地”在某个单核 CPU 上被证明是原子操作。正如您所注意到的,它在您的特定 uC 上不是原子的,保证原子性的唯一方法是禁用此操作的中断。

因此,最终,您应该确保阅读该平台的文档并了解编译器可以做什么和不能做什么。如果编译器在添加看似微小的更改后决定重新排序指令,那么今天有效的东西明天可能就不起作用了,特别是因为竞争条件可能不够频繁而无法立即检测到它。