ISR keyword 'volatile' and multithreaded program?

I read about using the C volatile keyword in a hardware memory 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) multithreading

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

I can understand why the compiler can mistakenly optimize while in case 1), if volatile does not exist, because the change of the variable is made from hardware, the compiler may not see the change of the variable made from the code.

But for case 2) and 3), why are volatiles ever needed? In both cases, the variable is declared global, and the compiler can see that it is used in several places. So, why does the compiler optimize the while if the variable is not volatile ?

Is it because by default the compiler does not have the concept of asynchronous call (in the case of ISR) or multithreading? But this cannot be, right?

In addition, case 3) looks like a general program in multithreading without the volatile keyword. Let's say I add some lock to a global variable (the volatile keyword):

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

For me it looks fine. So do I really need volatile in multithreading? Why have I never seen a qualifier added to a variable to avoid optimization in a multi-threaded program before ?

+7
source share
5 answers

The main point of using the volatile keyword is to prevent the compiler from generating code that uses processor registers as faster ways to represent variables. This forces the compiled code to access the exact memory location in RAM each time the variable is accessed to get the last value that can be changed by another object. By adding volatile , we guarantee that our code will be aware of any changes made to the variable by someone else, such as hardware or ISR, and that there is no consistency problem.

In the absence of the volatile keyword, the compiler tries to generate faster code by reading the contents of the variable from RAM to the CPU register once and using this cached value in a loop or function. Access to RAM can be tens of times slower than access to the CPU register.

I had experience with elements 1 and 2, but I don’t think you need to define a variable as volatile in a multi-threaded environment. Adding a lock / unlock mechanism is necessary to solve the synchronization problem and is not related to the fact that volatile .

+9
source

The compiler really allows nothing else to change your variables if any specific conditions are not met. One of them is unsustainable access; others are specific compiler barriers.

The naive way of programming multi-threaded code, which you may have in mind, is really error prone and will be considered as undefined behavior. If you have the correct multi-threaded code, then either optimization is still legal (as in your final task1 , where the loop is still UB and can be thrown away), or synchronization primitives will contain the necessary barriers (usually in the guts of some atomic variables).

To get around everything, the version of the multithreaded example is fixed here:

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

The implementation of the unlock() function introduces a compiler barrier that ensures that the loop cannot be optimized.

+2
source
Is this due to the fact that by default the compiler does not have the concept of asynchronous call (in the case of ISR) or multithreading? But this cannot be, can it?

Yes it is.

In C, the compiler does not have the concept of concurrency, so it is allowed to reorder and cache memory accesses if the view from one thread cannot notice the difference.

To do this, you need volatility (block these types of optimizations for a variable), memory barriers (block them at one point in the program for all variables), or other forms of synchronization, such as blocking (which usually act as memory barriers).

+1
source

You can freely avoid volatile variables in multithreaded software by using barriers. You can find many examples in Linux kernel sources. Also, using barriers instead of volatile allows the compiler to generate much more efficient code.

0
source

As in case 2),

I wrote the same code as case 2) in your question many times, and have NOT encountered any problems. I think this is due to the fact that the modern compiler can handle this situation. Say the compiler can “see” change “flag” inside “rx_isr” and do not add any optimization. However, this is unsafe for the following reasons:

1) the level of optimization of your compiler, which may affect the following reason: 3)

2) the method for calling your isr can be a function pointer from the compiler view

3) compiler implementation, another compiler may have a different definition of "see flag changed in isr"

...

So, to be safe and portable as much as possible, just add “volatile”.

0
source

All Articles