0

Suppose having the following code elements working on a fifo buffer:

static uint_fast32_t buffer_start;
static uint_fast32_t buffer_end;
static mutex_t buffer_guard;

(...)

void buffer_write(uint8_t* data, uint_fast32_t len)
{
  uint_fast32_t pos;

  mutex_lock(buffer_guard);

  pos = buffer_end;
  buffer_end = buffer_end + len;

  (...) /* Wrap around buffer_end, fill in data */

  mutex_unlock(buffer_guard);
}

bool buffer_isempty(void)
{
  bool ret;
  mutex_lock(buffer_guard);
  ret = (buffer_start == buffer_end);
  mutex_unlock(buffer_guard);
  return ret;
}

This code might be running on an embedded system, with a RTOS, with the buffer_write() and buffer_isempty() functions called from different threads. The compiler has no means to know that the mutex_lock() and mutex_unlock() functions provided by the RTOS are working with a critical sections.

As the code is above, due to buffer_end being a static variable (local to the compilation unit), the compiler might choose to reorder accesses to it around function calls (at least as far as I understand the C standard, this seems possible to happen). So potentially the code performing buffer_end = buffer_end + len line have a chance to end up before the call to mutex_lock().

Using volatile on these variables (like static volatile uint_fast32_t buffer_end;) seems to resolve this as then they would be constrained by sequence points (which a mutex_lock() call is, due to being a function call).

  • Is my understanding right on these?
  • Is there a more appropriate means (than using volatile) of dealing with this type of problem?
Jubatian
  • 2,171
  • 16
  • 22
  • 1
    `mutex_lock`/`mutex_unlock` are likely macros that add memory barriers. So the side effects should be visible whenever "crossing" those locks. See https://stackoverflow.com/questions/24137964/does-pthread-mutex-lock-contains-memory-fence-instruction – tstanisl May 04 '22 at 11:59
  • 1
    Does this answer your question? [Does pthread\_mutex\_lock contains memory fence instruction?](https://stackoverflow.com/questions/24137964/does-pthread-mutex-lock-contains-memory-fence-instruction) – tstanisl May 04 '22 at 12:03
  • 1
    If you are building firmware for a microcontroller, then I would strongly advise you to build it using the tools provided by the hardware vendor. You can't make those things work without invoking what is now called "undefined behavior" by international language standards. Use the vendor's tools, follow the vendor's examples and rules, and trust the vendor to ensure that the right thing will happen. – Solomon Slow May 04 '22 at 13:32
  • P.S., On a single-processor MCU, for something as simple as a circular buffer, consider just disabling interrupts instead of using a mutex lock. – Solomon Slow May 04 '22 at 13:34
  • Rather than `volatile`, you probably would want to use atomic variables: https://stackoverflow.com/questions/25319825/how-to-use-atomic-variables-in-c – Jeremy Friesner May 04 '22 at 13:49
  • @tstanisl By that another question's wording, I am interested in complier barriers here, how to properly ensure that the compiler doesn't reorder accesses to those static variables around the mutex operation. Actually working on an ESP32, there these operations are function calls without any specific instruction regulating the compiler (example: https://github.com/espressif/esp-idf/blob/a470ae224d2479cbb1019a0b69490a1249e6a562/components/freertos/FreeRTOS-Kernel/include/freertos/semphr.h#L319 ) – Jubatian May 04 '22 at 15:49
  • @SolomonSlow We do use the vendor's tools, though as far as our experience goes with them, better be safe than sorry. – Jubatian May 04 '22 at 15:53
  • @JeremyFriesner Not sure they are even supported in our environment, and not sure whether it wouldn't be an overkill already in a critical section (as long as it is not possible to come up with a design which could leave the mutex, relying on atomicity and order with the atomic variables). – Jubatian May 04 '22 at 15:58
  • @Jubatian mutexes/critical-sections are fine if that's the way you want to go; just be aware that the `volatile` keyword doesn't really help you when dealing with lockless data-sharing by multiple threads (it guarantees *some* of the behaviors you need, but not all of them, and for reliable behavior, you need all of the guarantees -- you can search this site for "volatile multithreading" for more info on the topic) – Jeremy Friesner May 04 '22 at 17:40
  • @JeremyFriesner Yes, I am aware of volatile's pitfalls, in the example I am using it only to control the compiler's optimization, to avoid variables to be protected reordered around the mutex operations intending to protect them. I would mostly seek for a solution for use-cases which seem more complex than what would be doable lockless (the fifo above is just an arbitrary example hoping to demonstrate the case well enough). Could be a viable answer to advise going lockless with atomics (please post as answer if you feel that right). – Jubatian May 09 '22 at 09:21

0 Answers0