我读到了内存映射硬件寄存器,ISR和多线程程序中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)多线程
int volatile var = 0;
int task1()
{
while (var == 0) { // do sth }
}
int task2()
{
var++;
}
我可以看到为什么编译器可能会错误地优化案例1中的while
,如果volatile
不存在,'因为变量是从硬件进行的,编译器可能看不到由代码创建的变量的任何更改。
但对于案例2)和3),为什么需要使用volatile?在这两种情况下,变量都声明为 global ,编译器可以看到它被使用了在不止一个地方。那么,如果变量不是while
,为什么编译器会优化volatile
循环呢?
是因为编译器副设计不知道"异步调用" (在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
限定符添加到变量中以避免在之前优化多线程程序?
答案 0 :(得分:9)
使用volatile
关键字的要点是防止编译器生成使用CPU寄存器的代码作为表示变量的更快方法。这会强制编译代码在每次访问时访问RAM中的确切内存位置,以获取可能已由另一个实体更改的最新值。通过添加volatile
,我们确保我们的代码知道任何其他人(如硬件或ISR)对变量所做的任何更改,并且不会发生一致性问题。
如果没有volatile
关键字,编译器会尝试通过将RAM中的变量内容读入CPU寄存器一次来生成更快的代码,并在循环或函数中使用该缓存值。访问RAM可能比访问CPU寄存器慢几十倍。
我已经有过第1项和第2项的经验,但我认为您不需要在多线程环境中将变量定义为volatile
。添加锁定/解锁机制对于解决同步问题是必要的,与volatile
的内容无关。
答案 1 :(得分:2)
编译器确实允许 else 更改变量,除非满足某些特定条件。其中一个是易失性访问;其他是一定的编译障碍。
您可能想到的编写多线程代码的天真方式确实容易出错,并且会被视为未定义的行为。如果你有正确的多线程代码,那么优化仍然是合法的(比如你的最终task1
,其中循环仍然是UB并且可能被抛出),或者同步原语将包含必要的障碍(通常在一些原子变量的内部)。
为了解决这个问题,这里是多线程示例的更正版本:
for (;;)
{
lock();
if (var != 0) { unlock(); break; }
unlock();
}
unlock()
函数的实现引入了一个编译器屏障,可确保无法优化循环。
答案 2 :(得分:2)
是因为编译器副设计不知道“异步调用”(在ISR情况下)或多线程?但这不可能,对吧?
是的,就是这样。
在C语言中,编译器没有并发概念,因此只要单个线程的视图无法注意到差异,就可以重新排序和缓存内存访问。
这就是为什么你需要volatile(阻止对变量进行这种优化),内存障碍(在所有变量的程序的单个点上阻止它)或其他形式的同步,如锁定(通常作为内存障碍) )。
答案 3 :(得分:0)
您可以使用障碍自由地避免多线程软件中的volatile变量。你可以在linux内核源代码中找到很多例子。使用障碍而不是volatile允许编译器生成更有效的代码。
答案 4 :(得分:0)
关于案例2),
我在你的问题中多次编写了与案例2相同的代码,并没有遇到任何问题。我认为这是因为现代编译器可以处理这种情况。比如,编译器可以“看到”我在“rx_isr”中更改“flag”,并且不添加任何优化。但是,由于以下原因,这是不安全的:
1)编译器的优化级别,这可能会影响以下原因3)
2)调用isr的方法,可能是函数指针超出了编译器的视图
3)编译器实现,不同的编译器可能有不同的定义“看到标志在isr中改变了”...
因此,为了最大限度地保证安全性和便携性,只需添加“volatile”。