Volatile and its harmful effects

I am an embedded developer and use the volatile keyword when working with I / O ports. But my project manager suggested using the volatile keyword is harmful and has many backlinks, but I find in most cases volatile useful in embedded programming. According to my knowledge, volatile is harmful in kernel code, since changes in our code will be unpredictable. Are there any flaws associated with volatility in Embedded Systems?

+5
source share
5 answers

No, volatile not harmful. In any situation. Ever. It is impossible to create a well-formed piece of code that breaks with the addition of volatile to the object (and pointers to this object). However, volatile often poorly understood . The reason the docs kernel claims that volatile should be considered harmful is because people constantly used it to synchronize between kernel threads. In particular, they used volatile integer variables, as if access to them was guaranteed to be atomic, but this is not so.

volatile also not useless, and especially if you go bare metal, you need it. But, like any other tool, it is important to understand the semantics of volatile before using it.

What is volatile

Access to volatile objects in the standard is considered a side effect in the same way as incrementing or decreasing by ++ and -- . In particular, this means that 5.1.2.3 (3), which states that

(...) An actual implementation should not evaluate part of an expression if it can infer that its value is not used and that necessary side effects are not created (including those caused by a function call or access to a mutable object)

not applicable. The compiler should throw out everything that it thinks about the value of the volatile variable at each point in the sequence. (like other side effects when accessing volatile objects using sequence points)

The effect of this is largely a prohibition of certain optimizations. Take for example the code

 int i; void foo(void) { i = 0; while(i == 0) { // do stuff that does not touch i } } 

The compiler is allowed to do this in an infinite loop, which never checks i again, because it can infer that the value of i does not change in the loop, and therefore i == 0 will never be false. This is true even if there is another thread or interrupt handler that could apparently change i . The compiler does not know about them, and anyway. I obviously have nothing to worry about.

Contrast this with

 int volatile i; void foo(void) { i = 0; while(i == 0) { // Note: This is still broken, only a little less so. // do stuff that does not touch i } } 

Now the compiler should assume that i can change at any time and cannot do this optimization. This means, of course, that if you are dealing with interrupt handlers and threads, volatile objects are needed for synchronization. However, they are not sufficient.

What volatile not

What volatile does not guarantee is atomic access. This should make an intuitive sense if you are used to embedded programming. Consider the following code snippet for an 8-bit AVR MCU if you want:

 uint32_t volatile i; ISR(TIMER0_OVF_vect) { ++i; } void some_function_in_the_main_loop(void) { for(;;) { do_something_with(i); // This is thoroughly broken. } } 

The reason this code is broken is because access to i not atomic - it cannot be atomic on an 8-bit MCU. In this simple case, for example, the following may happen:

  • i 0x0000ffff
  • do_something_with(i) will be called
  • the top two bytes i copied to the parameter slot for this call
  • at this moment, timer 0 overflows and interrupts the main loop
  • ISR i changes. The bottom two bytes of overflow i and now 0 . i now 0x00010000 .
  • the main loop continues, and the bottom two bytes i copied to the parameter slot
  • do_something_with is called with parameter 0 .

Similar things can happen on PCs and other platforms. In any case, more possibilities it may not open with a more complex architecture.

Removal

Thus, no, using volatile not bad, and you (often) will have to do this in code without codes. However, when you use it, you should keep in mind that this is not a magic wand, and you still have to make sure that you are not traveling on your own. In embedded code, there is often a platform-specific way to solve the atomicity problem; in the case of AVR, for example, the usual scrap method is to disable interruptions for a duration, as in

 uint32_t x; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { x = i; } do_something_with(x); 

... where the ATOMIC_BLOCK macro calls cli() (disable interrupts) before and sei() (enable interrupts) after they were turned on in advance.

With C11, which is the first C standard that explicitly recognizes the existence of multithreading, a new family of atomic types and memory fencing operations has been introduced that can be used for cross-thread synchronization and, in many cases, do not use volatile . If you can use them, do it, but it is likely to take some time before they reach all the common built-in toolchains. With them, the loop above can be fixed as follows:

 atomic_int i; void foo(void) { atomic_store(&i, 0); while(atomic_load(&i) == 0) { // do stuff that does not touch i } } 

... in its most basic form. The exact semantics of the more relaxed semantics of the memory order are beyond the scope of the SO answer, so I will stick to the standard sequentially consistent material here.

If you're interested in this, Gil Hamilton provided a link in the comments to explaining the implementation of the lock stack using C11 atomicity, although I don't feel like this is an awfully good record of the memory order of semantics. However, the C11 model seems to closely reflect the C ++ 11 memory model, from which a useful view exists here . If I find a link to a C11-specific entry, I will put it here later.

+21
source

volatile is only useful when such a qualified object can change asynchronously. Such changes may occur.

  • if the object is actually a hardware I / O register or similar that has changes external to your program.
  • if the object can be modified by the signal handler
  • if the object changes between calls to setjmp and longjmp

in all these cases, you must declare your volatile object, otherwise your program will not work correctly. (And you may have noticed that objects shared between different threads are not on the list.)

In all other cases, you should not, because you may not be able to optimize. On the other hand, selecting a volatile object that does not fall under the above points will not make your code incorrect.

+2
source

Volatile tells the compiler not to optimize anything related to a mutable variable.

Why should a volatile class not be used? - Best article in Kernel document

https://www.kernel.org/doc/Documentation/volatile-considered-harmful.txt

+1
source

Do not use volatile , where necessary and appropriate, it is much more likely that it will be harmful! The solution to any problems associated with volatile should not prohibit its use, since there are a number of cases where it is necessary for safe and correct semantics. Rather, the solution is to understand its purpose and its behavior.

This is important for any data that can be changed outside the compiler’s knowledge, such as I / O and dual-port or DMA memory. It is also necessary to access memory shared between execution contexts, such as threads and interrupt handlers; this is where there may be confusion; it provides an explicit reading of such memory and does not require the use of atomicity or mutual exclusion - this requires additional mechanisms, but this does not exclude volatile , but it is just part of the solution for accessing shared memory.

See the following articles for using volatile (and send them to the project manager too!):

+1
source

volatile is a keyword in c that tells the compiler that it does not do any optimization for this variable.

Let me give you a simple example:

 int temp; for ( i=0 ;i <5 ; i++ ) { temp = 5; } 

which compiler will do to optimize the code:

 int temp; temp = 5; /* assigned temp variable before the loop. */ for ( i=0 ;i <5 ; i++ ) { } 

But if we specify the volatile keyword, then the compiler will not do any optimization in the temp variable.

 volatile int temp; for ( i=0 ;i <5 ; i++ ) { temp = 5; } 

"Volatile is considered harmful" ---> I do not consider volatile as harmful. You use volatile where you do not want any optimization to be from the end of the compiler.

For example, consider that this piece of code is used by a thermometer company, and temp is a variable used to measure atmospheric temperature, which can change at any time. Therefore, if we do not use volatile, then the compiler will perform the optimization, and the temperature of the atmosphere will always be the same.

0
source

Source: https://habr.com/ru/post/1210389/


All Articles