Even I write software for embedded systems for more than 10 years, there's an argument that from time to time let me think for hours and leave me with many doubts.
Consider a simple embedded system based on a MCU (AVR8 or Cortex-Mx). The software is bare metal, without any OS. The main pattern is the well known mainloop (background code) that is interrupted by ISR.
Interrupts are used mainly for timings and for low-level driver. For example, the UART reception ISR move the last received char in a FIFO buffer, while the mainloop code pops new data from the FIFO.
static struct { unsigned char buf[RXBUF_SIZE]; uint8_t in; uint8_t out; } rxfifo;
/* ISR */ void uart_rx_isr(void) { unsigned char c = UART->DATA; rxfifo.buf[in % RXBUF_SIZE] = c; rxfifo.in++; // Reset interrupt flag }
/* Called regularly from mainloop code */ int uart_task(void) { int c = -1; if (out != in) { c = rxfifo.buf[out % RXBUF_SIZE]; out++; } return -1; }
From a 20-years old article[1] by Nigle Jones, this seems a situation where volatile must be used for rxfifo.in, because is modified by an ISR and used in the mainloop code.
I don't think so, rxfifo.in is read from memory only one time in uart_task(), so there isn't the risk that compiler can optimize badly. Even if ISR is fired immediately after the if statement, this doesn't bring to a dangerous state: the just received data will be processed at the next call to uart_task().
So IMHO volatile isn't necessary here. And critical sections (i.e. disabling interrupts) aren't useful too.
However I'm thinking about memory barrier. Suppose the compiler reorder the instructions in uart_task() as follows:
c = rxfifo.buf[out % RXBUF_SIZE] if (out != in) { out++; return c; } else { return -1; }
Here there's a big problem, because compiler decided to firstly read rxfifo.buf[] and then test in and out equality. If the ISR is fired immediately after moving data to c (most probably an internal register), the condition in the if statement will be true and the register value is returned. However the register value isn't correct.
I don't think any modern C compiler reorder uart_task() in this way, but we can't be sure. The result shouldn't change for the compiler, so it can do this kind of things.
How to fix this issue if I want to be extremely sure the compiler will not reorder this way? Applying volatile to rxfifo.in shouldn't help for this, because compiler is allowed to reorder access of non volatile variables yet[2].
One solution is adding a memory barrier in this way:
int uart_task(void) { int c = -1; if (out != in) { memory_barrier(); c = rxfifo.buf[out % RXBUF_SIZE]; out++; } return -1; }
However this approach appears to me dangerous. You have to check and double check if, when and where memory barriers are necessary and it's simple to skip a barrier where it's nedded and add a barrier where it isn't needed.
So I'm thinking that a sub-optimal (regarding efficiency) but reliable (regarding the risk to skip a barrier where it is needed) could be to enter a critical section (disabling interrupts) anyway, if it isn't strictly needed.
int uart_task(void) { ENTER_CRITICAL_SECTION(); int c = -1; if (out != in) { c = rxfifo.buf[out % RXBUF_SIZE]; out++; } EXIT_CRITICAL_SECTION(); return -1; }
Another solution could be to apply volatile keyword to rxfifo.in *AND* rxfifo.buf too, so compiler can't change the order of accesses them.
Do you have other suggestions?
[1]