{"id":9389,"date":"2023-04-19T18:01:53","date_gmt":"2023-04-19T16:01:53","guid":{"rendered":"https:\/\/playembedded.org\/?p=9389"},"modified":"2024-02-24T16:31:04","modified_gmt":"2024-02-24T15:31:04","slug":"avoiding-race-conditions-in-chibiosrt-a-guide-to-mutex","status":"publish","type":"post","link":"https:\/\/playembedded.org\/blog\/avoiding-race-conditions-in-chibiosrt-a-guide-to-mutex\/","title":{"rendered":"Avoiding Race Conditions in ChibiOS\/RT: a guide to Mutex"},"content":{"rendered":"<p>In this article, we will explain what race conditions are and provide examples to help you understand their impact on multi-threading applications.<\/p>\n\n\n\n<p>We will also introduce a powerful tool to prevent race conditions: the mutex. We will explain what a mutex is, how it works, and provide examples of how to use it in ChibiOS. By the end of this article, you will have a better understanding of how to avoid race conditions and write more reliable ChibiOS code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading level_1\" id=\"1_Race_condition\">Race condition<\/h2>\n\n\n\n<p>A race condition is a phenomenon that can occur in concurrent or multi-threaded programs when two or more threads access shared resources or execute critical sections of code in an unpredictable order, leading to unexpected behavior and incorrect results. In a race condition, the outcome of the program depends on the exact timing and interleaving of the threads, which can vary between different runs of the same program. Race conditions can cause various problems, including data corruption, deadlocks, and inconsistent behavior, and they can be difficult to diagnose and fix.<\/p>\n\n\n\n<p>To understand the problem better let us look at the following code where two threads are juggling a shared variable. One thread is incrementing the variable while the other is decreasing it.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">static int32_t shared_variable = 0;\n\nstatic THD_FUNCTION(Thread1, arg) {\n  ...\n  while (true) {\n    shared_variable++;\n    chThdSleepMilliseconds(1);\n  }\n}\n\nstatic THD_FUNCTION(Thread2, arg) {\n  ...\n  while (true) {\n    shared_variable--;\n    chThdSleepMilliseconds(1);\n  }\n}<\/pre>\n\n\n\n<p>The threads in the code are designed to run at the same time interval, with the expectation that the shared_variable will remain at 0 and only briefly be 1 or -1 depending on which thread runs first. However, there is a problem with the <code>shared_variable++<\/code> and <code>shared_variable--<\/code> operations. These operations are not atomic, meaning they are not performed as a single, indivisible step and can be interrupted by other operations. <\/p>\n\n\n\n<p>For instance, the operation <code>shared_variable++<\/code> entails reading the current value of <code>shared_variable<\/code> from memory, incrementing this value, and then writing the updated value back to memory. Considering an ARM Cortex architecture, the assembly translation of this C instruction would appear as follows:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"asm\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">LDR R0, =shared_variable ; Load the address of shared_variable into R0\nLDR R1, [R0]             ; Load the value of shared_variable into R1\nADD R1, R1, #1           ; Increment the value in R1\nSTR R1, [R0]             ; Store the value back into shared_variable\n<\/pre>\n\n\n\n<p>On the other hand, the <code>shared_variable--<\/code> instruction in this context would be represented as:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"asm\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">LDR R0, =shared_variable ; Load the address of shared_variable into R0\nLDR R1, [R0]             ; Load the value of shared_variable into R1\nSUB R1, R1, #1           ; Decrement the value in R1\nSTR R1, [R0]             ; Store the value back into shared_variable\n<\/pre>\n\n\n\n<p>In a fully pre-emptive operating system like ChibiOS, a thread can be interrupted at any time by another thread with a higher priority. For example, Thread1 can be preempted in the middle of the operation shared_variable++  after that the current value of the variable is loaded in a CPU register.<\/p>\n\n\n\n<p>Consider a scenario illustrating a race condition with assembly code. Initially, <code>shared_variable<\/code> is 0. Thread 1 begins by loading and incrementing <code>shared_variable<\/code>. However, before Thread 1 can complete its operation, Thread 2 preempts. Thread 2 performs its entire cycle, decrementing <code>shared_variable<\/code>, which results in a value of -1. When Thread 1 resumes, it continues with the outdated value it loaded earlier (which was 0), incrementing it to 1. This outcome illustrates a race condition where <code>shared_variable<\/code> ends up being 0 instead of the expected 1, due to concurrent access and modification by both threads without synchronization.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"asm\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">; Initial state: shared_variable is 0. Thread 1 starts incrementing.\nLDR R0, =shared_variable ; Load the address of shared_variable into R0\nLDR R1, [R0]             ; Load the value of shared_variable into R1\n\n; Pre-emption occurs here. Thread 2 runs its complete cycle.\nLDR R0, =shared_variable ; Load the address of shared_variable into R0 (again for Thread 2)\nLDR R1, [R0]             ; Load the value of shared_variable into R1 (for Thread 2)\nSUB R1, R1, #1           ; Decrement the value in R1 (Thread 2 operation)\nSTR R1, [R0]             ; Store the decremented value back into shared_variable\n; shared_variable is now -1 after Thread 2's operation.\n\n; Thread 1 resumes, using the value of shared_variable before preemption.\nADD R1, R1, #1           ; Increment the value in R1 (Thread 1 resumes)\nSTR R1, [R0]             ; Store the incremented value back into shared_variable\n; shared_variable is now 0, but it was expected to be 1!<\/pre>\n\n\n\n<p>This is a race condition and its result can be unpredictable because it depends on the order of execution of the threads accessing the same variable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading level_1\" id=\"2_Mutual_exclusion\">Mutual exclusion<\/h2>\n\n\n\n<p>The solution to the issue of race conditions is to prevent threads from interrupting each other during operations on shared resources. Specifically, we want to ensure that two threads sharing a resource cannot operate on it simultaneously, which is where mutual exclusion comes in. The idea behind mutual exclusion is to create a zone in which only one thread can access the resource at any given time.<\/p>\n\n\n\n<p>The mechanism for enforcing mutual exclusion is called a <strong>mutex<\/strong>. In ChibiOS\/RT, mutexes are implemented as a modular library that can be disabled at compile time. To use mutexes in your code, you need to ensure that they are enabled in <code>chconf.h<\/code>.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/**\n * @brief   Mutexes APIs.\n * @details If enabled then the mutexes APIs are included in the kernel.\n *\n * @note    The default is @p TRUE.\n *\/\n#if !defined(CH_CFG_USE_MUTEXES)\n#define CH_CFG_USE_MUTEXES                  TRUE\n#endif<\/pre>\n\n\n\n<p>While the ChibiOS\/RT mutex API includes several functions, in practice, you can achieve mutual exclusion in most cases using just three main functions: <code>chMtxObjectInit<\/code>, <code>chMtxLock<\/code>, and <code>chMtxUnlock<\/code>.<\/p>\n\n\n\n<p>All the functions operate on a variable of type <code>mutex_t<\/code> that represents a mutex. As these APIs need to modify the mutex they receive a pointer to it. <\/p>\n\n\n\n<h3 class=\"wp-block-heading level_2\" id=\"3_chMtxObjectInit\">chMtxObjectInit<\/h3>\n\n\n\n<p>The <code>chMtxObjectInit <\/code>function is used to initialize a mutex object, which allocates any necessary resources and sets the initial state of the mutex. This initialization needs to be called before using the mutex: this means that you may want to initialize the object in the main before instantiating the threads  that are going to rely on the mutex itself.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/**\n * @brief   Initializes s @p mutex_t structure.\n *\n * @param[out] mp       pointer to a @p mutex_t structure\n *\n * @init\n *\/\nvoid chMtxObjectInit(mutex_t *mp)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading level_2\" id=\"4_chMtxLock_and_chMtxUnlock\">chMtxLock and chMtxUnlock<\/h3>\n\n\n\n<p>The <code>chMtxLock <\/code>function is used to acquire the mutex and enter a mutually exclusive zone. If the mutex is already held by another thread, the calling thread will be blocked until the mutex is released. The <code>chMtxUnlock <\/code>function is used to release the mutex and exit the mutually exclusive zone, allowing other threads to acquire the mutex and access the shared resource.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/**\n * @brief   Locks the specified mutex.\n * @post    The mutex is locked and inserted in the per-thread stack of owned\n *          mutexes.\n *\n * @param[in] mp        pointer to the @p mutex_t structure\n *\n * @api\n *\/\nvoid chMtxLock(mutex_t *mp)\n\n\/**\n * @brief   Unlocks the specified mutex.\n * @note    Mutexes must be unlocked in reverse lock order. Violating this\n *          rules will result in a panic if assertions are enabled.\n * @pre     The invoking thread &lt;b&gt;must&lt;\/b&gt; have at least one owned mutex.\n * @post    The mutex is unlocked and removed from the per-thread stack of\n *          owned mutexes.\n *\n * @param[in] mp        pointer to the @p mutex_t structure\n *\n * @api\n *\/\nvoid chMtxUnlock(mutex_t *mp)<\/pre>\n\n\n\n<h2 class=\"wp-block-heading level_1\" id=\"5_How_to_use_the_mutex\">How to use the mutex<\/h2>\n\n\n\n<p>The working principle is quite simple: if a thread tries to acquire a mutex that is already locked, it gets suspended until the mutex does not get released. De facto this forbids that thread to enter its mutual exclusion zone as another thread is keeping the mutex locked. <\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">    \/* Begin of the mutual exclusion zone. *\/\n    chMtxLock(&amp;my_mutex);\n\n    \/* Doing some operation on the shared variable. *\/\n\n    chMtxUnlock(&amp;my_mutex);\n    \/* End of the mutual exclusion zone. *\/<\/pre>\n\n\n\n<h3 class=\"wp-block-heading level_2\" id=\"6_Protecting_a_shared_variable\">Protecting a shared variable<\/h3>\n\n\n\n<p>The following code shows once more the example proposed before where two threads are accessing a shared variable. This time the two threads are getting synchronized by acquiring and releasing the same mutex.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#include \"ch.h\"\n#include \"hal.h\"\n#include \"chprintf.h\"\n\n\/* Shared variable. *\/\nstatic volatile int32_t shared_var = 0;\n\n\/* Mutex. *\/\nstatic mutex_t my_mutex;\n\nstatic THD_WORKING_AREA(waThread1, 128);\nstatic THD_FUNCTION(Thread1, arg) {\n\n  (void) arg;\n\n  while (true) {\n\n    \/* Begin of the mutual exclusive zone. *\/\n    chMtxLock(&amp;my_mutex);\n    shared_var++;\n    chMtxUnlock(&amp;my_mutex);\n    \/* End of the mutual exclusive zone. *\/\n\n    chThdSleepMilliseconds(1);\n  }\n}\n\nstatic THD_WORKING_AREA(waThread2, 128);\nstatic THD_FUNCTION(Thread2, arg) {\n\n  (void) arg;\n\n  while (true) {\n\n    \/* Begin of the mutual exclusive zone. *\/\n    chMtxLock(&amp;my_mutex);\n    shared_var--;\n    chMtxUnlock(&amp;my_mutex);\n    \/* End of the mutual exclusive zone. *\/\n\n    chThdSleepMilliseconds(1);\n  }\n}\n\n\nint main(void) {\n\n  \/* ChibiOS\/HAL and ChibiOS\/RT initialization. *\/\n  halInit();\n  chSysInit();\n\n  sdStart(&amp;SD5, NULL);\n\n  \/* Initializing the mutex. *\/\n  chMtxObjectInit(&amp;my_mutex);\n\n  \/* Contenders. *\/\n  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO - 2, Thread1, NULL);\n  chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO - 1, Thread2, NULL);\n\n  while (true) {\n    chprintf((BaseSequentialStream*)&amp;SD5, \"%d\\r\\n\", shared_var);\n    chThdSleepMilliseconds(200);\n  }\n}\n<\/pre>\n\n\n\n<p>Note that the mutex is getting initialized in the main before being used.<\/p>\n\n\n\n<h3 class=\"wp-block-heading level_2\" id=\"7_Protecting_a_shared_hardware_resource\">Protecting a shared hardware resource<\/h3>\n\n\n\n<p>In certain use cases, it may be necessary for two threads to access the same hardware resource. In such cases, a mutex can be used to ensure that only one thread at a time can access the resource. Some drivers in ChibiOS\/HAL have native support for mutexes to facilitate multithreaded applications. For example, the SPI driver offers specific APIs, such as <code>spiAcquireBus<\/code> and <code>spiReleaseBus<\/code>, which lock and unlock a mutex associated with that driver.<\/p>\n\n\n\n<p>To better understand how this works, let&rsquo;s consider the following example.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">static THD_FUNCTION(Thread1, arg) {\n  ...\n  while (true) {\n    spiAcquireBus(&amp;SPID1);\n    spiStart(&amp;SPID1, &amp;spicfg_thd1);\n \n    \/* Some SPI operations. *\/\n    spiSelect(&amp;SPID1);\n    spiExchange(&amp;SPID1, ...);\n    spiUnselect(&amp;SPID1);\n   \n    spiReleaseBus(&amp;SPID1);\n    chThdSleepMilliseconds(100);\n  }\n}\n\nstatic THD_FUNCTION(Thread2, arg) {\n  ...\n  while (true) {\n    spiAcquireBus(&amp;SPID1);\n    spiStart(&amp;SPID1, &amp;spicfg_thd2);\n \n    \/* Some SPI operations. *\/\n    spiSelect(&amp;SPID1);\n    spiExchange(&amp;SPID1, ...);\n    spiUnselect(&amp;SPID1);\n   \n    spiReleaseBus(&amp;SPID1);\n    chThdSleepMilliseconds(100);\n  }\n}<\/pre>\n\n\n\n<p>In the example, both <code>Thread1<\/code> and <code>Thread2<\/code> are operating on the same SPI driver, but before starting any activity, they first acquire the bus by calling the <code>spiAcquireBus<\/code> function, which locks the mutex associated with SPID1. Similarly, when the operation is complete, each thread releases the bus by calling the <code>spiReleaseBus<\/code> function, allowing the other thread to use the SPI driver.<\/p>\n\n\n\n<p>It is important to note that after acquiring the bus, each thread reconfigures the SPI with its own unique configuration.<\/p>\n\n\n\n<p>However, it is worth noting that this API is only available if the <code>SPI_USE_MUTUAL_EXCLUSION<\/code> switch is enabled in <code>halconf.h<\/code>. The reason behind this switch is that the capability of acquiring and releasing the bus relies on ChibiOS\/RT to provide the mutex functionality. Meanwhile, ChibiOS\/HAL can be run standalone therefore switches like this allow HAL to be decoupled from RT completely.<\/p>\n\n\n\n<h2 class=\"wp-block-heading level_1\" id=\"8_Priority_inversion_and_priority_inheritance\">Priority inversion and priority inheritance<\/h2>\n\n\n\n<p>Blocking shared resources can lead to <strong>priority inversion<\/strong>, where a higher-priority thread is blocked by a lower-priority thread holding the resource. Unlike semaphores, ChibiOS mutexes are not affected by this issue because ChibiOS\/RT handles thread priorities opportunistically when using mutexes, applying <strong>priority inheritance<\/strong>. To gain a better understanding of this topic, it&rsquo;s necessary to have a broader explanation of how ChibiOS\/RT scheduling works. Therefore, we invite you to read <a href=\"https:\/\/playembedded.org\/the-complete-reference-for-multithreading-in-chibios-rt\/\" data-type=\"post\" data-id=\"9915\">The Complete Reference for Multithreading in ChibiOS\/RT<\/a> if you want to learn more.<\/p>\n\n\n\n<h2 class=\"wp-block-heading level_1\" id=\"9_Further_reading\">Further reading<\/h2>\n\n\n\n<p>In conclusion, race conditions can be a significant issue in ChibiOS applications, causing unpredictable and incorrect program behavior. However, by using the ChibiOS\/RT mutex API, you can prevent race conditions and ensure that threads access shared resources in a coordinated and mutually exclusive manner.<\/p>\n\n\n\n<p>While this article provides an overview of the ChibiOS\/RT mutex API and how to use it to prevent race conditions, there is much more to learn about ChibiOS and concurrent programming. For further reading, we recommend consulting the article <a href=\"https:\/\/www.chibios.org\/dokuwiki\/doku.php?id=chibios:documentation:books:rt:kernel_mutexes\">RT mutexes and condition variables<\/a> on the official of ChibiOS, which includes detailed information on the mutex API.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>In this article, we will explain what race conditions are and provide examples to help you understand their impact on multi-threading applications. We will also introduce a powerful tool to prevent race conditions: the mutex. We will explain what a mutex is, how it works, and provide examples of how to use it in ChibiOS. [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":9410,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1221],"tags":[1304,1299],"coauthors":[241],"class_list":["post-9389","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-further-readings","tag-chibios-rt","tag-rtos","pumpkin"],"views":3248,"jetpack_featured_media_url":"https:\/\/playembedded.org\/blog\/wp-content\/uploads\/2023\/04\/Avoiding-Race-Conditions-in-ChibiOS-RT-A-guide-to-MutexPE.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/9389","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/comments?post=9389"}],"version-history":[{"count":0,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/posts\/9389\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media\/9410"}],"wp:attachment":[{"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/media?parent=9389"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/categories?post=9389"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/tags?post=9389"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/playembedded.org\/blog\/wp-json\/wp\/v2\/coauthors?post=9389"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}