问题
I read about usage of C volatile
keyword in memory-mapped hardware register, ISR, and multithreaded program.
1) register
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) multithread
int volatile var = 0;
int task1()
{
while (var == 0) { // do sth }
}
int task2()
{
var++;
}
I can see why compiler can mistakenly optimize the while
in case 1) if volatile
is not there, 'cause variable change is made from hardware, compiler may not see any change of the variable made from code.
But for case 2) and 3), why is volatile ever needed? In both cases variable is declared global, and compiler can see it's used in more than one place. So why would compiler optimize the while
loop if the variable is not volatile
?
Is it because a compiler by-design has no idea of "asynchronous call" (in case of ISR), or multithreading? But this can't be, right?
In addition, case 3) looks like a common program in multithreading without the volatile
keyword. Let's say I add some locking to the global variable (no volatile
keyword):
int var = 0;
int task1()
{
lock(); // some mutex
while (var == 0) { do sth }
release()
}
int task2()
{
lock();
var++;
release();
}
It looks normal enough to me. So do I really need volatile
in multithreading? How come I've never seen volatile
qualifier added to variable to avoid optimization in multithread program before?
回答1:
The main point of using volatile
keyword is to prevent compiler from generating a code that uses CPU registers as faster ways to represent variables. This forces compiled code to access the exact memory location in RAM on every access to the variable to get the latest value of it which may have been changed by another entity. By adding volatile
we make sure that our code is aware of any change made to a variable by anyone else like hardware or ISR and no coherency issue happens.
In absence of volatile
keyword, compiler tries to generate faster code by reading the content of variable from RAM into a CPU register once and use that cached value in a loop or function. Accessing RAM could be tens of times slower than accessing the CPU register.
I've had the experience on item 1 and 2 but I don't think you need to define a variable as volatile
in a multi threded environment. Adding the lock/unlock mechanism is necessary to solve synchronization problem and is not related the what volatile
is about.
回答2:
The compiler is indeed to allow that nothing else changes your variables unless some every specific conditions are met. One of them is volatile access; others are certain compiler barriers.
The naive way to program multithreaded code which you may have in mind is indeed prone to errors and would be considered undefined behaviour. If you have correct multithreaded code, then either an optimisation is still legal (like in your final task1
, where the loop is still UB and may be thrown out), or the synchronisation primitives will contain the necessary barriers (usually in the guts of some atomic variables).
To round things up, here's a corrected version of the multithreaded example:
for (;;)
{
lock();
if (var != 0) { unlock(); break; }
unlock();
}
The implementation of the unlock()
function introduces a compiler barrier which ensures that the loop cannot be optimized away.
回答3:
Is it because a compiler by-design has no idea of "asynchronous call" (in case of ISR), or multithreading? But this can't be, right?
Yes, it is that way.
In C the compiler has no notion of concurrency, so it is allowed to reorder and cache memory accesses, as long as the view from a single thread can't notice the difference.
That's why you need volatile (block this kind of optimizations for a variable), memory barriers (block it at a single point of the program for all variables) or other forms of synchronization such as locking (which typically act as memory barriers).
回答4:
You can freely avoid volatile variables in multi-threaded software by using barriers. You can find many examples in linux kernel sources. Also using barriers instead of volatile allow compiler to generate much more efficient code.
回答5:
As for case 2),
I have written the same code as case 2) in your question many times, and did NOT meet any problems. I think this is because the modern compiler can handle this situation. Say, the compiler can "see" I change "flag" inside "rx_isr", and do not add any optimization. However, this is insecure due to the following reasons:
1) optimization level of your compiler, which may affect the following reason 3)
2) calling method of your isr, may be a function pointer is out of compiler's view
3) compiler implementation, different compiler may have different definition of "see flag changed in isr"
...
So, to be secure and portable to the maximum extent, just add "volatile".
来源:https://stackoverflow.com/questions/12738672/c-volatile-keyword-in-isr-and-multithreaded-program