I'm writing a code for Cortex-M0 CPU and gcc. I've the following structure:
struct {
volatile unsigned flag1: 1;
unsigned flag2: 1;
unsigned foo; // something else accessed in main loop
} flags;
flag1
is read and written from both GPIO interrupt handler and main loop. flag2
is only read and written in main loop.
The ISR looks like this:
void handleIRQ(void) {
if (!flags.flag1) {
flags.flag1 = 1;
// enable some hw timer
}
}
The main loop looks like this:
for (;;) {
// disable IRQ
if (flags.flag1) {
// handle IRQ
flags.flag1 = 0;
// access (rw) flag2 many times
}
// wait for interrupt, enable IRQ
}
When accessing flag2
in main loop, will the compilier optimize access to it so it won't be fetched or stored to memory every time it is read or written to in code?
It's not clear to me because to set flag1
in ISR, it will need to load whole char
, set a bit and store it back.
It is my reading of the C11 standard that it is not proper to use a bitfield for this - even if both of them were declared as volatile
. The following excerpt is from 3.14 Memory location:
- Memory location
Either an object of scalar type, or a maximal sequence of adjacent bit-fields all having nonzero widthNOTE 1 Two threads of execution can update and access separate memory locations without interfering with each other.
NOTE 2 It is not safe to concurrently update two non-atomic bit-fields in the same structure if all members declared between them are also (non-zero-length) bit-fields, no matter what the sizes of those intervening bit-fields happen to be.
There is no exception given for volatile
. Thus it wouldn't be safe to use the above bitfield if both threads of execution (i.e. the main and the ISR) if ISR will update one flag and the main will update another. The solution given is to add a member of size 0 in between to force them be placed in different memory locations. But then again, it would mean that both flags would consume at least one byte of memory, so it is again just simpler to use a non-bit-field unsigned char
or bool
for them:
struct {
volatile bool flag1;
bool flag2;
unsigned foo; // something else accessed in main loop
} flags;
Now they will be placed in different memory locations and they can be updated without them interfering with each other.
However the volatile
for flag1
is still strictly necessary because otherwise updates to flag1
would be side-effect free in the main thread, and the compiler could deduce that it can keep that field in a register only - or that nothing need to be updated at all.
However, one needs to note that under C11, even the guarantees of volatile
might not be enough: 5.1.2.3p5:
When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects that are neither lock-free atomic objects nor of type volatile sig_atomic_t are unspecified, as is the state of the floating-point environment. The value of any object modified by the handler that is neither a lock-free atomic object nor of type volatile sig_atomic_t becomes indeterminate when the handler exits, as does the state of the floating-point environment if it is modified by the handler and not restored to its original state.
Thus, if full compatibility is required, flag1
ought to be for example of type volatile _Atomic bool
; it might even be possible to use an _Atomic
bitfield. Both of these require a C11 compiler, however.
Then again, you can check the manuals of your compiler if they guarantee that an access to such volatile objects is also guaranteed to be atomic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With