In this post, we will learn about how to use Mutex to protect a shared resource in a multi-tasking system using freeRTOS. Whenever there are two or more tasks need to access the same resource, it is responsible of the application writer to make sure that the resource can only be accessed by one task at a time and that task has finished processing with the shared resource before handing it over to another task. In FreeRTOS, Mutex (MUTual EXclusion) is a technique to avoid corruption of a shared resource. In this tutorial, we will learn about what mutex is and its API functions. We will demonstrate the concepts by showing a practical project on Arduino where we have two tasks, task A and task B, sending information to serial port to display on Serial Monitor.
Mutex introduction
The general concept of mutex is simple. To protect the shared resource, one assign a special card (mutex) to the resource. Whoever has the card can access the resource exclusively, everybody has to wait until the card is returned and then can request the card to have access to the resource.
In our system, there are two tasks, task A and task B which both need to send information to the serial interface. The serial interface is the shared resource. We will create a mutex called serial_mutex to control access to the serial interface. If a task wants to print something on the Serial Monitor via serial interface, it must first obtain the serial_mutex. When it has done writing to the serial port, it must return serial_mutex so that other tasks can take it. If a task attempts to take serial_mutex but it is not available, it can opt to go to blocked state to wait for serial_mutex to become available again.
FreeRTOS Mutex APIs
Create a mutex
Before a mutex is used, it must be created explicitly. The function to create a mutex is xSemaphoreCreateMutex(). The name suggests mutex is a type of semaphore. We have learnt about binary semaphore in the previous post. Previously, we learnt how to defer processing from an interrupt service routine to a task by using binary semaphore. Although binary semaphore and mutex are both semaphores, they are used in different contexts and for different purposes. Binary semaphore is for synchronising whereas mutex is used for protecting a shared resource. The xSemaphoreCreateMutex() does not take any parameter and return a variable of type SemaphoreHandle_t. If the mutex can not be created, xSemaphoreCreateMutex() return NULL. In our project, we create serial_mutex by:
Taking a mutex
When a task wishes to access a resource, it first needs to take the mutex by calling function xSemaphoreTake(). We have seen this function in the previous post. This function takes two parameters: the first parameter is the mutex to be taken, the second parameter is xTicksToWait which specifies the amount of time the calling task are willing to wait in blocked state for the mutex to become available.
Giving a mutex
After accessing the shared resource and finishing its job, the task that took the mutex must return the mutex so that other tasks can access the resource. The task returns the mutex by calling xSemaphoreGive() function. We have seen xSemaphoreGiveFromISR() in the previous post which is an ISR safe version of xSemaphoreGive(). The xSemaphoreGive() function takes a parameter which is the mutex to be given.
In our project, when task A wants to send information to Serial Monitor, it must first attempt to take the serial_mutex. Task A will look like this:
Similarly, when task B wants to send data to serial, it must try to take serial_mutex. Task B will look like this:
Notes on using mutex
Although mutex is a effective way of protecting a shared resource, care must be taken to avoid certain problems, for example, deadlock or priority inversion. Deadlock can happen in certain situations, for example, when two tasks wait in blocked state forever because each task waits for mutex that is hold by the other task. Priority inversion happens when a high priority task has to wait in blocked state for a lower priority task to finish executing because the lower priority task is holding a mutex that prevents the high priority task executes.
Running on Arduino
The full code is shown below:
To compile and run this code, the FreeRTOS library for Arduino must be installed first. In Arduino IDE, go to Sketch->Include library->Manage Libraries… and search for freeRTOS. Install the freeRTOS library by Richard Barry. The library is included by the macro #include <Arduino_FreeRTOS.h>
. To use Mutex, you will also need to include semaphore library by #include <semphr.h>
.
Compile and run the code will show this result. Data printed by task A and task B is not corrupted as accessing to the Serial is protected by the mutex.
Wrapping Up
That’s all for this post. Hopefully you gain some understanding in using mutex in FreeRTOS. We will be learning more about FreeRTOS in coming posts.