6. Mutexes

 

This tutorial covers the basic use of Mutual Exclusion Semaphores (Mutexes). These are particular semaphores used to 'protect' unique resources used by several tasks. You'll see what this means next.

 

1. What's the problem?

 

Just try this simple application:

/*
 * main.c
 *
 *  Created on: 24/02/2018
 *      Author: Laurent
 */

#include "main.h"

// Static functions
static void SystemClock_Config	(void);

// FreeRTOS tasks
void vTask1 	(void *pvParameters);
void vTask2 	(void *pvParameters);


// Main program
int main()
{
	// Configure System Clock
	SystemClock_Config();

	// Initialize LED pin
	BSP_LED_Init();

	// Initialize the user Push-Button
	BSP_PB_Init();

	// Initialize Debug Console
	BSP_Console_Init();

	// Start Trace Recording
	vTraceEnable(TRC_START);

	// Create Tasks
	xTaskCreate(vTask1, "Task_1", 256, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task_2", 256, NULL, 2, NULL);

	// Start the Scheduler
	vTaskStartScheduler();

	while(1)
	{
		// The program should never be here...
	}
}

/*
 *	Task_1
 */
void vTask1 (void *pvParameters)
{
	while(1)
	{
		my_printf("With great power comes great responsibility\r\n");
		vTaskDelay(20);
	}
}

/*
 *	Task_2
 */
void vTask2 (void *pvParameters)
{
	while(1)
	{
		my_printf("#");
		vTaskDelay(1);
	}
}

Here, Task_1 and Task2 are just sending messages to the console. The problem is that the message printing process in Task_1 takes 4 to 5ms. In this delay, Task_2 is activated several times. As Task_2 has higher priority level, Task_1 is suspended while Task_2 executes.

 

As a result, we've got the Task_1 message cluttered with '#'... hum, that's not good.

image_000.png
 
 
The trace record just confirms what we already assumed:
 
image_001.png
 
 
Of course, reversing the priority of Task_1 and Task_2 would solve this problem. But let us assume here that this is not what we want to do. Say, Task_2 could perform some other, highly important things, and we want to keep it at a higher priority level.
 
 

2. Here comes the Mutex

 
The problem introduced above is quite common with RTOS. It rises from an unique resource (the print peripheral here, a.k.a. USART2), either hardware (peripheral) or software (e.g. a global variables) that may be used by more than one task at a time. There are various way to prevent such collision. An usual method is to involve a specific semaphore mechanism called Mutual Exclusion semaphore (Mutex). Let us try this.
 
As for binary semaphore, the first step is to declare the Mutex as global variable:
...
// Kernel Objects
xSemaphoreHandle xConsoleMutex;
...

 

Second, we need to create the Mutex object, within main() function:

...
	// Create a Mutex for accessing the console
	xConsoleMutex = xSemaphoreCreateMutex();

	// Give a nice name to the Mutex in the trace recorder
	vTraceSetMutexName(xConsoleMutex, "Console Mutex");
...

 

Finally, the Mutex can be used to 'protect' the unique ressource:

*
 *	Task_1
 */
void vTask1 (void *pvParameters)
{
	while(1)
	{
		// Take Mutex
		xSemaphoreTake(xConsoleMutex, portMAX_DELAY);

		// Send message to console
		my_printf("With great power comes great responsibility\r\n");

		// Release Mutex
		xSemaphoreGive(xConsoleMutex);

		vTaskDelay(20);
	}
}

/*
 *	Task_2
 */
void vTask2 (void *pvParameters)
{
	while(1)
	{
		// Take Mutex
		xSemaphoreTake(xConsoleMutex, portMAX_DELAY);

		// Send message to console
		my_printf("#");

		// Release Mutex
		xSemaphoreGive(xConsoleMutex);

		vTaskDelay(1);
	}
}

 

Unlike the binary semaphore, which is usually first given and then taken by two different tasks, the Mutex is first taken, and then released by the same task. It's a way for that task to say others "Hey, if you want to use that resource I'm using now, please don't interrupt me!".

 

Now watch the result of the above code: problem solved!

image_002.png
 
 
The trace below show the whole process. It is worth taking some time to understand what's happening into the details.
  • At t ≈ 2.112s, both Task_1 and Task_2 are ready for doing their jobs. Because Task_2 has higher priority level, it takes the mutex, prints '#', releases the mutex and then go to wait for 1ms. During this time, Task_1 is waiting.
  • Right after this, Task_1 is activated. It takes the mutex and starts printing its long message.
  • At t ≈ 2.113ms, Task2 is again activated, suspending Task1. It tries to take the mutex, but it is not available, therefore it enters the blocked state.
  • Right after, Task1 resumes and we observe that OS sets its priority to the same level as the one of Task_2. This mechanism, called priority inheritance is related to the use of mutexes. Because Task_2 is now blocked by Task_1, the scheduler consider that Task_1 is as important as Task_2. This mechanism prevents a well-known issue when using mutexes, that arises when a third task of intermediate priority comes into the game, blocking Task_2 execution (by blocking Task_1) despite its lower priority level.
  • After t ≈ 2.116s, Task_1 is done printing the message and releases the mutex. Instantly, Task_2 resumes, and then both tasks go to waiting state.

End of story!

image_003.png