1

I am making embedded firmware where everything after initialization happens in ISRs. I have variables that are shared between them, and I am wondering in which cases they need to be volatile. I never block, waiting for a change in another ISR.

When can I be certain that actual memory is read or written to when not using volatile? Once every ISR?

Addendum:
This is for ARM Cortex-M0, but this isn't really a question about ISRs as much as it is about compiler optimization, and as such, the platform shouldn't really be important.

oyvind
  • 1,429
  • 3
  • 14
  • 24
  • I suspect this is not answerable without specification of the supporting O/S, and or other information about the context in which the ISRs operate. Even with that information, I won't be able to help you. – Jonathan Leffler Oct 04 '14 at 01:44
  • Since standard C has nothing to say about such things, the whole issue depends entirely (from my perspective) on the platform and what the platform (compiler plus o/s plus h/w) does. Is the code single-threaded or multi-threaded; how many cores in the CPU; what common resources to the ISRs modify (in particular). Shared read-only resources are probably OK — though if the reading is destructive because it is collecting data from some sort of register, you have to treat it with the same care as if you're writing. I admit that the last time I did anything resembling this was 30 years ago. – Jonathan Leffler Oct 04 '14 at 01:56
  • I am trying to understand compiler optimization, in broad strokes, so I hope the answer to my question doesn't vary wildly between platforms, as I suppose the basic principles behind optimization of variables are mostly the same between compilers. For example, can I be sure that a global variable is written to memory at least once if it is written to in code? – oyvind Oct 04 '14 at 02:04
  • 1
    In agreement with @JonathanLeffler, there is not enough information presented for a definitive answer. Given that, take a look at this: http://www.embedded.com/electronics-blogs/beginner-s-corner/4023801/Introduction-to-the-Volatile-Keyword As always, GIYF (google is your friend), there is lots of material out there that can help. – Throwback1986 Oct 04 '14 at 02:05
  • No - you can't rely on the scheduling of writes to global variables relative to other operations. It'll happen eventually, but not necessarily immediately (where eventually might be a few instructions later, or it could be longer). It depends in part on whether you've got mutual exclusion in effect; caches; whether there are multiple threads around; whether there are higher-priority interrupts that can interfere with the current one; whether you told the compiler that the variable was `volatile`. You could slap `volatile` on everything; it isn't a good way of programming, though. – Jonathan Leffler Oct 04 '14 at 02:05
  • `volatile` in embedded system, is majorly used to describe a register and it's essential. sometimes, you found you have to do `reg|=0x1;reg&=~0x1;` and it makes sense. without `volatile`, this will be optimized to no code, unless you turn off the optimizer, which sometimes you won't be happy to do that. to simple memory IO, i don't see the reason. – Jason Hu Oct 04 '14 at 02:39

3 Answers3

4

The question is entirely answerable, and the answer is simple:

Without volatile you (simplistically) can't assume that actual memory access will ever happen - the compiler is free to conclude that results are either entirely unused (if that is apparent in what it can see), or that they may be safely cached in a register, or computed out of order (as long as visible dependencies are maintained).

You need volatile to tell the compiler that the side effects of access may be important to something the optimizer is unable to analyze, such as an interrupt context or the context of a different "thread".

In effect volatile is how you say to the compiler "I know something you don't, so don't try to be clever here"

Beware that volatile does not guarantee atomicity of read-modify-write, or of even read or write alone where the data type (or its misalignment) requires muti-step access. In those cases, you risk not just getting a stale value, but an entirely erroneous one.

Chris Stratton
  • 39,853
  • 6
  • 84
  • 117
1

it is already mentioned that actual write to memory/cache is not exactly predictable when using non volatile variables. but it is also worth mentioning about another aspect where the volatile variable might get cached and might require a forced cache flush to write in to the actual memory ( depends on whether a write-through or a write-back policy is used).

consider another case where the volatile variable is not cached ( placed in non-cacheable area) but due to the presence of write buffers and bus bridges sometimes it is not predictable when the real write happens to the intended register and it requires a dummy read to ensure that write actually happened to the real register/memory. This is particularly helpful to avoid race conditions in interrupt clearing/masking.

even though compilers are not supposed to be clever around volatile variables.it is free to do some optimizations with respect to volatile sequence points ( optimization across sequence points not permitted, but optimization between sequence points are permitted)

bare_metal
  • 1,134
  • 9
  • 20
  • CPU caches aren't a problem for sharing data between an ISR and the thread being interrupted. Or in practice with any other thread, because [caches are coherent across the cores that you'd run threads on](https://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading/58535118#58535118). `volatile` just makes a load or store instruction happen in the asm, it doesn't also trigger cache-flushing instructions like you'd need on a heterogeneous ARM board with non-coherent shared cache between a DSP and microcontroller, for example. – Peter Cordes Jan 05 '23 at 12:42
  • Your example of needing a dummy read could make sense to drain a write buffer for an I/O access, though. – Peter Cordes Jan 05 '23 at 12:44
0

The variables that need volatile are:

1) Share data between ISR and program data or other threads.
Preferable these are flags that indicate block access to various data structures.

// main() code;
disable_interrupts();
if (flag == 0) {
  flag = 1;
  enable_interrupts();
  Manipulate_data();
  flag = 0;
} else {
  enable_interrupts();
  Cope_with_data_unavailable();
}

2) Memory mapped hardware registers.
This "memory" can change at any time due to hardware conditions and the complier needs to know that their values are not necessarily consistent. Without volatile, a naive comapiler would only sample fred once resulting in a potential endless loop.

volatile int *fred = 0x1234; // Hardware reg at address 0x1234;
while (*fred);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256