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”即可。