ISR和multithreading程序中的C’Volatile’关键字?

我读到了内存映射硬件寄存器,ISR和multithreading程序中C 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)multithreading

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

我可以看到为什么编译器可以错误地优化while ,如果是1)如果不存在volatile ,’因为变量是由硬件进行的 ,编译器可能看不到代码所做的变量的任何变化。

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

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

此外,案例3)看起来像没有volatile关键字的multithreading中的常见程序。 假设我在全局变量中添加了一些锁定(没有volatile关键字):

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

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

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

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

我已经有了第1项和第2项的经验,但我认为你不需要在multithreading环境中将变量定义为volatile 。 添加锁定/解锁机制对于解决同步问题是必要的,并且与volatile的含义无关。

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

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

为了解决这个问题,这里是multithreading示例的更正版本:

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

unlock()函数的实现引入了编译器屏障,确保无法优化循环。

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

是的,就是这样。

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

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

您可以通过使用障碍自由地避免multithreading软件中的volatile变量。 你可以在linux内核源代码中找到很多例子。 使用障碍而不是volatile允许编译器生成更高效的代码。

至于案例2),

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

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

2)调用isr的方法,可能是函数指针是编译器的视图

3)编译器实现,不同的编译器可能有不同的定义“看到isr中的标志改变了”

因此,为了最大程度地保证安全性和便携性,只需添加“volatile”即可。