在裸机控制器上的不同上下文中设置 int 变量中的标志

set a flag in int variable from different context on bare metal controller

对于 avr 8 位微控制器,必须在某个 8 位整数变量中设置或清除单个位(标志)。这个 set/clear 函数可以从普通上下文 ( main ) 中调用,也可以从中断处理程序 ( Isr() ) 中调用。因此,必须使变量 volatile 以防止它重新排序或将其保存在寄存器中的某个位置。 std::atomic 在这里不是一个有效的选项,因为没有底层 OS 也没有多 cpu 核心也没有缓存,所以不需要某种内存栅栏。甚至 std::atomic 也不是任何 avr c++ 库的一部分。

设置标志的操作类似于: some_flags|= new_set_flags

但是 c++20 我收到警告:warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile].

使用临时变量重写函数是没有问题的,但感觉这不是在这种情况下弃用volatile关键字的意图。

顺便说一句:由于变量存储在 RAM 中,cpu 无法在单个汇编程序指令中设置内存中的位。因此,整个操作必须是原子的。对于那个用例,avr-lib 有 ATOMIC_BLOCK(ATOMIC_RESTORESTATE),它只是禁用中断。

#include <util/atomic.h>

volatile uint8_t some_flags;

void SetFlag( uint8_t new_set_flags )
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) 
    {   
        uint8_t tmp = some_flags;
        tmp |= new_set_flags;
        some_flags = tmp;

        ... vs ...

        some_flags|= new_set_flags; // main.cpp:65:19: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
    }   
}

void SomeIsr()
{
    SetFlag( 0x02 );
}

int main()
{
    SetFlag( 0x01);
}

问题:如果此 flags/variable 用于具有单核且没有 OS 的裸机控制器的不同上下文中,则将标志设置为 int 变量的“正确”方法是什么? MMU.

如果你想在 IO 寄存器上设置一个位,这就变得很好奇了,它在 AVR 上类似于 PORTA|=0x01;,并且编译器可以在单个汇编指令中执行该操作。

#include <avr/io.h>

int main()
{
    PORTA|=0x01; // main.cpp:57:10: warning: compound assignment with 'volatile'-qualified left operand is deprecated [-Wvolatile]
}

0000006c <main>:
  6c:   d8 9a           sbi 0x1b, 0 ; 27
  6e:   90 e0           ldi r25, 0x00   ; 0
  70:   80 e0           ldi r24, 0x00   ; 0
  72:   08 95           ret

基本原理是复合赋值或预 post 递增或递减即使在 volatile 变量上也不是原子的,而程序员可以将其视为单个操作。此外,标准说 E1 op= E2 与 E1 = E1 op E2 相同,只是 E1 仅被评估一次。

这意味着不谨慎的程序员可以使用

volatile uint8_t some_flags;
...
some_flags|= new_set_flags;

期望它是原子的,即使存在硬件中断也不是必需的。

在机器级别,它看起来像 3 个操作:

load value from memory
update accumulator register
store value to memory

这意味着如果没有更多的预防措施,如果 2 个 执行线程(这里是正常处理和一个 ISR)交错,则会出现竞争条件:

normal loads
! ISR takes the processor
ISR loads updates and stores
! return from ISR
normal updates and stores erasing the change from ISR

当程序使用临时变量时,很明显会出现竞争条件。

对您不利的是,C++ 委员会已弃用该用法,目的是稍后将其完全删除。

所以你可以:

  • 在您的代码规范中添加它依赖于允许对 volatile 变量进行复合赋值,并且只希望编译器会为它提供选项(即使不是很好也感觉合理)
  • 添加兼容 C++17 但不支持 C++20 及更高版本的代码规范
  • 将其编译为 C 代码(C++ 标准仍然支持交叉 C - C++ 链接)
  • 就写成some_flags = some_flags | new_set_flags;

我更喜欢最后一种方法,因为对于易失性字节,编译器没有理由产生效率较低的代码,而且从早期的 C 版本到最后的 C++ 版本都是一致的


参考文献: