wok*_*oky 10 c arm volatile bit-fields
我正在为Cortex-M0 CPU和gcc编写代码.我有以下结构:
struct {
volatile unsigned flag1: 1;
unsigned flag2: 1;
unsigned foo; // something else accessed in main loop
} flags;
Run Code Online (Sandbox Code Playgroud)
flag1从GPIO中断处理程序和主循环读取和写入.flag2只在主循环中读写.
ISR看起来像这样:
void handleIRQ(void) {
if (!flags.flag1) {
flags.flag1 = 1;
// enable some hw timer
}
}
Run Code Online (Sandbox Code Playgroud)
主循环看起来像这样:
for (;;) {
// disable IRQ
if (flags.flag1) {
// handle IRQ
flags.flag1 = 0;
// access (rw) flag2 many times
}
// wait for interrupt, enable IRQ
}
Run Code Online (Sandbox Code Playgroud)
flag2在主循环中访问时,编译器是否会优化对它的访问,以便每次在代码中读取或写入时都不会将其提取或存储到内存中?
我不清楚因为要设置flag1ISR,它需要加载整数char,设置一下并存储回来.
我对C11标准的解读是,使用位域是不合适的 - 即使它们都被声明为volatile.以下摘录来自3.14内存位置:
- 内存位置
标量类型的对象,或者具有非零宽度的相邻位域的最大序列注1:两个执行线程可以更新和访问单独的存储器位置,而不会相互干扰.
注2: 如果在它们之间声明的所有成员也是(非零长度)位域,则同时更新同一结构中的两个非原子位域是不安全的,无论这些中间位域的大小如何碰巧是.
没有例外volatile.因此,如果两个执行线程(即主要和ISR)如果ISR将更新一个标志而主要将更新另一个标志,则使用上述位域是不安全的.给出的解决方案是在两者之间添加一个大小为0的成员,以强制它们放置在不同的内存位置.但话说再说一遍,这意味着两个标志都会消耗至少一个字节的内存,因此使用非位字段unsigned char或者bool对它们来说再简单一点:
struct {
volatile bool flag1;
bool flag2;
unsigned foo; // something else accessed in main loop
} flags;
Run Code Online (Sandbox Code Playgroud)
现在,它们将被放置在不同的存储位置,并且可以在不相互干扰的情况下进行更新.
然而,volatilefor flag1仍然是严格必要的,因为否则更新flag1将在主线程中具有副作用,并且编译器可以推断它可以仅将该字段保留在寄存器中 - 或者根本不需要更新任何内容.
但是,需要注意的是,在C11下,即使保证volatile可能还不够:5.1.2.3p5:
当通过接收信号中断抽象机器的处理时,既不是无锁原子对象也不是类型volatile sig_atomic_t的对象的值是未指定的,浮点环境的状态也是如此.处理程序修改的既不是无锁原子对象也不是volatile sig_atomic_t类型的任何对象的值在处理程序退出时变得不确定,如果由处理程序修改并且未恢复,则浮点环境的状态也是如此到原来的状态.
因此,如果需要完全兼容,则flag1应该是例如类型volatile _Atomic bool; 甚至可以使用_Atomic位域.但是,这两个都需要C11编译器.
然后,您可以检查编译器的手册,如果它们保证对这些易失性对象的访问也保证是原子的.
仅一位的 volatile 标志并不是那么有意义——它甚至可能是有害的。编译器在实践中可能会做的是分配两个内存块,每块可能是 32 位宽。因为 volatile 标志阻止它在同一分配区域内组合两个位,因为没有可用的位级访问指令。
在主循环中访问 flag2 时,编译器是否会优化对它的访问,以便每次在代码中读取或写入时都不会将其提取或存储到内存中?
这很难说,取决于有多少数据寄存器可用。反汇编代码看看。
总的来说,不推荐使用位字段,因为标准对它们的定义很差。在这种情况下,单个易失性位可能会导致分配额外的内存。
相反,你应该这样做:
volatile bool flag1;
bool flag2;
Run Code Online (Sandbox Code Playgroud)
假设这些标志不是硬件寄存器的一部分,在这种情况下,代码从一开始就不正确,它们都应该是易失性的。