10

我读到了 Cvolatile关键字在内存映射硬件寄存器、ISR 和多线程程序中的用法。

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 呢?在这两种情况下,变量都被声明为global,编译器可以看到它在多个地方使用。while那么,如果变量不是,为什么编译器会优化循环volatile呢?

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

此外,case 3) 看起来像一个没有volatile关键字的多线程中的普通程序。假设我为全局变量添加了一些锁定(无volatile关键字):

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

对我来说这看起来很正常。那么我真的需要volatile多线程吗?为什么我以前从未见过volatile将限定符添加到变量以避免多线程程序中的优化

4

5 回答 5

10

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

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

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

于 2012-10-05T05:09:58.293 回答
3
是因为编译器的设计不知道“异步调用”(在 ISR 的情况下)还是多线程?但这不可能,对吧?

是的,就是这样。

在 C 中,编译器没有并发的概念,因此允许重新排序和缓存内存访问,只要来自单个线程的视图无法注意到差异。

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

于 2012-10-07T14:52:31.097 回答
2

编译器确实允许除非满足某些特定条件,否则没有其他任何东西会更改您的变量。其中之一是可变访问;其他是某些编译器障碍。

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

总结一下,这里是多线程示例的更正版本:

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

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

于 2012-10-05T02:34:15.807 回答
0

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

于 2012-10-05T12:43:41.590 回答
0

至于情况2),

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

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

2)调用你的isr的方法,可能是一个函数指针不在编译器的视图中

3) 编译器实现,不同的编译器可能对“see flag changed in isr”有不同的定义

...

所以,为了最大程度的安全和便携,只要加上“volatile”就行了。

于 2018-01-23T08:43:32.267 回答