ISR 和多线程程序中的 C"易失性"关键字?



我读到关于在内存映射硬件寄存器、ISR和多线程程序中使用volatile关键字的信息。

1) 寄存器

uint8_t volatile * pReg;
while (*pReg == 0) { // do sth } // pReg point to status register 

2) ISR

int volatile flag = 0;
int main() 
{
while(!flag) { // do sth }
}
interrupt void rx_isr(void)
{
//change flag
}

3) 多线程

int volatile var = 0;
int task1()
{
while (var == 0) { // do sth }
}
int task2()
{
var++;
}

我可以理解为什么编译器会错误地优化while(情况1)如果volatile不存在,因为变量更改是从硬件中进行的,编译器可能看不到代码对变量的任何更改。

但对于情况2)和3),为什么需要volatile在这两种情况下,变量都被声明为全局,编译器可以看到它在多个地方使用。那么,如果变量不是volatile,编译器为什么要优化while循环呢?

这是因为编译器在设计上不知道"异步调用"(在ISR的情况下)或多线程吗?但这不可能,对吧?

此外,情况3)看起来像是多线程中没有volatile关键字的常见程序。假设我为全局变量添加了一些锁定(没有volatile关键字):

int var = 0;
int task1()
{
lock();   // some mutex
while (var == 0) { do sth }
release()
}
int task2()
{
lock();
var++;
release();
}

这对我来说已经足够正常了。那么在多线程中我真的需要volatile为什么我以前从未见过在多线程程序中为避免优化而将volatile限定符添加到变量中

使用volatile关键字的主要目的是防止编译器生成使用CPU寄存器作为更快表示变量的方法的代码。这迫使编译后的代码在每次访问变量时访问RAM中的确切内存位置,以获取可能已被另一个实体更改的变量的最新值。通过添加volatile,我们确保我们的代码知道任何其他人(如硬件或ISR)对变量所做的任何更改,并且不会发生一致性问题。

在没有volatile关键字的情况下,编译器试图通过将变量的内容从RAM读取到CPU寄存器一次并在循环或函数中使用该缓存值来生成更快的代码。访问RAM可能比访问CPU寄存器慢几十倍。

我对第1项和第2项有过经验,但我认为在多线程环境中不需要将变量定义为volatile。添加锁定/解锁机制是解决同步问题所必需的,与volatile无关。

是因为编译器在设计上不知道"异步调用"(在ISR的情况下)还是多线程?但这不可能,对吧

是的,就是这样。

在C中,编译器没有并发性的概念,所以它可以重新排序和缓存内存访问,只要来自单个线程的视图不能注意到差异。

这就是为什么您需要volatile(为变量阻止这种优化)、内存屏障(为所有变量在程序的一个点阻止它)或其他形式的同步,如锁定(通常充当内存屏障)。

编译器实际上不允许任何其他更改您的变量,除非满足某些特定条件。其中之一是易失性访问;其他则是某些编译器的障碍。

你可能想到的编程多线程代码的天真方法确实容易出错,并且会被认为是未定义的行为。如果您有正确的多线程代码,那么优化仍然是合法的(就像在最终的task1中,循环仍然是UB,可能会被抛出),或者同步原语包含必要的障碍(通常在一些原子变量的内部)。

为了总结,这里有一个多线程示例的更正版本:

for (;;)
{
lock();
if (var != 0) { unlock(); break; }
unlock();
}

unlock()函数的实现引入了一个编译器屏障,确保循环不能被优化掉。

您可以通过使用屏障来自由避免多线程软件中的易失性变量。您可以在linux内核源代码中找到许多示例。此外,使用屏障而不是volatile允许编译器生成更高效的代码。

对于情况2),

我已经在你的问题中多次编写了与案例2)相同的代码,但没有遇到任何问题。我认为这是因为现代编译器能够处理这种情况。比如说,编译器可以"看到"rx_isr内部的"我更改"标志,并且不添加任何优化。然而,由于以下原因,这是不安全的:

1) 编译器的优化级别,这可能会影响以下原因3)

2) 调用您的isr的方法,可能是一个函数指针超出了编译器的视图

3) 编译器实现,不同的编译器可能对"参见isr中更改的标志"有不同的定义

因此,为了最大限度地确保安全和可移植性,只需添加"易失性"即可。

最新更新