mrkline wrote on Thursday, March 17, 2016:
FreeRTOS seems to take an… interesting approach to atomicity. Code in tasks.c
and list.h
use volatile
in an apparent attempt to make some code safe from compiler optimizations that would “break” said code during inopportune context switches (either to another task or to an interrupt “beneath” the OS). The smoking gun is the existence of configLIST_VOLATILE
and its comment,
The list structure members are modified from within interrupts, and therefore by rights should be declared volatile. However, they are only modified in a functionally atomic way (within critical sections of with [sic] the scheduler suspended) and are either passed by reference into a function or indexed via a volatile variable. Therefore, in all use cases tested so far, the volatile qualifier can be omitted in order to provide a moderate performance improvement without adversely affecting functional behaviour. The assembly instructions generated by the IAR, ARM and GCC compilers when the respective compiler’s options were set for maximum optimisation has been inspected and deemed to be as intended. That said, as compiler technology advances[…] it is feasible that the volatile qualifier will be needed for correct optimisation[…] If this is ever experienced then the volatile qualifier can be inserted in the relevant places within the list structures by simply defining configLIST_VOLATILE to volatile in FreeRTOSConfig.h (as per the example at the bottom of this comment block).
This, along with a linter note in tasks.c
that, “A manual analysis and inspection has been used to determine which static variables must be declared volatile.”, gives the impression that FreeRTOS authors believe declaring a variable volatile
makes it safe to use in multiple, concurrent contexts. This misconception is as dangerous as it is old. volatile
only guarantees that the compiler will not optimize away reads and writes to a given value, nor perform speculative reads ahead of time. It is only useful for accessing “special” addresses, such as MMIO. For lockless interaction between different contexts of execution, a set of ordering guarantees are also required, which volatile
simply does not provide.
This is so crucial to systems programming that the 2011 ISO standards for C and C++ spend great effort to define atomic semantics and the orderings needed (see http://en.cppreference.com/w/c/atomic). Unless I am mistaken, the only reason FreeRTOS doesn’t fall apart during untimely context switches is because most platforms which run it are single-core microprocessors where a write to an address is not buffered or cached in some way. In these cases, volatile
has been “good enough” to stop breaking optimizations. However, this is a very poor safety net. Some architectures supported by FreeRTOS have instructions needed to properly order lockless code. (see ARM’s DSB
). These are emitted by C11 atomic reads and writes, but not by volatile
. And as the comments for configLIST_VOLATILE
note, optimizers could quickly ruin the assumptions FreeRTOS makes about these accesses.
The obvious solution would be to use the C11 facilities for variables shared locklessly across several contexts of execution, but given that FreeRTOS supports many compilers (several of which can be charitably described as “stable” and uncharitably described as “archaic”), a different approach may be needed. One pre-C11 approach is to create port-specific inline functions or macros that perform various atomic operations (load, store, increment, decrement, compare and swap, etc.). This is the approach taken by Linux. At any rate, why is volatile
being used in these places?