19.3.2.1 Sharing Data Between Different Threads of Execution

Sharing data between different threads of execution (such as between mainline code and an interrupt service routine or between two different threads in a Realtime Operating System like environment) can sometimes be complex.

Make sure that any objects that may be shared in this way are marked as volatile (both read or write sharing). volatile instructs the compiler to honor all accesses to memory, which will prevent the compiler from caching a value in a register. If the variable is shared, then this is a good thing! The compiler needs to know that the variable might change because of a hardware or other external event, such as in this example where we wish to wait for the buffer to have some data in it before progressing:

IOPORT.buffer_emtpy = 1; 
while (IOPORT.buffer_empty);

If the object is not marked as volatile when optimizing the compiler, then it might determine that the value will never change and do something horrible to the loop or worse. Consider the rest of the code to be unreachable and replace the expression with while(1);.

There are times however, when volatile is not enough. The compiler may not be able to compute an expression without going through an intermediate register. This means there may be a window of time when a value is stored in a register while the new value is being computed. If it is marked as volatile, then it will be written back. This could be the source of data corruption, especially if the object is a single memory location that has many separate data values like a C bitfield. In the following structure example, status is a flag set by some external process and blinky is a heartbeat in the mainline code.

volatile struct corruptable {
  uint16_t status:3;
  uint16_t blinky:1;
} object;

...

while (object.status != 0) {
  object.blinky ^= 1;
}

If the compiler has not been able to generate an atomic, uninterruptable sequence to XOR blinky then this can be a possible source of corruption. Consider the flow where status is updated but the blinky update is not complete. Writing back the new value of blinky, which shares a word with status, might over-write the possibly new value of status causing the generated code to never see when status has been updated.

If your code is similar to the above example, you can see that volatile is not a sufficient solution. Consider coding styles that will prevent this overwrite from occurring, such as not sharing memory in that way and the use of critical-sections to control access of shared data. Often it is efficient and clearer to make use of the object-oriented principal of accessor functions where the access of each object is tightly controlled in one place. A well-defined gating of shared data can allow the code to be written without using volatile at all, thus allowing the code to be safe and share data efficiently.