Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

embedded C - using "volatile" to assert consistency

Consider the following code:

// In the interrupt handler file:
volatile uint32_t gSampleIndex = 0; // declared 'extern'
void HandleSomeIrq()
{
    gSampleIndex++;
}

// In some other file
void Process()
{
    uint32_t localSampleIndex = gSampleIndex;   // will this be optimized away?
    PrevSample      = RawSamples[(localSampleIndex + 0) % NUM_RAW_SAMPLE_BUFFERS];
    CurrentSample   = RawSamples[(localSampleIndex + 1) % NUM_RAW_SAMPLE_BUFFERS];
    NextSample      = RawSamples[(localSampleIndex + 2) % NUM_RAW_SAMPLE_BUFFERS];
}

My intention is that PrevSample, CurrentSample and NextSample are consistent, even if gSampleIndex is updated during the call to Process().

Will the assignment to the localSampleIndex do the trick, or is there any chance it will be optimized away even though gSampleIndex is volatile?

like image 411
bavaza Avatar asked Nov 12 '14 11:11

bavaza


1 Answers

In principle, volatile is not enough to guarantee that Process only sees consistent values of gSampleIndex. In practice, however, you should not run into any issues if uinit32_t is directly supported by the hardware. The proper solution would be to use atomic accesses.

The problem

Suppose that you are running on a 16-bit architecture, so that the instruction

localSampleIndex = gSampleIndex;

gets compiled into two instructions (loading the upper half, loading the lower half). Then the interrupt might be called between the two instructions, and you'll get half of the old value combined with half of the new value.

The solution

The solution is to access gSampleCounter using atomic operations only. I know of three ways of doing that.

C11 atomics

In C11 (supported since GCC 4.9), you declare your variable as atomic:

#include <stdatomic.h>

atomic_uint gSampleIndex;

You then take care to only ever access the variable using the documented atomic interfaces. In the IRQ handler:

atomic_fetch_add(&gSampleIndex, 1);

and in the Process function:

localSampleIndex = atomic_load(gSampleIndex);

Do not bother with the _explicit variants of the atomic functions unless you're trying to get your program to scale across large numbers of cores.

GCC atomics

Even if your compiler does not support C11 yet, it probably has some support for atomic operations. For example, in GCC you can say:

volatile int gSampleIndex;
...
__atomic_add_fetch(&gSampleIndex, 1, __ATOMIC_SEQ_CST);
...
__atomic_load(&gSampleIndex, &localSampleIndex, __ATOMIC_SEQ_CST);

As above, do not bother with weak consistency unless you're trying to achieve good scaling behaviour.

Implementing atomic operations yourself

Since you're not trying to protect against concurrent access from multiple cores, just race conditions with an interrupt handler, it is possible to implement a consistency protocol using standard C primitives only. Dekker's algorithm is the oldest known such protocol.

like image 173
jch Avatar answered Sep 25 '22 16:09

jch