10

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?

embedded.kyle
  • 10,976
  • 5
  • 37
  • 56
user1559625
  • 2,583
  • 5
  • 37
  • 75
  • 1
    Note that the last `task1` has undefined behaviour. – Kerrek SB Oct 05 '12 at 02:35
  • Give the various responses to http://stackoverflow.com/questions/72552/why-does-volatile-exist a read – D.Shawley Oct 05 '12 at 02:40
  • hi Shawley, actually i saw the post. The question i asked is on a small, specific subject of the topic that i do not understand. I need to read more on the subject i am sure. – user1559625 Oct 05 '12 at 04:16

5 Answers5

10

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.

Kamyar Souri
  • 1,871
  • 19
  • 29
  • hi, Kamyar, i thought the same thing on the 1st two paragraph you wrote, but for item 2, i do not understand why compiler can't see that variable is changed in ISR in addition to main(). Is compiler doing optimization only based on scope and call stack? so it can not see the change in ISR? – user1559625 Oct 05 '12 at 05:24
  • 1
    It is very possible that the compiler has no understanding of how the isr handler is called, especially if it is used as a function pointer is some vector table. – Alexandre Vinçon Oct 05 '12 at 05:54
  • 4
    In addition, 'volatile' keyword prevents the compiler from optimizing consecutive read or write operations to the variable. For ex: flag = 1; flag = 2; Without 'volatile' keyword, the compiler would optimize the code by ignoring the first assignment. But when you write to hardware registers, writing multiple value to the same register makes sense and no step must be skipped. The other way around, reading 'flag' several times in a row can make sense when reading from some hardware buffer (ex: serial port). There again, the 'volatile' keyword prevents optimization. – Alexandre Vinçon Oct 05 '12 at 05:59
  • Alex is right. Compiler may be able to detect an ISR in the code, but leaves the option to programmer to use/not use volatile. – Kamyar Souri Oct 05 '12 at 14:23
  • 2
    The comment about multithreading here is erroneous. Often in a multithreaded setting you do need a lock, however, you also need to declare the variable volatile. The lock is to make sure you don't see a multi-access-word-width variable "partially changed", while the volatile is to make sure that the memory value actually gets changed. – Chris Stratton Aug 26 '18 at 16:44
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).

Community
  • 1
  • 1
starblue
  • 55,348
  • 14
  • 97
  • 151
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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Sorry if my question is not clear, i edit it again with some highlight, and add 'do sth' in while loop, i did not know if it cause misunderstanding before. Could you take a look of the question? And i still do not understand why final task1 has undefined behavior like you said, sorry about it. – user1559625 Oct 05 '12 at 04:30
  • @user1559625: Check out 1.10/24. Basically, you mustn't have infinite loops, unless the loop performs a volatile access, an atomic synchronisation, or a library I/O call. – Kerrek SB Oct 05 '12 at 11:06
0

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.

vitaly.v.ch
  • 2,485
  • 4
  • 26
  • 36
  • `volatile` is a standard C keyword, and in C versions prior to C11 barriers were not part of the standard. But you are right that a careful usage of barriers might allow a skilled programmer to safe a couple of instructions, at the potential expense of readability and portability. – vgru Jan 30 '19 at 11:22
0

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".

  • *" I think this is because the modern compiler can handle this situation."* - with optimizations enabled, most modern compilers will replace case 2 with an [infinite loop](https://godbolt.org/z/qRRri9). The code in other function is irrelevant, omitting `volatile` in this case would be programmer's mistake. – vgru Jan 30 '19 at 11:14