在裸机控制器上的不同上下文中设置 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++ 版本都是一致的
参考文献:
- P1152R4 Deprecating volatile Rev4: 当前版本
- P1152R4 Deprecating volatile Rev0:具有广泛背景信息的原始版本,主要是为什么建议更改?部分
对于 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++ 版本都是一致的
参考文献:
- P1152R4 Deprecating volatile Rev4: 当前版本
- P1152R4 Deprecating volatile Rev0:具有广泛背景信息的原始版本,主要是为什么建议更改?部分